docs(listener): ListenerQUIC::Port(), and port 0 for tests

The README gains a QUIC section, which it never had: plain QUIC under an
ALPN of your own, starting with a listener on port 0 and Port(). The
consumer notes describe Port() and point tests at port 0 rather than at
binding a socket by hand; the agent notes count the new test and make
port 0 the default for a new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-11-01 01:46:29 +01:00
commit fdb0ac2524
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
3 changed files with 22 additions and 4 deletions

View file

@ -51,7 +51,7 @@ crafter-build # native: TCP, TLS, HTTP/1.1, QUI
crafter-build --no-quic # native without QUIC, HTTP/3, WebTransport and msquic
crafter-build --local # Crafter.Thread from ../Crafter.Thread
crafter-build --target=wasm32-wasip1 # the browser library
crafter-build test # the suite, native: 30 tests
crafter-build test # the suite, native: 31 tests
crafter-build test 'Should*HTTP1' # a name glob
crafter-build test --no-quic # the TCP, URL, TLS and HTTP/1.1 tests plus ShouldBuildWithoutQUIC: 19
crafter-build test --list
@ -69,7 +69,7 @@ Conventions the tests follow:
- Names are `Should<Verb><What>`; the `SendRecieve*` and `Interop*` families end in `HTTP1` or `HTTPS1` on the HTTP/1.1 side and `HTTP` or `QUIC` on the QUIC side, and `ShouldSend`, `ShouldNotDropEarlyStreams`, `ShouldFallbackUnknownRoutes` and `ShouldSurviveConnectionChurn` carry no suffix. Open `main.cpp` with a comment on the behaviour under test and the failure it guards against; `tests/ShouldRecieveTCP/main.cpp` and `tests/ShouldSurviveConnectionChurn/main.cpp` show the shape, and a regression test names the bug.
- A `Check(ok, what)` helper in an anonymous namespace counts failures and prints them (`PASS`/`FAIL` lines in the newer tests), and `main` returns nonzero at the end; the oldest QUIC tests return at the first failure. Do not stop at the first failure unless later steps are meaningless without it.
- Each test owns fixed ports, distinct across the suite because the runner is parallel: 8090 to 8097 and 8110 to 8114 for HTTP/1.1 listeners, 8082 to 8086 and 8098 for HTTP/3, 9181, 9182, 9187 and 9188 for QUIC, 19200 to 19224 for the churn test; the two TCP tests bind ephemeral ports, and `ShouldBuildWithoutQUIC` and `ShouldSend` bind none. Pick an unused one for a new test, or bind an ephemeral port yourself (`LoopbackListener` in `ShouldConfigureTCP`).
- Each test owns fixed ports, distinct across the suite because the runner is parallel: 8090 to 8097 and 8110 to 8114 for HTTP/1.1 listeners, 8082 to 8086 and 8098 for HTTP/3, 9181, 9182, 9187 and 9188 for QUIC, 19200 to 19224 for the churn test; the two TCP tests bind ephemeral ports, as do the QUIC tests written since `ListenerQUIC::Port()` (port 0, read back), and `ShouldBuildWithoutQUIC` and `ShouldSend` bind none. A new test binds port 0 and reads `Port()` (`ListenerTCP` and `ListenerQUIC` both have it); a fixed port is for when it cannot.
- `ThreadPool::Start()` first, for anything that enqueues: every `*Async` call, `ClientHTTP` (its constructor installs an `OnStream` drain that runs as pool tasks), `ListenerHTTP`, `ListenerQUIC`, and the `*Async`-dispatch `Listen*` variants of `ListenerTCP`; `ListenerHTTP1` and the sync TCP listeners need none, which is why ten tests never call it. `Start()` runs `hardware_concurrency() - 1` workers and `Start(1)` none at all (tasks then wait for a `JoinPool()` nobody calls), so don't use `Start(1)` to make callbacks deterministic.
- A QUIC test ends in `std::_Exit(code)`, after `std::fflush(stdout)` since `_Exit` flushes nothing (`ShouldReportClosedQUICStream` does; the older ones skip the flush and can lose their last line in the log): msquic's two registrations are closed by static destructors that block until every connection is torn down, and the peers a listener accepted are heap-allocated for the callback's owner, so returning from `main` waits for the idle timeout. `ShouldSurviveConnectionChurn` is the exception on purpose: that wait is its assertion, so it deletes everything and returns, under a watchdog thread that calls `std::_Exit(1)` after 90 s, which the runner's 60 s timeout beats; a watchdog meant to fire goes under 60 s, as the 8 s one in `ShouldNotDropEarlyStreams` does. Any test that can hang gets one.
- Credentials: `QUICServerCredentials{ .selfSigned = true }` against `QUICClientCredentials{ .insecureNoServerValidation = true }`; for TLS, `TLSServerCredentials{ .selfSigned = true }` against a client given `GetSelfSignedCertificatePem().certificate` as `caPem`, so verification is exercised rather than skipped.