Skip to content

OpenBSD and Windows: make ci portability batch - #3645

Merged
paul-hammant merged 11 commits into
mainfrom
fix/openbsd-windows-batch
Nov 10, 2026
Merged

paul-hammant merged 11 commits into
mainfrom
fix/openbsd-windows-batch

Conversation

@paul-hammant

Copy link
Copy Markdown
Collaborator

One batch, a commit per fix, from running make ci on OpenBSD 8.0 and Windows 11 (MSYS2) lab hosts.

OpenBSD (65 failures to 0: 2153 pass, 67 skip)

Product fixes (842e5fe):

  • std.audio links without -ldl where libc carries dlopen.
  • checksec reads RETGUARD as the stack-protector evidence.
  • The spawn stdio slot fix.
  • SSL_CERT_FILE is honoured under LibreSSL.

Tests:

  • Portable shell tools throughout the shell suite (no GNU-only flags, no grep "a\|b").
  • TLS tests run against LibreSSL's openssl.
  • Tests skip, with the reason printed, what the host genuinely lacks (musl cross builds need a zig that runs on OpenBSD; not addressed here).
  • deep_constants scans its C by split, not substr per character.

Windows

Test fixes only:

  • win_batch_compiler_long_cmdline keeps each path under MAX_PATH.
  • release_contrib_c_modules skips avcodec on Windows by uname.
  • operator_chain_nesting and the bracket-depth walks avoid slow gawk paths and run in the C locale.
  • crypto_tls13_peer builds its seven programs at once at -O0, and its test servers drain before closing a refused handshake.

Verification (make ci on this branch)

Host Result
OpenBSD 8.0 2153 pass, 0 fail, 67 skip
ae-x64, Ubuntu 22.04, gcc -Werror 2197 pass, 0 fail, 25 skip
cachyos, gcc 16 2203 pass, 0 fail, 19 skip
FreeBSD 15.1 (GhostBSD) 2195 pass, 0 fail, 27 skip
Windows 11 MSYS2 2107 pass, 1 fail, 114 skip

The Windows failure is no_threads_refs reaching the 420 s per-test limit during the full sweep. It is untouched by this branch and passes alone on the same VM in 382 s, so it is slow on that 4-core VM, not hung; left as is.

Closes no issues.

🤖 Generated with Claude Code

paul-hammant and others added 11 commits November 10, 2026 02:45
OpenBSD's userland differs from GNU/macOS/FreeBSD in ways a test script
must not depend on, and each showed up as a failure that read as a product
bug:

- tests/lib/syslibs.sh: -ldl and -lrt only on Linux (BSDs and macOS have
  dlopen and shm in libc; the linker fails outright on -ldl there).
- tests/lib/portable.sh: head_c (no head -c), sha256_of (no shasum),
  copy_listed (no tar -T), dyn_syms (nm -D/-gU/-g), file_desc (file prints
  "data" for wasm/COFF/Mach-O and "AArch64"), od_uint (od zero-pads -tu).
- 75 `grep "a\|b"` became `grep -E "a|b"`: OpenBSD's grep reads `\|` as a
  literal pipe, so the warnings filter of ~35 tests let warnings into
  compared output and positive matches never matched.
- `\b`, `\s` became POSIX classes; `grep -r --exclude=NAME` became find.
- http_proxy_direct_equivalence compares Content-Length after the port
  rewrite (the echoed body holds the port; OpenBSD's ephemeral range
  includes four-digit ports).

Co-Authored-By: Claude <noreply@anthropic.com>
… stdio slot, LibreSSL SSL_CERT_FILE

Four gaps the test suite exposed (see the changelog fragment):

- ae: the -ldl codegen's link table names for std.audio is dropped on
  OpenBSD (dlopen is in libc; no libdl exists).
- ae checksec: __retguard_* / __stack_smash_handler count as the stack
  canary; OpenBSD's clang emits no __stack_chk_fail for a plain array.
- std.os: on OpenBSD/NetBSD a pipe's stat is dev 0/ino 0 and its time is a
  10 ms clock, so a spawn pipe that took a closed stdout's number within a
  tick of process start passed for the stream and was handed to the child.
  A stream that was not close-on-exec at start no longer matches one that is.
- std.http client: LibreSSL ignores SSL_CERT_FILE/SSL_CERT_DIR in
  SSL_CTX_set_default_verify_paths; name them explicitly, as OpenSSL and the
  pure-Aether client do.

Co-Authored-By: Claude <noreply@anthropic.com>
- x509 -req signs with SHA-1 under LibreSSL, which the pure TLS stack
  rightly refuses ("does not chain to a trusted anchor"): pass -sha256.
- s_client's LibreSSL output names the version "TLSv1/SSLv3" on success and
  "(NONE)" on failure: https_tls_backend_select reads that.
- openssl_is_libressl / openssl_has_option (tests/lib/test_openssl_conf.sh):
  skip, naming the reason, what needs ssl_conf (https_openssl_policy),
  s_server -cert_chain (https_partial_chain), s_client -sigalgs
  (crypto_tls13_signature_check and a case of https_pure_server_keys) or
  Ed25519 certificates in TLS (a case of https_pure_server_keys).

Co-Authored-By: Claude <noreply@anthropic.com>
- ae_download_api_fallback: no release build for the host ("Platform: unsupported").
- musl targets (cross_linux_musl, cross_riscv64_loongarch64, sqlite_vendored_cross,
  and the cross legs of c_include_directive and source_directive): only when
  zig reports it could not build musl's libc.a, which it cannot on OpenBSD.
