diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-23 22:25:28 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-24 16:45:37 +0200 |
| commit | 328b01a2dd545d70a078db8df1e91b02d65bfc9c (patch) | |
| tree | 9af69d617d9482ba4256f3d7334c9dd6cde0d5e2 /packages/meshbay-node/tests/test_task_lifetime.py | |
| parent | 92ea222b7aca6a3eb5f04330f0af6755e6e434e3 (diff) | |
| download | meshbay-328b01a2dd545d70a078db8df1e91b02d65bfc9c.tar.gz | |
test: read the WebRTC transport's source as a set of files
Source-reading tests take their text from node_source (node) and node_tree
(hub): webrtc_server.py plus anything under transport/webrtc/, so a check
for something's absence keeps reading the code it guards if that code moves.
test_node_source_scope holds the boundary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-node/tests/test_task_lifetime.py')
| -rw-r--r-- | packages/meshbay-node/tests/test_task_lifetime.py | 51 |
1 files changed, 16 insertions, 35 deletions
diff --git a/packages/meshbay-node/tests/test_task_lifetime.py b/packages/meshbay-node/tests/test_task_lifetime.py index 133c030..a8445fa 100644 --- a/packages/meshbay-node/tests/test_task_lifetime.py +++ b/packages/meshbay-node/tests/test_task_lifetime.py @@ -25,33 +25,28 @@ file. """ import re -from pathlib import Path import pytest - -SERVER = (Path(__file__).resolve().parents[1] / "src" / "meshbay_node" - / "transport" / "webrtc_server.py") +from node_source import TRANSPORT, session_method, session_source, webrtc_source pytestmark = pytest.mark.skipif( - not SERVER.exists(), reason="the node sources are not available") + not TRANSPORT.exists(), reason="the node sources are not available") @pytest.fixture(scope="module") def source(): - return SERVER.read_text(encoding="utf-8") + return webrtc_source() @pytest.fixture(scope="module") -def session(source): - """The body of WebRTCPeerSession.""" - i = source.index("class WebRTCPeerSession") - return source[i:source.index("\nclass WebRTCTransport")] +def session(): + """The body of WebRTCPeerSession, and of every class it is built from.""" + return session_source() def test_the_session_keeps_a_reference_to_what_it_starts(session): assert "self._tasks: set[asyncio.Task] = set()" in session - spawn = session[session.index("def _spawn("):] - spawn = spawn[:spawn.index("\n def ", 1)] + spawn = session_method("_spawn") assert "self._tasks.add(task)" in spawn, "the reference is what keeps it alive" assert "self._tasks.discard(" in spawn and "add_done_callback(" in spawn, ( "without this the set grows for the life of the session") @@ -72,8 +67,7 @@ def test_nothing_in_the_session_is_fired_and_forgotten(session): def test_the_stream_still_takes_a_slot_for_its_whole_life(session): """The leak is only interesting because the slot is held this way.""" - body = session[session.index("async def _stream_video(self"):] - body = body[:body.index("\n async def ", 1)] + body = session_method("_stream_video") assert "async with sem:" in body @@ -83,13 +77,11 @@ def test_closing_a_session_releases_its_tasks(session): The cancelling itself lives in shutdown_tasks, which the state handler also uses; close() is the variant that additionally shuts the peer connection. """ - close = session[session.index("async def close(self)"):] - close = close[:close.index("\n\n")] if "\n\n" in close else close + close = session_method("close") assert "shutdown_tasks()" in close assert "self._pc.close()" in close - fn = session[session.index("async def shutdown_tasks(self)"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("shutdown_tasks") assert "_stop_stream()" in fn assert "task.cancel()" in fn assert "gather" in fn, "cancelling without awaiting does not run the exits" @@ -129,8 +121,7 @@ def test_a_new_request_retires_the_previous_stream(session): assert "self._spawn(self._replace_stream(msg))" in session, ( "the stream request must go through the path that retires the old one") - fn = session[session.index("async def _replace_stream(self"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("_replace_stream") assert "self._stop_stream()" in fn assert "await asyncio.wait_for(asyncio.shield(prev)" in fn, ( "the slot comes back when the old task exits its `async with sem` — " @@ -173,8 +164,7 @@ def test_losing_the_peer_stops_its_work(source): def test_shutdown_is_separate_from_closing_the_connection(session): """The state handler runs while aiortc is already tearing pc down.""" - fn = session[session.index("async def shutdown_tasks(self)"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("shutdown_tasks") assert "self._pc.close()" not in fn, ( "calling pc.close() from the state handler re-enters the teardown") assert "task.cancel()" in fn and "gather" in fn @@ -186,8 +176,7 @@ def test_a_dead_channel_is_noticed_while_waiting_not_after(source): Waiting the whole budget on a channel that is already shut is the slot being held for nothing, which is what the log above shows. """ - fn = source[source.index("async def _await_stream_credit"):] - fn = fn[:fn.index("\n async def ", 1)] + fn = session_method("_await_stream_credit") before = fn[:fn.index("wait_for")] assert 'readyState != "open"' in before, ( "the channel must be checked before the wait, not only after it") @@ -221,8 +210,7 @@ def test_the_pipes_are_drained_before_waiting_on_ffmpeg(source): Which is the reported failure exactly: one video fine, the next hanging, the one after refused. """ - fn = source[source.index("async def _stream_video_inner"):] - fn = fn[:fn.index("\n def _send(")] + fn = session_method("_stream_video_inner") finally_block = fn[fn.index("finally:"):] assert "proc.kill()" in finally_block @@ -234,8 +222,7 @@ def test_the_pipes_are_drained_before_waiting_on_ffmpeg(source): def test_releasing_the_slot_does_not_depend_on_ffmpeg_behaving(source): """SIGKILL has already been sent; the OS will reap it either way.""" - fn = source[source.index("async def _stream_video_inner"):] - fn = fn[:fn.index("\n def _send(")] + fn = session_method("_stream_video_inner") tail = fn[fn.index("wait_for(proc.wait()"):] assert "except Exception" in tail, ( "a timeout waiting for ffmpeg must not stop the slot coming back") @@ -253,13 +240,7 @@ def test_chunks_wait_for_room_on_the_channel(session): work through the rest — which is what "stuck at 1 MB" looks like, one chunk being exactly one megabyte. """ - start = session.index("async def _do_file_request") - # Up to whatever the next member is. This used to end at - # "\n def _do_stream_segment" — a neighbour removed in MNP 2.0 — and an - # `index()` on a name that no longer exists fails the test for a reason - # that has nothing to do with what it is about. - nxt = re.search(r"\n (?:@|(?:async )?def )", session[start:]) - fn = session[start:start + nxt.start()] if nxt else session[start:] + fn = session_method("_do_file_request") assert "DOWNLOAD_BUFFER_HIGH" in fn, "the send buffer has to be watched" assert "await asyncio.sleep" in fn, "waiting for room is the point" assert 'readyState != "open"' in fn, ( |