Skip to content

tests: test_connect_all_parallel_execution asserts a 0.2s wall-clock threshold with ~2ms of real margin #226

Closed

Description

@ViperJuice

tests/test_client_manager.py::TestParallelConnections::test_connect_all_parallel_execution proves that connect_all runs connections in parallel by mocking _connect_server with await asyncio.sleep(0.1), running three of them, and asserting:

assert elapsed < 0.2, f"Expected parallel execution, took {elapsed}s"

Parallel is ~0.1 s and serial would be ~0.3 s, so the threshold sits midway — but the slack over parallel is only ~100 ms, and that has to absorb coverage instrumentation (--cov in CI), event-loop scheduling, and a shared runner. It does not, reliably. Two consecutive test (3.10) failures on PR #225, whose diff touches only exception handlers:

run elapsed margin
33718867432 0.22305679321289062 s 23 ms over
33727450531 0.2019734382629... s 2 ms over

Measured 10 consecutive local runs on both that PR's branch and main: 10/10 pass on each, so this is environment sensitivity, not a regression — the test is simply calibrated too tightly to survive a loaded runner.

Why it is worth fixing rather than re-running: a two-millisecond margin makes the test a coin flip under load, and it fails in a way that looks like the PR under review broke something. It cost two review cycles on #225 alone.

Suggested fix — assert the property directly instead of via wall clock. The test already records call_times; concurrency is provable from them without any threshold on total elapsed:

assert max(call_times) - min(call_times) < 0.05   # all three started together

That is what "parallel" actually means here, and it does not tighten as the machine slows down. If a wall-clock bound is still wanted as a backstop, < 0.25 keeps the serial case (~0.3 s) excluded with 2.5x the current margin.

Found while working #224; not caused by it.

Activity

  1. ViperJuice commented on Sep 5, 2026

    @ViperJuice
    CollaboratorAuthor

    Folded into #235, which covers the same class across the suite: non-hermetic tests, 65 real wall-clock sleeps, unreset module caches, and a test reaching the npm registry.

    Keeping this open as the concrete instance — it has the measurements (two CI failures at 23 ms and 2 ms over a 0.2 s threshold, and 10/10 local passes on both branches) and a specific fix that asserts concurrency from the recorded call_times rather than from total elapsed. Whoever takes #235 should do this one first: it is the cheapest, and it is the only one of the group that has already cost review cycles on unrelated PRs.

  2. ViperJuice commented on Sep 21, 2026

    @ViperJuice
    CollaboratorAuthor

    Closing evidence (this issue was auto-closed by the merge commit; recording the verification here).

    Fixed by #268 (slice C1 of #235). test_connect_all_parallel_execution no longer measures wall-clock time at all — each of the three mock_connect bodies passes a Rendezvous(3) released only when the third arrives, so "parallel" is asserted as the property it actually is rather than inferred from a margin that shrinks as the runner slows.

    The fix suggested in this issue — max(call_times) - min(call_times) < 0.05 — was deliberately not adopted: it is still a wall-clock upper bound, so it has the same failure mode under load, just with a different constant.

    Verified:

    • 30/30 consecutive passes under --cov=pmcp, the instrumentation that widened the original margin past 0.2 s.
    • A serial _connect_all_unlocked (each task awaited before the next is created) fails the test in ~4.3 s, inside its 5 s hang guard, with Failed to connect to …: only 1 of 3 parties were in flight together — the regression is still caught, and now names itself.
    • Green on CI for 3.10, 3.11 and 3.12.

    The same PR removed the other ten elapsed < X upper bounds across the suite and added tests/_timing.py plus a CONTRIBUTING rule, so the class does not come back by hand.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions