From e7130de838d0e107ff458d2c38166aecd9ea67e0 Mon Sep 17 00:00:00 2001 From: Niclas Overby Date: Wed, 19 Aug 2026 18:07:23 +0200 Subject: [PATCH] test(cider): script the testsuite staging, because rebuilding it by hand loses two details THE SUITE IS THE REGRESSION NET FOR THIS FRAMEWORK and it lives in a container prefix rather than in buck-out, since the cases are guest binaries. The prefix tmp had been cleared since the last run, so re-establishing the net today meant redoing the staging by hand, and it went wrong in the way it always will. RESOURCES KEEP THEIR PATHS. A case asks for its resource by the path it has in the source tree, so DARLING_TESTSUITE_RESOURCE_PATH must point at a directory that still has testsuite/... underneath. Copying every resource directory into one flat directory makes test_read_file fail its fd >= 0 assertion, which reads exactly like a defect in our read and is nothing of the kind. That is the second time a rig has made that case look broken. THE RUNNER MUST NOT EAT THE CASE OUTPUT. An AppKit case prints two lines of backend greeting before anything of its own, so a runner that keeps the first two lines keeps nothing that matters, and a case that prints on its happy path looks as silent as one that never ran. The staged runner drops the backend lines and keeps three of the case own. MEASURED WITH THE SCRIPT, and it reproduces the state exactly: 69 case targets, 36 built, 36 run, 28 exit zero, AppKit 11 of 12. That is the same as before today's AppKit changes, so the table background fix, the alert button metrics and the view tree instrument cost nothing. The libxpc group still does not build and its one case that does still fails, which is unchanged and known. --- scripts/stage-testsuite.py | 112 +++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100755 scripts/stage-testsuite.py diff --git a/scripts/stage-testsuite.py b/scripts/stage-testsuite.py new file mode 100755 index 000000000..03bfefd19 --- /dev/null +++ b/scripts/stage-testsuite.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 +"""Stage the built darling-testsuite cases into a container prefix (task #123). + +The suite is a regression net for AppKit and libc, and it lives in the container rather than in +buck-out: the cases are guest binaries, so they have to be run through `cider shell`. Re-creating +that staging by hand costs an hour and gets one detail wrong every time, which is what this is for. + +Two details are the ones that get lost: + +RESOURCES KEEP THEIR PATHS. A case asks for its resource by the path it has in the source tree: + + grab_full_resource_path(c, "testsuite/usr/lib/system/libsystem_kernel.dylib/read/resources/read_hello_world.txt") + +so DARLING_TESTSUITE_RESOURCE_PATH must point at a directory that still has `testsuite/...` under +it. Copying every `resource`/`resources` directory into one flat directory makes `test_read_file` +fail its `fd >= 0` assertion, which reads exactly like a libc defect and is not one. + +THE RUNNER MUST NOT EAT THE CASE OUTPUT. An AppKit case prints two lines of backend greeting before +anything of its own, so a runner that keeps the first two lines keeps nothing that matters. A case +that prints on its happy path then looks as silent as one that never ran. + +Usage: scripts/stage-testsuite.py [--prefix /tmp/cider-appkit-1000/prefix] + Build first: buck2 build --keep-going --show-output $(targets) > outputs.txt + or let this run buck2 itself with --build. +""" +import os +import re +import shutil +import subprocess +import sys + +REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +SUITE = "vendor/src/darling-testsuite" + +RUNNER = """#!/bin/sh +DARLING_TESTSUITE_RESOURCE_PATH=/tmp/dts-res +export DARLING_TESTSUITE_RESOURCE_PATH +cd /tmp/dts-res +for f in /tmp/dts/dts_*; do + [ -x "$f" ] || continue + out=$("$f" 2>&1) + code=$? + echo "DTSRESULT $code $(basename $f)" + echo "$out" | grep -v "^cider-wayland" | head -3 | sed 's/^/DTSOUT /' +done +echo DTSDONE +""" + + +def case_targets(): + body = open(os.path.join(REPO, "vendor/src/BUCK"), errors="replace").read() + names = re.findall(r'name = "(dts_[A-Za-z0-9_]*)"', body) + return ["//vendor/src:" + n for n in names if not n.endswith("_obj")] + + +def build(targets): + """--keep-going, because the libxpc group does not build and the rest still should.""" + out = subprocess.run( + ["buck2", "build", "--keep-going", "--show-output"] + targets, + cwd=REPO, capture_output=True, text=True, + ).stdout + return [l.split(None, 1)[1] for l in out.split("\n") if l.startswith("root//") and " " in l] + + +def stage(prefix, paths): + dts = os.path.join(prefix, "tmp/dts") + res = os.path.join(prefix, "tmp/dts-res") + os.makedirs(dts, exist_ok=True) + if os.path.exists(res): + shutil.rmtree(res) + os.makedirs(res) + + staged = 0 + for p in paths: + full = os.path.join(REPO, p) + if not os.path.isfile(full): + continue + shutil.copy2(full, os.path.join(dts, os.path.basename(full))) + staged += 1 + + dirs = 0 + for root, _sub, _files in os.walk(os.path.join(REPO, SUITE)): + if os.path.basename(root) not in ("resource", "resources"): + continue + rel = os.path.relpath(root, os.path.join(REPO, SUITE)) + target = os.path.join(res, rel) + os.makedirs(os.path.dirname(target), exist_ok=True) + shutil.copytree(root, target, symlinks=True) + dirs += 1 + + runner = os.path.join(dts, "run.sh") + open(runner, "w").write(RUNNER) + os.chmod(runner, 0o755) + return staged, dirs + + +def main(): + prefix = "/tmp/cider-appkit-1000/prefix" + if "--prefix" in sys.argv: + prefix = sys.argv[sys.argv.index("--prefix") + 1] + targets = case_targets() + print(f"{len(targets)} case targets") + paths = build(targets) + print(f"{len(paths)} built") + staged, dirs = stage(prefix, paths) + print(f"staged {staged} binaries and {dirs} resource directories into {prefix}/tmp") + print("run it with scratchpad/run-dts-wayland.sh, NEVER headless: an AppKit case dies at its") + print("first use of a display that correctly declined to exist, which reads as a crash in us.") + + +if __name__ == "__main__": + main() -- 2.51.2