Files
ww/test/wwfixture/integration.sh
Hojun-Cho aa0f77b891 wwfixture: declarative fixture runner
Discovers test/wcc/data, hard-pins corpus identity (1,224 fixtures,
314 error / 12 compile / 136 run / 762 run-exit, sha256 name hash),
schedules both frontend cells per fixture through os.exec with
per-cell capture dirs, and emits the strict wwfix TSV result stream.
integration.sh is the black-box owner of the CLI/protocol boundary.
2026-08-07 23:21:04 +09:00

443 lines
16 KiB
Bash

#!/bin/sh
set -eu
LC_ALL=C
export LC_ALL
repo=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
tool=$repo/out/bin/wwfixture
work=$(mktemp -d "${TMPDIR:-/tmp}/wwfixture-test.XXXXXXXX")
coordinator=
cleanup()
{
if [ -n "$coordinator" ]; then
kill -KILL "$coordinator" 2> /dev/null || :
wait "$coordinator" 2> /dev/null || :
fi
for stream in "$work"/*.tab; do
[ -f "$stream" ] || continue
awk -F '\t' '$1 == "case" && $10 ~ /^\/tmp\/[0-9a-f]{16}\/[0-9]{4}$/ {
p=$10; sub("/[0-9]{4}$", "", p); print p
}' "$stream"
done | sort -u | while IFS= read -r run; do
if [ -d "$run" ]; then
find "$run" -depth -delete
fi
done
find "$work" -depth -delete
}
trap cleanup EXIT HUP INT TERM
fail()
{
echo "test/wwfixture: $*" >&2
exit 1
}
clone_corpus()
{
dest=$1
mkdir -p "$dest/test/wcc/data"
for source in "$repo"/test/wcc/data/*/case.ww; do
name=${source%/case.ww}
name=${name##*/}
mkdir "$dest/test/wcc/data/$name"
cp "$source" "$dest/test/wcc/data/$name/case.ww"
done
ln -s "$repo/out" "$dest/out"
ln -s "$repo/lib" "$dest/lib"
}
cleanup_capture()
{
stream=$1
capture=$(awk -F '\t' '$1 == "case" && $10 != "-" { print $10; exit }' \
"$stream")
[ -n "$capture" ] || return 0
run=${capture%/*}
id=${run#/tmp/}
[ "$run" != "$id" ] || fail "capture outside /tmp"
[ "${#id}" -eq 16 ] || fail "malformed capture run ID"
case $id in *[!0-9a-f]*) fail "unsafe capture run ID" ;; esac
[ -d "$run" ] || fail "missing retained capture"
find "$run" -depth -delete
}
cleanup_run()
{
run=$1
id=${run#/tmp/}
[ "$run" != "$id" ] || fail "retained run outside /tmp"
[ "${#id}" -eq 16 ] || fail "malformed retained run ID"
case $id in *[!0-9a-f]*) fail "unsafe retained run ID" ;; esac
[ -d "$run" ] || fail "missing retained run"
find "$run" -depth -delete
}
replace_directive()
{
path=$1
line=$2
tmp=$path.new
{
printf '%s\n' "$line"
sed -n '2,$p' "$path"
} > "$tmp"
mv "$tmp" "$path"
}
expect_invalid()
{
root=$1
pattern=$2
stream=$3
if "$tool" validate -root "$root" -run "$pattern" "$stream" \
> /dev/null 2>&1; then
fail "validator accepted $(basename -- "$stream")"
fi
}
base=$work/base
clone_corpus "$base"
# Filtering and listing publish identities only; no synthetic SKIP records.
list=$work/list.tab
"$tool" -root "$base" -run 'compiler.runww_dup_main_reject/c' -list > "$list"
[ "$(wc -l < "$list")" -eq 1 ] || fail "filtered list count"
[ "$(sed -n '1p' "$list")" = "compiler.runww_dup_main_reject c" ] \
|| fail "filtered list identity"
# Real positive fixtures must build successfully before their programs run.
runstream=$work/run-pass.tab
"$tool" -root "$base" -run 'compiler.idx_dot_aggret_subtail_run/c' \
-j 1 > "$runstream"
"$tool" validate -root "$base" \
-run 'compiler.idx_dot_aggret_subtail_run/c' "$runstream"
awk -F '\t' 'NR == 2 && $5 == "run" && $6 == "pass" && \
$7 == "exit" && $8 == "0" && $10 == "-" { ok=1 } \
END { exit !ok }' "$runstream" || fail "run fixture terminal row"
runexitstream=$work/run-exit-pass.tab
"$tool" -root "$base" -run 'compiler.runww_enum_run/c' -j 1 \
> "$runexitstream"
"$tool" validate -root "$base" -run 'compiler.runww_enum_run/c' \
"$runexitstream"
awk -F '\t' 'NR == 2 && $5 == "run" && $6 == "pass" && \
$7 == "exit" && $8 == "6" && $10 == "-" { ok=1 } \
END { exit !ok }' "$runexitstream" || fail "run-exit fixture terminal row"
# Compile fixtures stop at the frontend and publish one terminal row per stage.
compilepattern=compiler.r78_strict_package_main_accept
compilestream=$work/compile-pass.tab
"$tool" -root "$base" -run "$compilepattern" -j 1 > "$compilestream"
"$tool" validate -root "$base" -run "$compilepattern" "$compilestream"
awk -F '\t' 'NR == 1 && $7 == "2" { h=1 } \
$1 == "case" && $4 == "c" && $5 == "compile" && $6 == "pass" && \
$7 == "exit" && $8 == "0" && $10 == "-" { c=1 } \
$1 == "case" && $4 == "ww" && $5 == "compile" && $6 == "pass" && \
$7 == "exit" && $8 == "0" && $10 == "-" { w=1 } \
END { exit !(h && c && w) }' "$compilestream" \
|| fail "compile fixture terminal rows"
# A compile directive cannot turn a frontend rejection into a pass.
compilefailroot=$work/compile-failure
cp -R "$base" "$compilefailroot"
compilecase=$compilefailroot/test/wcc/data/r78_strict_package_main_accept/case.ww
printf '%s\n' '//ww:compile' 'package main;' 'fn main( {' > "$compilecase"
compilefailstream=$work/compile-failure.tab
if "$tool" -root "$compilefailroot" -run "$compilepattern/c" -j 1 \
> "$compilefailstream"; then
fail "compile rejection satisfied compile directive"
fi
"$tool" validate -root "$compilefailroot" -run "$compilepattern/c" \
"$compilefailstream"
awk -F '\t' 'NR == 2 && $5 == "compile" && $6 == "fail" && \
$7 == "exit" && $8 != "0" && $10 != "-" { ok=1 } \
END { exit !ok }' "$compilefailstream" \
|| fail "compile failure terminal row"
cleanup_capture "$compilefailstream"
# A build exit equal to the requested runtime exit is still a build failure.
buildroot=$work/build-failure
cp -R "$base" "$buildroot"
buildpattern=compiler.r816_one_elem
buildcase=$buildroot/test/wcc/data/r816_one_elem/case.ww
printf '%s\n' '//ww:run-exit 1' 'package main;' \
'fn main( {' > "$buildcase"
buildstream=$work/build-failure.tab
if "$tool" -root "$buildroot" \
-run "$buildpattern/c" -j 1 > "$buildstream"; then
fail "build failure satisfied runtime expectation"
fi
"$tool" validate -root "$buildroot" \
-run "$buildpattern/c" "$buildstream"
awk -F '\t' 'NR == 2 && $5 == "build" && $6 == "fail" && \
$7 == "exit" && $8 == "1" { ok=1 } END { exit !ok }' "$buildstream" \
|| fail "build failure terminal row"
cleanup_capture "$buildstream"
# A normal rejecting compile without the required stderr fragment fails.
diagroot=$work/diagnostic-mismatch
cp -R "$base" "$diagroot"
replace_directive "$diagroot/test/wcc/data/runww_dup_main_reject/case.ww" \
'//ww:error "fragment that is deliberately absent"'
diagstream=$work/diagnostic-mismatch.tab
if "$tool" -root "$diagroot" -run 'compiler.runww_dup_main_reject/c' \
-j 1 > "$diagstream"; then
fail "diagnostic mismatch passed"
fi
"$tool" validate -root "$diagroot" -run 'compiler.runww_dup_main_reject/c' \
"$diagstream"
awk -F '\t' 'NR == 2 && $5 == "compile" && $6 == "fail" && \
$7 == "exit" && $8 != "0" && $10 != "-" { ok=1 } \
END { exit !ok }' "$diagstream" || fail "diagnostic mismatch terminal row"
cleanup_capture "$diagstream"
# A labeled error directive selects the exact diagnostic for each stage.
stagediagpattern=compiler.r828_arrlit_ret_overlong
stagediagstream=$work/stage-diagnostic-pass.tab
"$tool" -root "$base" -run "$stagediagpattern" -j 1 > "$stagediagstream"
"$tool" validate -root "$base" -run "$stagediagpattern" "$stagediagstream"
awk -F '\t' '$1 == "case" && $4 == "c" && $5 == "compile" && \
$6 == "pass" && $7 == "exit" && $8 != "0" && $10 == "-" { c=1 } \
$1 == "case" && $4 == "ww" && $5 == "compile" && \
$6 == "pass" && $7 == "exit" && $8 != "0" && $10 == "-" { w=1 } \
END { exit !(c && w) }' "$stagediagstream" \
|| fail "stage-specific diagnostic terminal rows"
# Swapping the labeled fragments must fail both cells. This catches either
# fragment being shared across stages or treated as an unordered alternative.
stageswaproot=$work/stage-diagnostic-swapped
cp -R "$base" "$stageswaproot"
replace_directive \
"$stageswaproot/test/wcc/data/r828_arrlit_ret_overlong/case.ww" \
'//ww:error c "array literal has 3 elements but declared array holds 2" ww "return [3]int not assignable to [2]int"'
stageswapstream=$work/stage-diagnostic-swapped.tab
if "$tool" -root "$stageswaproot" -run "$stagediagpattern" -j 1 \
> "$stageswapstream"; then
fail "swapped stage-specific diagnostics passed"
fi
"$tool" validate -root "$stageswaproot" \
-run "$stagediagpattern" "$stageswapstream"
awk -F '\t' '$1 == "case" && $4 == "c" && $5 == "compile" && \
$6 == "fail" && $7 == "exit" && $8 != "0" && $10 != "-" { c=1 } \
$1 == "case" && $4 == "ww" && $5 == "compile" && \
$6 == "fail" && $7 == "exit" && $8 != "0" && $10 != "-" { w=1 } \
END { exit !(c && w) }' "$stageswapstream" \
|| fail "stage-specific diagnostics were shared across stages"
cleanup_capture "$stageswapstream"
# Signals remain signals and can never satisfy a numeric runtime expectation.
signalroot=$work/signal
cp -R "$base" "$signalroot"
signalcase=$signalroot/test/wcc/data/idx_dot_aggret_subtail_run/case.ww
printf '%s\n' '//ww:run' 'package main;' 'import os;' \
'export fn main() int = {' \
' os.kill(os.getpid(), os.SIGKILL);' ' return 0;' '};' > "$signalcase"
signalstream=$work/signal.tab
if "$tool" -root "$signalroot" -run 'compiler.idx_dot_aggret_subtail_run/c' \
-j 1 > "$signalstream"; then
fail "signaled fixture passed"
fi
"$tool" validate -root "$signalroot" \
-run 'compiler.idx_dot_aggret_subtail_run/c' "$signalstream"
awk -F '\t' 'NR == 2 && $5 == "run" && $6 == "fail" && \
$7 == "signal" && $8 == "9" { ok=1 } END { exit !ok }' "$signalstream" \
|| fail "signal terminal row"
cleanup_capture "$signalstream"
# One total deadline covers build and run; the busy program is terminated.
timeoutroot=$work/timeout
cp -R "$base" "$timeoutroot"
timeoutcase=$timeoutroot/test/wcc/data/idx_dot_aggret_subtail_run/case.ww
printf '%s\n' '//ww:run' 'package main;' 'export fn main() int = {' \
' for (true) { };' ' return 0;' '};' > "$timeoutcase"
timeoutstream=$work/timeout.tab
if "$tool" -root "$timeoutroot" -run 'compiler.idx_dot_aggret_subtail_run/c' \
-j 1 -timeout-ms 250 > "$timeoutstream"; then
fail "timed out fixture passed"
fi
"$tool" validate -root "$timeoutroot" \
-run 'compiler.idx_dot_aggret_subtail_run/c' "$timeoutstream"
awk -F '\t' 'NR == 2 && $6 == "fail" && $7 == "timeout" && \
$8 == "0" && $9 >= 200000000 { ok=1 } END { exit !ok }' "$timeoutstream" \
|| fail "timeout terminal row"
cleanup_capture "$timeoutstream"
# The real coordinator consumes SIGTERM, owns active cleanup, and still emits
# exactly one terminal record for the active cell and the queued cell.
interruptstream=$work/interrupted.tab
"$tool" -root "$timeoutroot" -run 'compiler.idx_dot_aggret_subtail_run' \
-j 1 -timeout-ms 10000 > "$interruptstream" &
coordinator=$!
program=
tries=0
while [ "$tries" -lt 300 ]; do
if [ -r "/proc/$coordinator/task/$coordinator/children" ]; then
for child in $(sed -n '1p' "/proc/$coordinator/task/$coordinator/children"); do
exe=$(readlink "/proc/$child/exe" 2> /dev/null || :)
case $exe in /tmp/*/program) program=$child ;; esac
done
fi
[ -z "$program" ] || break
tries=$((tries + 1))
sleep 0.01
done
[ -n "$program" ] || fail "coordinator runtime child did not start"
kill -TERM "$coordinator"
if wait "$coordinator"; then
fail "interrupted coordinator returned success"
fi
coordinator=
[ ! -e "/proc/$program" ] || fail "interrupted leader was not reaped"
"$tool" validate -root "$timeoutroot" \
-run 'compiler.idx_dot_aggret_subtail_run' "$interruptstream"
awk -F '\t' 'NR == 1 && $7 == "2" { h=1 } \
NR == 2 && $2 == "1" && $5 == "run" && $6 == "error" && \
$7 == "signal" && $8 == "15" { a=1 } \
NR == 3 && $2 == "2" && $5 == "build" && $6 == "error" && \
$7 == "setup" && $8 == "-4" && $9 == "0" { q=1 } \
NR == 4 && $1 == "end" && $2 == "2" && $3 == "2" { e=1 } \
END { exit !(h && a && q && e) }' "$interruptstream" \
|| fail "interrupted coordinator terminal accounting"
cleanup_capture "$interruptstream"
# Directive parsing and corpus pinning happen before filtering or execution.
malformed=$work/malformed
cp -R "$base" "$malformed"
replace_directive "$malformed/test/wcc/data/runww_dup_main_reject/case.ww" \
'//ww:run-exit 01'
if "$tool" -root "$malformed" -list > "$work/malformed.out" \
2> "$work/malformed.err"; then
fail "malformed directive accepted"
fi
[ ! -s "$work/malformed.out" ] || fail "malformed directive published output"
stagemalformed=$work/stage-malformed
cp -R "$base" "$stagemalformed"
stagemalformedcase=$stagemalformed/test/wcc/data/r828_arrlit_ret_overlong/case.ww
stagemalformedn=0
for directive in \
'//ww:error c "" ww "WW fragment"' \
'//ww:error c "C fragment" ww ""' \
'//ww:error c "C fragment" c "WW fragment"' \
'//ww:error c "C fragment" ww "WW fragment" trailing'
do
stagemalformedn=$((stagemalformedn + 1))
replace_directive "$stagemalformedcase" "$directive"
out=$work/stage-malformed-$stagemalformedn.out
err=$work/stage-malformed-$stagemalformedn.err
if "$tool" -root "$stagemalformed" -list > "$out" 2> "$err"; then
fail "malformed stage-specific directive accepted"
fi
[ ! -s "$out" ] \
|| fail "malformed stage-specific directive published output"
grep -F 'malformed stage-specific error directive' "$err" > /dev/null \
|| fail "wrong malformed stage-specific diagnostic"
done
countdrift=$work/count-drift
cp -R "$base" "$countdrift"
find "$countdrift/test/wcc/data/a2s_mismatch_callarg" -depth -delete
if "$tool" -root "$countdrift" -list > "$work/count.out" \
2> "$work/count.err"; then
fail "corpus count drift accepted"
fi
[ ! -s "$work/count.out" ] || fail "count drift published output"
hashdrift=$work/hash-drift
cp -R "$base" "$hashdrift"
mv "$hashdrift/test/wcc/data/a2s_mismatch_callarg" \
"$hashdrift/test/wcc/data/a2s_mismatch_callarg_changed"
if "$tool" -root "$hashdrift" -list > "$work/hash.out" \
2> "$work/hash.err"; then
fail "corpus hash drift accepted"
fi
[ ! -s "$work/hash.out" ] || fail "hash drift published output"
# A terminal-stream sink failure must force a nonzero producer status.
if "$tool" -root "$base" -run 'compiler.runww_dup_main_reject/c' -j 1 \
> /dev/full 2> "$work/full.err"; then
fail "result publication failure returned success"
fi
publication=$(sed -n 's/^wwfixture: .*; capture: \(\/tmp\/[0-9a-f][0-9a-f]*\)$/\1/p' \
"$work/full.err")
[ -n "$publication" ] || fail "publication failure capture was not retained"
[ -f "$publication/publication.txt" ] \
|| fail "publication failure diagnostic was not retained"
cleanup_run "$publication"
# Strict decoder: start with an independently produced, valid two-cell stream.
good=$work/good.tab
pattern=compiler.runww_dup_main_reject
"$tool" -root "$base" -run "$pattern" -j 1 > "$good"
"$tool" validate -root "$base" -run "$pattern" "$good"
bad=$work/bad-no-header.tab
sed '1d' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-version.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 1 { $2=2 } { print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-corpus.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 1 { $4=105 } { print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-missing-ordinal.tab
sed '2d' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-duplicate-ordinal.tab
awk 'NR == 2 { print; print; next } { print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-identity.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 2 { $3="compiler.not_selected" } \
{ print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-stage.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 2 { $4="ww" } { print }' \
"$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-field-count.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 2 { print $0, "extra"; next } \
{ print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-enum.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 2 { $6="bogus" } { print }' \
"$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-contradiction.tab
awk -F '\t' 'BEGIN { OFS="\t" } NR == 2 { $8=0 } { print }' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-incomplete-line.tab
bytes=$(wc -c < "$good")
dd if="$good" of="$bad" bs=1 count=$((bytes - 1)) 2> /dev/null
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-missing-footer.tab
sed '$d' "$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-footer.tab
awk -F '\t' 'BEGIN { OFS="\t" } $1 == "end" { $3=1 } { print }' \
"$good" > "$bad"
expect_invalid "$base" "$pattern" "$bad"
bad=$work/bad-trailing.tab
cp "$good" "$bad"
printf '%s\n' 'case 3 compiler.trailing c compile pass exit 1 0 -' \
>> "$bad"
expect_invalid "$base" "$pattern" "$bad"
echo "wwfixture integration: PASS"