Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,17 @@ jobs:
- name: Fetch dependencies (with retries)
shell: bash
run: bash scripts/ci-retry.sh zig build --fetch
# Every test binary is compiled before any of them runs, so no compiler is alive beside a
# running test. On Windows, Zig 0.16 spawns each child inheriting every inheritable handle
# the build runner holds, the pipes it is making for its other children included. A
# compiler spawned while a test's pipes exist keeps a copy of that test's stdout: the test
# passes and exits, but its stdout reaches EOF only when the compiler exits, and the runner
# gives up after a minute with "test runner failed to respond", on whichever tiny test was
# unlucky, about one run in thirty. `check` is exactly the compiles `test` needs, so the
# step after it spawns only test binaries, which live for milliseconds.
- name: Compile tests
shell: bash
run: bash scripts/ci-retry.sh zig build check --summary all
- name: Run tests
shell: bash
run: bash scripts/ci-retry.sh zig build test --summary all
Expand Down
9 changes: 9 additions & 0 deletions core/transport/NativeTransport.zig
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,15 @@ test "cancel returns at once while the server is still holding the connection" {
const Hold = struct {
fn run(s: *std.Io.net.Server, held_io: std.Io, release: *std.atomic.Value(bool)) void {
const stream = s.accept(held_io) catch return;
// Read the request before holding: a socket closed with bytes still unread is reset,
// not closed, and Zig 0.16 reads a reset on Windows as `error.Unexpected`, dumping a
// stack trace into the test's stderr that reads as a failure.
var in_buf: [1024]u8 = undefined;
var out_buf: [64]u8 = undefined;
var reader = stream.reader(held_io, &in_buf);
var writer = stream.writer(held_io, &out_buf);
var http_server = std.http.Server.init(&reader.interface, &writer.interface);
_ = http_server.receiveHead() catch {};
while (!release.load(.acquire)) std.Io.sleep(held_io, .fromMicroseconds(1000), .awake) catch {};
stream.close(held_io);
}
Expand Down
7 changes: 7 additions & 0 deletions docs/PITFALLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ goes beside the code if it has an area, here if not.
for function-local structs with `var`s inside generic functions.
- **Tests run only from files their root module analyzes** (`tests/AGENTS.md`).
`zig build check-tests-reached` names any that don't.
- **On Windows, "test runner failed to respond" with every test passed means a leaked pipe.**
Zig 0.16's Windows spawn makes each child inherit every inheritable handle the build runner
holds, its other children's pipes among them. A long-lived child (a compiler, a sleeper)
spawned beside a test keeps that test's stdout open, so after the test exits the runner waits a
minute for EOF and fails it. CI compiles the tests (`zig build check`) before running them for
this; don't fold the two steps back together, or start anything long-lived inside `zig build
test`.

## Running a second fizzy

Expand Down
Loading