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.
443 lines
16 KiB
Bash
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"
|