- cross_emit_lib: no target triple whose binary runs on OpenBSD/NetBSD.
- http_ipv6: OpenBSD's sockets are IPv6-only, so "::" is not dual-stack.
- string_into_c_memory / ownership_grid: no malloc_usable_size on OpenBSD.
- sandbox_toolchain: spawn_sandboxed is Linux and FreeBSD only.
- checksec_hardening: libc has no __*_chk, so FORTIFY is asserted to be "no".
- killed_compiler_diagnostic: kill the stand-in with SIGKILL; ksh answers a
  SIGTERM to itself with exit 143, which is an exit, not a signal death.

Co-Authored-By: Claude <noreply@anthropic.com>
The nesting scan read each line with substr($0, i, 1). The awk of OpenBSD
rescans the line from its start on every call, and these lines hold
1000-link constant chains: 5m52s alone, past the 420s per-test limit once the
suite ran in parallel. One split() per line: 10s.

Co-Authored-By: Claude <noreply@anthropic.com>
The test pushed the C command line past cmd.exe's 8191 cap with 25 --extra
paths of a fixed deep directory. Under a temp directory like MSYS2's
C:/msys64/tmp each path came to about 270 characters, past Windows' MAX_PATH,
so cc1 could open them only where long paths are enabled machine-wide
(LongPathsEnabled, set on GitHub's runners, off by default and on the win11
lab host): there it failed "No such file or directory" on every part, before
the response file the test is about was ever in question.

The deep directory is now sized from the temp directory's own length, so
each path is about 240 characters wherever TMP is, and as many parts are
added as keep the --extra list under ae's 8 KiB cap. The build runs
--verbose and the test checks ae says it wrote a response file, so the
command line is shown to be past the cap rather than assumed to be.

Co-Authored-By: Claude <noreply@anthropic.com>
…e's name

The avcodec check is meant to stay off Windows (ae links -static there and
MSYS2's static FFmpeg needs its whole dependency closure), and was gated on
$EXE being ".exe". $EXE came from a name probe, `[ ! -x build/ae ]`, which
MSYS2 answers true for build/ae.exe, so it stayed empty and the check ran
wherever MSYS2's FFmpeg is installed: on the win11 lab host it failed with
undefined soxr_* references from libswresample.a. CI's Windows job installs
no FFmpeg, so it never got that far.

Gated on uname now, as the other Windows-aware tests are. $EXE is left as it
was, so the direct-link check, which passes on Windows, still runs there.

Co-Authored-By: Claude <noreply@anthropic.com>
… gsub

max_seq_depth stripped every non-bracket from each generated line with
gsub(/[^][(){}]/, ""). MSYS2's gawk 5.4.0 takes 32 s for that on one of the
150 KB lines of the 1000-link chains (5.4.1 takes 0.4 s), and the test reads
seven such lines: it ran 262 s alone on the win11 lab host and past the
sweep's 420 s limit under load. The macros are now spelled out by sed and
the brackets kept by tr, so awk only walks the brackets: 12 s on the same
host with gawk 5.4.0 (181 s before), the same depths on macOS (2 4 5 5 5 5 8 7 5 5).

Co-Authored-By: Claude <noreply@anthropic.com>
…ndshake

server.ae and kuserver.ae closed the socket of a refused handshake with
tcp.close, while the library's own entry closes it with tcp.close_drained
(#3465). What the client sent after the refused record is still unread
then (in "server HRR: two change_cipher_spec records", the second CCS), so
the close ends with a reset, and on Windows that reset can take the alert
from the client before it reads it: the case saw "HRR CLOSED" for
"HRR ALERT 10". On the win11 lab host, 25 runs of that case: 8 failed with
the plain close, none drained.

Co-Authored-By: Claude <noreply@anthropic.com>
Each program is the whole pure TLS stack in one C file. GCC on Windows
compiled each in 18-23 s at -O2 (2-4 s on macOS), one after another, so the
builds alone took 168 s of the test's 440 on the win11 lab host (468 s with
its older MSYS2), past the sweep's 420 s limit under load; CI's 4-core
runner took 311 s. They now build in parallel with --quick (-O0 -g): 7-9 s
each there, and the cases, which check the bytes on the wire, take the same
time (about 1 s a handshake either way). The whole test: 242 s on win11 from
a cold cache, 90 s on macOS (101 before). Every build is waited for before a
failed one ends the test, so none still writes into $TMP as it is removed.

Co-Authored-By: Claude <noreply@anthropic.com>
deep_constants, deep_nesting, block_nesting_cc and operator_chain_nesting
measure the bracket depth of generated C with an awk loop that takes
substr($0, i, 1) for each character. Under a UTF-8 locale (MSYS2's default,
en_US.UTF-8, and CI's Windows runner) gawk's substr counts characters from
the start of the line on every call, so the walk is quadratic in a line.
deep_constants' consts.c has lines of 245 KB: its awk took 361 s on the
win11 lab host (the test 367 s alone, 771 s beside three others, past the
sweep's 420 s limit) and the test 292 s on CI's Windows runner. With
LC_ALL=C the same awk takes 6 s and the test 14 s on win11. The walks only
compare ASCII characters, which no byte of a UTF-8 multibyte character
equals, so the depths are the same (51, 1018, and the rest, on macOS and
win11).

Co-Authored-By: Claude <noreply@anthropic.com>
@paul-hammant
paul-hammant merged commit 4b3d26b into main Nov 10, 2026
75 of 76 checks passed
@paul-hammant
paul-hammant deleted the fix/openbsd-windows-batch branch November 10, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant