From 90af21e1327d5c59d7572b00c703c63c30b1d032 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 00:39:53 +0900 Subject: [PATCH] test/package: wire the runtime fixture set into package_test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test/package/runtime/ was a manual README-driven corpus with no target. Its unique surface — the driver's single-file ww test leg, the unmet-expected-abort outcome, and the published -c binary's own CLI (repeated globs, -list, -timeout-ms, direct-binary timeout and descendant handling) — is now asserted by three new @test fns in package_test.ww; the README drops its manual-run instructions. --- test/package/package_test.ww | 160 +++++++++++++++++++++++++++++++++ test/package/runtime/README.md | 62 +++++-------- 2 files changed, 180 insertions(+), 42 deletions(-) diff --git a/test/package/package_test.ww b/test/package/package_test.ww index cbfd4ffc..648aa196 100644 --- a/test/package/package_test.ww +++ b/test/package/package_test.ww @@ -442,3 +442,163 @@ fn packagepath(relative: str) str = { "/route_test.test.sepwork/__root.s")))); clean(root); }; + +fn runtimepath(relative: str) str = { + return strings.concat(repo(), "/test/package/runtime/", relative); +}; + +// The single-FILE `ww test ` route wraps the same in-binary test +// runtime as the directory-package route (cases/*), but through the +// driver's single-file leg. One row per outcome class keeps that leg's +// wiring honest; the runtime-internal classifications themselves are +// owned by the cases/* tests above. +@test fn singlefile_runtime_outcomes() void = { + let root: str = fresh(); + let out: commandout; + let successav: []str = [driver("ww"), "test", + runtimepath("success_test.ww")]; + runcommand(root, "sf-success", successav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "alpha_pass ... ok\n")); + assert(has(out.stdout, + "beta_skip ... SKIP: synthetic unavailable feature\n")); + assert(has(out.stdout, "gamma_expected_abort ... ok\n")); + assert(has(out.stdout, "delta_pass ... ok\n")); + assert(has(out.stdout, + "3 passed, 0 failed, 1 skipped, 0 harness errors\n")); + assert(has(out.stdout, + "4 discovered, 4 selected, 4 started, 4 completed\n")); + + let failav: []str = [driver("ww"), "test", + runtimepath("fail_test.ww")]; + runcommand(root, "sf-fail", failav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, "assertion_failure ... FAIL (exit 1)\n")); + + let multiav: []str = [driver("ww"), "test", + runtimepath("multiple_fail_test.ww")]; + runcommand(root, "sf-multi", multiav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, + "0 passed, 2 failed, 0 skipped, 0 harness errors\n")); + + let unmetav: []str = [driver("ww"), "test", + runtimepath("expected_abort_return_test.ww")]; + runcommand(root, "sf-unmet", unmetav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, + "expected_abort_must_happen ... FAIL (expected abort)\n")); + + let premav: []str = [driver("ww"), "test", + runtimepath("premature_exit_test.ww")]; + runcommand(root, "sf-premature", premav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, + "clean_exit_without_completion ... HARNESS (incomplete result)\n")); + + let sigav: []str = [driver("ww"), "test", + runtimepath("signal_test.ww")]; + runcommand(root, "sf-signal", sigav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, "signal_is_not_exit ... FAIL (signal 15)\n")); + clean(root); +}; + +// The published test binary's OWN command surface: repeated glob args, +// -list (with and without a glob), and -timeout-ms. Only a direct +// invocation of the `-c` artifact proves these; every driver route +// parses its flags before the binary sees them. +@test fn published_binary_cli() void = { + let root: str = fresh(); + let out: commandout; + let stem: str = strings.concat(root, "/rtsuccess"); + let cav: []str = [driver("ww"), "test", "-c", "-o", stem, + runtimepath("success_test.ww")]; + runcommand(root, "publish", cav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + + let gav: []str = [stem, "alpha*", "delta*"]; + runcommand(root, "globs", gav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, "alpha_pass ... ok\n")); + assert(has(out.stdout, "delta_pass ... ok\n")); + assert(has(out.stdout, + "2 passed, 0 failed, 0 skipped, 0 harness errors\n")); + assert(has(out.stdout, + "4 discovered, 2 selected, 2 started, 2 completed\n")); + + let lav: []str = [stem, "-list"]; + runcommand(root, "list", lav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(out.stdout, + "alpha_pass\nbeta_skip\ngamma_expected_abort\ndelta_pass\n")); + + let lgav: []str = [stem, "-list", "gamma*"]; + runcommand(root, "list-glob", lgav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(same(out.stdout, "gamma_expected_abort\n")); + + let tav: []str = [stem, "-timeout-ms=50", "alpha*"]; + runcommand(root, "cli-timeout", tav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, + "1 passed, 0 failed, 0 skipped, 0 harness errors\n")); + clean(root); +}; + +// Direct-binary timeout classification and descendant handling. The +// runcommand deadline bounds the no-hang claims: a runner waiting on a +// SIGTERM-blocking or process-group-escaped pipe writer would trip it. +@test fn direct_binary_timeout_and_descendants() void = { + let root: str = fresh(); + let out: commandout; + let tstem: str = strings.concat(root, "/rttimeout"); + let tcav: []str = [driver("ww"), "test", "-c", "-o", tstem, + runtimepath("timeout_test.ww")]; + runcommand(root, "publish-timeout", tcav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let trun: []str = [tstem, "-timeout-ms=50"]; + runcommand(root, "run-timeout", trun, + (20i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 1); + assert(has(out.stdout, "hang_times_out ... FAIL(timeout)\n")); + + let lstem: str = strings.concat(root, "/rtlinger"); + let lcav: []str = [driver("ww"), "test", "-c", "-o", lstem, + runtimepath("lingering_descendant_test.ww")]; + runcommand(root, "publish-linger", lcav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let lrun: []str = [lstem, "-timeout-ms=250"]; + runcommand(root, "run-linger", lrun, + (20i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, + "returning_test_clears_descendant ... ok\n")); + + let estem: str = strings.concat(root, "/rtescaped"); + let ecav: []str = [driver("ww"), "test", "-c", "-o", estem, + runtimepath("escaped_descendant_test.ww")]; + runcommand(root, "publish-escaped", ecav, + (30i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + let erun: []str = [estem, "-timeout-ms=250"]; + runcommand(root, "run-escaped", erun, + (20i64 * (time.second: i64)): time.duration, &out); + expectexit(&out, 0); + assert(has(out.stdout, + "escaped_writer_does_not_hold_runner ... ok\n")); + clean(root); +}; diff --git a/test/package/runtime/README.md b/test/package/runtime/README.md index 49275663..af3d9a5e 100644 --- a/test/package/runtime/README.md +++ b/test/package/runtime/README.md @@ -1,45 +1,23 @@ -# Native package-runtime fixtures +# Package-runtime fixtures -These fixtures exercise the compiled package test binary, rather than a -subprocess-per-assertion harness. Build the bootstrap tools once with `make all`, -then run the focused cases from the repository root: +Single-file `@test` sources driven by `test/package/package_test.ww` +(the `make test-package` binary): `singlefile_runtime_outcomes`, +`published_binary_cli`, and `direct_binary_timeout_and_descendants`. +They exercise the driver's single-file `ww test` leg and the published +`-c` binary's own command surface (repeated globs, `-list`, +`-timeout-ms`); the directory-package legs of the same outcome classes +are owned by `test/package/cases/*`. -```sh -out/bin/ww test test/package/runtime/success_test.ww -out/bin/ww test -c -o /tmp/ww-runtime-test test/package/runtime/success_test.ww -/tmp/ww-runtime-test 'alpha*' 'delta*' -/tmp/ww-runtime-test -list -/tmp/ww-runtime-test -list 'gamma*' -/tmp/ww-runtime-test -timeout-ms=50 'alpha*' -out/bin/ww test test/package/runtime/fail_test.ww -out/bin/ww test test/package/runtime/multiple_fail_test.ww -out/bin/ww test test/package/runtime/expected_abort_return_test.ww -out/bin/ww test test/package/runtime/premature_exit_test.ww -out/bin/ww test test/package/runtime/signal_test.ww -out/bin/ww test -c -o /tmp/ww-runtime-timeout test/package/runtime/timeout_test.ww -/tmp/ww-runtime-timeout -timeout-ms=50 -out/bin/ww test -c -o /tmp/ww-runtime-descendant test/package/runtime/lingering_descendant_test.ww -/tmp/ww-runtime-descendant -timeout-ms=250 -out/bin/ww test -c -o /tmp/ww-runtime-escaped test/package/runtime/escaped_descendant_test.ww -/tmp/ww-runtime-escaped -timeout-ms=250 -``` +Fixture notes: -The success fixture reports two ordinary passes, one expected-abort pass, and -one skip with its reason. The preserved test binary accepts repeated glob -arguments directly. List mode prints selected names without starting tests. -Each test has a 30-second default timeout; `-timeout-ms=N` selects a positive -per-test timeout up to one hour for a direct test-binary invocation. -The current runtime recognizes `expectabort(); os.exit(nonzero)` as an expected -abort because WW's abort primitive itself terminates with a normal exit status; -the ABI cannot distinguish those two paths without a future abort hook. -The assertion, multiple-failure, unmet-expected-abort, premature-exit, and -signal fixtures fail respectively with a normal exit, two failures whose -process status is still clamped to 1, an expected abort that never happens, an -incomplete completion record, and signal 15; none may be mistaken for a pass. -The timeout fixture fails deterministically as `FAIL(timeout)`. The lingering -descendant fixture passes without waiting on the inherited control-pipe writer: -the descendant blocks SIGTERM, so the runner exercises its grace period and -SIGKILL escalation before draining the record. -The escaped-descendant fixture moves its inherited writer into another process -group for one second. The framed nonblocking protocol returns immediately after -the test leader and owned group complete; it never waits for pipe EOF. +- The runtime recognizes `expectabort(); os.exit(nonzero)` as an + expected abort because WW's abort primitive itself terminates with a + normal exit status; the ABI cannot distinguish those two paths + without a future abort hook. +- The lingering-descendant fixture blocks SIGTERM in its descendant, so + the runner exercises its grace period and SIGKILL escalation before + draining the record. +- The escaped-descendant fixture moves its inherited pipe writer into + another process group for one second; the framed nonblocking protocol + must return after the test leader and owned group complete, never + waiting for pipe EOF.