From 56ef0b962cd1877c4534c72d255bf0acd3ff6a45 Mon Sep 17 00:00:00 2001 From: mhchia Date: Mon, 2 Sep 2019 17:32:15 +0800 Subject: [PATCH] Add test for `host` `connect` and `disconnect` --- setup.py | 2 +- tests/conftest.py | 50 +++++++++++++++++++++++++++++- tests/interop/conftest.py | 19 ++++++++++++ tests/interop/daemon.py | 33 +++++++++++++++++--- tests/interop/test_bindings.py | 29 ++++++++++++++++++ tests/interop/test_pubsub_bind.py | 18 ----------- tests/pubsub/conftest.py | 51 ------------------------------- 7 files changed, 126 insertions(+), 76 deletions(-) create mode 100644 tests/interop/test_bindings.py delete mode 100644 tests/interop/test_pubsub_bind.py diff --git a/setup.py b/setup.py index f5eff257..9ddfbd12 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ extras_require = { "pytest-asyncio>=0.10.0,<1.0.0", "pexpect>=4.6,<5", # FIXME: Master branch. Use PyPI instead after it is released. - "p2pclient @ git+https://git@github.com/mhchia/py-libp2p-daemon-bindings@4777c62", + "p2pclient @ git+https://git@github.com/mhchia/py-libp2p-daemon-bindings@2647296", ], "lint": [ "mypy>=0.701,<1.0", diff --git a/tests/conftest.py b/tests/conftest.py index fd753be0..188c655d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,7 +3,8 @@ import asyncio import pytest from .configs import LISTEN_MADDR -from .factories import HostFactory +from .factories import FloodsubFactory, GossipsubFactory, HostFactory, PubsubFactory +from .pubsub.configs import GOSSIPSUB_PARAMS @pytest.fixture @@ -31,3 +32,50 @@ async def hosts(num_hosts, is_host_secure): await asyncio.gather( *[_host.close() for _host in _hosts], return_exceptions=True ) + + +@pytest.fixture +def floodsubs(num_hosts): + return FloodsubFactory.create_batch(num_hosts) + + +@pytest.fixture +def gossipsub_params(): + return GOSSIPSUB_PARAMS + + +@pytest.fixture +def gossipsubs(num_hosts, gossipsub_params): + yield GossipsubFactory.create_batch(num_hosts, **gossipsub_params._asdict()) + # TODO: Clean up + + +def _make_pubsubs(hosts, pubsub_routers, cache_size): + if len(pubsub_routers) != len(hosts): + raise ValueError( + f"lenght of pubsub_routers={pubsub_routers} should be equaled to the " + f"length of hosts={len(hosts)}" + ) + return tuple( + PubsubFactory(host=host, router=router, cache_size=cache_size) + for host, router in zip(hosts, pubsub_routers) + ) + + +@pytest.fixture +def pubsub_cache_size(): + return None # default + + +@pytest.fixture +def pubsubs_fsub(hosts, floodsubs, pubsub_cache_size): + _pubsubs_fsub = _make_pubsubs(hosts, floodsubs, pubsub_cache_size) + yield _pubsubs_fsub + # TODO: Clean up + + +@pytest.fixture +def pubsubs_gsub(hosts, gossipsubs, pubsub_cache_size): + _pubsubs_gsub = _make_pubsubs(hosts, gossipsubs, pubsub_cache_size) + yield _pubsubs_gsub + # TODO: Clean up diff --git a/tests/interop/conftest.py b/tests/interop/conftest.py index e85f2f6a..ef9de3f5 100644 --- a/tests/interop/conftest.py +++ b/tests/interop/conftest.py @@ -1,8 +1,11 @@ +import asyncio import sys import pexpect import pytest +from .daemon import make_p2pd + @pytest.fixture def proc_factory(): @@ -22,3 +25,19 @@ def proc_factory(): finally: for proc in procs: proc.close() + + +@pytest.fixture +def num_p2pds(): + return 1 + + +@pytest.fixture +async def p2pds(num_p2pds, is_host_secure, unused_tcp_port_factory): + p2pds = await asyncio.gather( + *[make_p2pd(unused_tcp_port_factory, is_host_secure) for _ in range(num_p2pds)] + ) + try: + yield p2pds + finally: + await asyncio.gather(*[p2pd.close() for p2pd in p2pds]) diff --git a/tests/interop/daemon.py b/tests/interop/daemon.py index 9bcb292f..c56bff72 100644 --- a/tests/interop/daemon.py +++ b/tests/interop/daemon.py @@ -39,14 +39,16 @@ class P2PDProcess: cmd: str = str(P2PD_PATH) args: List[Any] + _tasks: List["asyncio.Future[Any]"] + def __init__( self, control_maddr: Multiaddr, is_secure: bool, - is_pubsub_enabled=True, - is_gossipsub=True, - is_pubsub_signing=False, - is_pubsub_signing_strict=False, + is_pubsub_enabled: bool = True, + is_gossipsub: bool = True, + is_pubsub_signing: bool = False, + is_pubsub_signing_strict: bool = False, ) -> None: args = [f"-listen={str(control_maddr)}"] # NOTE: To support `-insecure`, we need to hack `go-libp2p-daemon`. @@ -68,6 +70,7 @@ class P2PDProcess: # - gossipsubHeartbeatInitialDelay: GossipSubHeartbeatInterval = 1 * time.Second # Referece: https://github.com/libp2p/go-libp2p-daemon/blob/b95e77dbfcd186ccf817f51e95f73f9fd5982600/p2pd/main.go#L348-L353 # noqa: E501 self.args = args + self._tasks = [] async def wait_until_ready(self): lines_head_pattern = (b"Control socket:", b"Peer ID:", b"Peer Addrs:") @@ -84,6 +87,24 @@ class P2PDProcess: # Sleep a little bit to ensure the listener is up after logs are emitted. await asyncio.sleep(0.01) + async def start_printing_logs(self) -> None: + async def _print_from_stream( + src_name: str, reader: asyncio.StreamReader + ) -> None: + while True: + line = await reader.readline() + if line != b"": + print(f"{src_name}\t: {line.rstrip().decode()}") + await asyncio.sleep(0.01) + + self._tasks.append( + asyncio.ensure_future(_print_from_stream("out", self.proc.stdout)) + ) + self._tasks.append( + asyncio.ensure_future(_print_from_stream("err", self.proc.stderr)) + ) + await asyncio.sleep(0) + async def start(self) -> None: self.proc = await asyncio.subprocess.create_subprocess_exec( self.cmd, @@ -93,10 +114,13 @@ class P2PDProcess: bufsize=0, ) await self.wait_until_ready() + await self.start_printing_logs() async def close(self) -> None: self.proc.terminate() await self.proc.wait() + for task in self._tasks: + task.cancel() class Daemon: @@ -165,5 +189,4 @@ async def make_p2pd( peer_info = info_from_p2p_addr( listen_maddr.encapsulate(Multiaddr(f"/p2p/{peer_id.to_string()}")) ) - print(f"!@# peer_info: peer_id={peer_info.peer_id}, maddrs={peer_info.addrs}") return Daemon(p2pd_proc, p2pc, peer_info) diff --git a/tests/interop/test_bindings.py b/tests/interop/test_bindings.py new file mode 100644 index 00000000..7844d4bb --- /dev/null +++ b/tests/interop/test_bindings.py @@ -0,0 +1,29 @@ +import asyncio + +from multiaddr import Multiaddr +import pytest + + +@pytest.mark.parametrize("num_hosts", (1,)) +@pytest.mark.asyncio +async def test_connect(hosts, p2pds): + p2pd = p2pds[0] + host = hosts[0] + assert len(await p2pd.control.list_peers()) == 0 + # Test: connect from Py + await host.connect(p2pd.peer_info) + assert len(await p2pd.control.list_peers()) == 1 + # Test: `disconnect` from Py + await host.disconnect(p2pd.peer_id) + assert len(await p2pd.control.list_peers()) == 0 + # Test: connect from Go + py_peer_id = host.get_id() + await p2pd.control.connect( + host.get_id(), + [host.get_addrs()[0].decapsulate(Multiaddr(f"/p2p/{py_peer_id.to_string()}"))], + ) + assert len(host.get_network().connections) == 1 + # Test: `disconnect` from Go + await p2pd.control.disconnect(py_peer_id) + # FIXME: Failed to handle disconnect + # assert len(host.get_network().connections) == 0 diff --git a/tests/interop/test_pubsub_bind.py b/tests/interop/test_pubsub_bind.py deleted file mode 100644 index 66793b40..00000000 --- a/tests/interop/test_pubsub_bind.py +++ /dev/null @@ -1,18 +0,0 @@ -import pytest - -from .daemon import make_p2pd - - -@pytest.mark.parametrize("num_hosts", (1,)) -@pytest.mark.asyncio -async def test_pubsub_init(hosts, is_host_secure, unused_tcp_port_factory): - try: - p2pd = await make_p2pd(unused_tcp_port_factory, is_host_secure) - host = hosts[0] - peers = await p2pd.control.list_peers() - assert len(peers) == 0 - await host.connect(p2pd.peer_info) - peers = await p2pd.control.list_peers() - assert len(peers) != 0 - finally: - await p2pd.close() diff --git a/tests/pubsub/conftest.py b/tests/pubsub/conftest.py index 1755ee59..e69de29b 100644 --- a/tests/pubsub/conftest.py +++ b/tests/pubsub/conftest.py @@ -1,51 +0,0 @@ -import pytest - -from tests.factories import FloodsubFactory, GossipsubFactory, PubsubFactory -from tests.pubsub.configs import GOSSIPSUB_PARAMS - - -@pytest.fixture -def floodsubs(num_hosts): - return FloodsubFactory.create_batch(num_hosts) - - -@pytest.fixture -def gossipsub_params(): - return GOSSIPSUB_PARAMS - - -@pytest.fixture -def gossipsubs(num_hosts, gossipsub_params): - yield GossipsubFactory.create_batch(num_hosts, **gossipsub_params._asdict()) - # TODO: Clean up - - -def _make_pubsubs(hosts, pubsub_routers, cache_size): - if len(pubsub_routers) != len(hosts): - raise ValueError( - f"lenght of pubsub_routers={pubsub_routers} should be equaled to the " - f"length of hosts={len(hosts)}" - ) - return tuple( - PubsubFactory(host=host, router=router, cache_size=cache_size) - for host, router in zip(hosts, pubsub_routers) - ) - - -@pytest.fixture -def pubsub_cache_size(): - return None # default - - -@pytest.fixture -def pubsubs_fsub(hosts, floodsubs, pubsub_cache_size): - _pubsubs_fsub = _make_pubsubs(hosts, floodsubs, pubsub_cache_size) - yield _pubsubs_fsub - # TODO: Clean up - - -@pytest.fixture -def pubsubs_gsub(hosts, gossipsubs, pubsub_cache_size): - _pubsubs_gsub = _make_pubsubs(hosts, gossipsubs, pubsub_cache_size) - yield _pubsubs_gsub - # TODO: Clean up