This document defines the expected behavior for stdlib test running.
Applies to:
stdlib/test.turtur test command path)tests/fixtures/** for test-bearing fixture directories.input.tur.tests/fixtures/errors/** and validated against expected.diag.expected.stdout where provided.deftest registers test definitions in a global registry in declaration order.run-tests! executes registered tests in deterministic order (declaration order).assert expected actual: pass iff values are equal.assert-true x: pass iff x is boolean true.assert-false x: pass iff x is boolean false.assert-nil x: pass iff x is nil.assert-error body: pass iff evaluating body emits an error per test harness policy.:ptr<void>.:ptr<void> is expected is supported.nil where :ptr<void> is expected is supported as a null-callback convention.:ptr<void> is a compile-time arity mismatch.0: all tests pass.1: at least one test fails.2: harness/runtime/configuration error prevented reliable execution.Streaming output:
. per passing testF per failing testSummary output at end:
Failure detail block includes:
expected.timeout)A fixture directory may contain an expected.timeout file whose content is an
integer number of seconds. The test runner kills the compiled binary and marks
the fixture as failed if it runs longer than this timeout. The default when the
file is absent is 10 seconds. Set the value to 0 to disable the timeout
for a fixture.
The runner uses timeout(1) (GNU coreutils), gtimeout (Homebrew coreutils on
macOS), or a perl -e 'alarm N' fallback.
requires.tsan / TUR_TSAN=1)A fixture directory may contain an empty requires.tsan marker file.
TUR_TSAN is not set (default): fixtures with requires.tsan are
skipped (counted as PASS with a (tsan-skipped) detail).TUR_TSAN=1: all fixtures run normally, including those with
requires.tsan. The compiler flags gain -fsanitize=thread -g.Run the concurrency fixtures under TSan with the dedicated recipe (Justfile,
via tur run or just):
tur run test-tsan
It builds a Release (non-ASan) tur into build-tsan/, then runs
tests/run-tsan.sh against it: every requires.tsan fixture plus the thread, STM, channel and scheduler
families, the same selection the nightly TSan workflow runs. For every fixture,
point TUR at that build:
TUR=./build-tsan/tur TUR_TSAN=1 bash tests/run.sh
Not at the default build/tur: that one is ASan-instrumented, fixture
programs link the runtime archives next to it, and ASan and TSan cannot share
a process.
Three failure shapes in this tree look exactly like product regressions and are not. Recognize them before you start bisecting. See test-suite-portability-guide.md for the platform-divergence counterparts (vacuous enumerations, heap probes under ASan, harness env parity, unspecified string-literal merging).
ASan and UBSan abort with exit code 1 on a deadly signal. Any
fork-and-classify harness whose outcome enum uses small exit codes will
therefore tally a sanitizer-killed child as whatever category owns code
1 -- the crash disappears into a legitimate-looking bucket and the
summary stays green.
Two requirements for any such harness:
40..46 for exactly this reason.Two distinct causes, both observed:
ctest -jN oversubscribes. tests/run.sh and tests/run-turi.sh
each already fan out across nproc internally, so -j4 is 4 x nproc
processes on the box, and the per-fixture timeouts (10s compiled, 15s
interpreted) expire on work that would otherwise finish comfortably.
Both targets are marked RUN_SERIAL in CMakeLists.txt so ctest gives
them the machine, which is what they already assumed.cmake --build relinks the compiler mid-run. Fixtures
exec ./build/tur straight out of the build tree. During the link
window the file exists but is not yet executable, so everything
dispatched in that window dies with Permission denied, which the
harness reports as build failed. A batch of those reads as a compiler
regression.tests/run.sh stamps the binary at startup, re-checks at the end, and
exits 2 with a WARNING: ... changed while this run was in progress if
it moved. Other harnesses do not, so learn the shape instead:
an assertion that passes when you run it by hand was probably never
really run. Re-run alone before believing a failure, and never launch a
build and a suite concurrently.
--keep-contracts themselvesA fixture that asserts contract or refinement runtime behavior must
put --keep-contracts in its own flags file. It must never inherit that
behavior from how tur happened to be built: a Debug tur checks
contracts, a Release tur strips them under NDEBUG.
Nine refine-* fixtures inherited Debug-ness, passed under
tests/run.sh, and failed under the Release-built tests/run-jit.sh,
where the check never fired. The same applies to diagnostics that depend
on CT1 obligation injection, such as TUR-E0375 -- with contracts
stripped the obligation is never injected and the diagnostic is never
reported.
If the fixture asserts a runtime abort, --keep-contracts is part of what
it is testing. Declare it.
stdlib/test.tur implementation work.tur test CLI command behavior and fixture validations.