Repository navigation
test: run the unit suite across parallel processes (~3x faster) - #3860
Merged
Merged
Conversation
arrufat
force-pushed
the
test-parallel
branch
2 times, most recently
from
October 8, 2026 13:13
ca36903 to
26a484c
Compare
The test servers bind ephemeral ports instead of 9582-9584. Fixtures and tests keep addressing the fixed ports: in test builds, libcurl's CONNECT_TO routes them to the real ones, so URLs and origins are unchanged. Only the few CDP server tests that dial it, or assert a URL it advertises, use the real port. Tests that wrote to fixed paths (/tmp/lightpanda_test.sqlite, /tmp/iid, .zig-cache/tmp/upload.txt, files in the repo root) now use tmpDir.
The runner re-executes itself TEST_JOBS times (default: up to 4), each copy taking every Nth test, and merges the reports they write back into the usual summary. METRICS=true stays in a single process so the numbers remain comparable across runs.
Share the bound-address lookup as sys_net.boundAddress, set the test connect-to routes once per handle in Connection.reset, add a testing.tmpPath helper, and print the runner Report directly instead of round-tripping it through SlowTracker. Replace deprecated std.fmt.allocPrint and std.fs.path uses.
A fixed stride left one shard as the tail. Shards now take the next unclaimed test index from a counter in a shared mapped file, so the suite finishes when the work runs out rather than when the unluckiest shard does.
A deleted session's worker exits after the 200, so the previous test's can still be live when this one snapshots the gauge. Once tests are claimed dynamically that predecessor varies, and the snapshot either never comes back or matches while our own worker is still running. Wait for no bidi workers before and after instead.
Shards share the terminal, and the color escape went out in a write of its own, so another shard's reset could land between it and its dot and leave the dot uncolored.
runShards ends in exit(), which skips defers, so its temp dir was never removed; it now uses tmpDir and cleans up before exiting. With TEST_FAIL_FIRST a failing shard pushes the shared counter past the last test so its siblings stop too. Status lines are buffered so long ones also go out in one write, the Trace test writes under tmpDir, and the report printing and comments are trimmed.
karlseguin
reviewed
Oct 9, 2026
Someone filtering is usually debugging one case and wants as little variability as possible. TEST_JOBS still overrides it.
Contributor
Author
|
I ran the zig-test job 4 times on CI on the latest commit (047ed3e), and all runs passed. That includes the TSan debug job, which runs the suite across parallel processes. |
Contributor
Author
|
Let's hope it doesn't break often 🤞 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
make testnow splits the suite across up to 4 processes. Each one claims the next test nobody has taken yet, and the parent merges their results into the usual summary. On an 8-core machine a cachedmake testgoes from 10.9s to 3.4s.Isolation
127.0.0.1:9582,localhost:9582and:9584. In test builds,CURLOPT_CONNECT_TO(set once per handle inConnection.reset) routes those to the real ports. URLs, origins andHostheaders are unchanged, so none of the ~350 hardcoded references needed touching.App.init, so the connection pool picks up the routes as it's built.createTestClientand the two assertions on URLs the CDP server advertises use the real CDP port.std.testing.tmpDir, through a newtesting.tmpPathhelper:/tmp/lightpanda_test.sqlite/tmp/iid.zig-cache/tmp/upload.txt,a.txtandb.txtRunner
TEST_JOBSsets the number of processes. The default ismin(cpus, 4);TEST_JOBS=1restores the single-process run.TEST_FAIL_FIRSTstops every shard: the failing one pushes the shared counter past the last test. The failing test still shows in the summary (it used to be dropped).TEST_FILTERbehaves as before; filtered runs give the same counts with 1 or 4 jobs.METRICS=truestays in a single process, so the CIbench.jsonnumbers remain comparable.TEST_FILTER(orTEST_JOBS=1).Timings on 8 cores (cached build, test binary only, performance power profile, median of 5):
For order dependence, I ran the full suite 12 times with 2–8 jobs, 20 more times with 8 jobs (claiming gives each run a different split), and two full suites at the same time. That turned up one hidden dependence:
server: HTTP session ended before its worker attachedsnapshotted the bidi worker gauge while the previous test's deleted session was still winding down (a delete answers 200 before its worker exits). It now waits for no bidi workers before and after, and the HTTP session tests that delete their session at the end now also delete it if they fail before getting there, so one failure can't spill into the next.Also lists the slowest tests slowest first, shares the bound-port lookup as
sys_net.boundAddress(it was copied in five places) and replaces the deprecatedstd.fmt.allocPrint/std.fs.pathuses in the new code.