The smoke-test suite for dot — tests/test.sh. 26 tests over 7 sections: build, help, preprocessor, demo runner, errors, runtime, printer. Each test is a tiny usage snippet you can lift verbatim.
Run yourself:
cd dot
./tests/test.sh
# All 26 tests passed.
Sanity gate: if the build step itself fails, nothing else is meaningful.
out=$(./build.sh 2>&1)
contains "build.sh runs" "built dot" "$out"
[ -x "$DOT" ] && ok "dot is executable" || bad ...
Asserts: build.sh emits the line built dot ...; the resulting dot file has the executable bit set. Catches missing chmod +x at the end of the build, or the heredoc termination going off the rails.
The dispatcher is a one-shot bash case. These tests verify each branch is reachable and prints something sensible.
contains "no args -> usage" "Usage:" "$($DOT 2>&1)"
contains "--help has Usage" "Usage:" "$($DOT --help)"
contains "--help lists --demo" "--demo" "$($DOT --help)"
contains "--demos lists hello" "hello" "$($DOT --demos)"
Asserts: bare dot shows usage; --help shows usage and lists the --demo subcommand; --demos finds demos/hello/ on disk and prints it. The last test exercises the filesystem-driven demo discovery — if you add a demos/foo/ directory, that command will list it without any code change.
The heart of dot. Each eq compares input source against expected rewritten output. dot -c FILE prints the rewrite without running gawk.
eq "field rewrite" 'HEAP[it]["n"]++' "$(echo '.it.n++' | $DOT -c /dev/stdin)"
eq "object rewrite" 'HEAP[d]' "$(echo '.d' | $DOT -c /dev/stdin)"
eq "nested rewrite" 'HEAP[d]["rows"][r][i]' "$(echo '.d.rows[r][i]' | $DOT -c /dev/stdin)"
eq "explicit dot ok" '"fred" "." "csv"' "$(echo '"fred" "." "csv"' | $DOT -c /dev/stdin)"
eq "no dots = passthrough" 'NAME[i] = $i' "$(echo 'NAME[i] = $i' | $DOT -c /dev/stdin)"
Asserts:
.it.n → HEAP[it]["n"] (value-char before dot → struct field)..d → HEAP[d] (no value-char before dot → object reference)."fred" "." "csv" survives unchanged. Quote isn't a value-char, so the dot stays a literal.--demo handles all three input modesThe --demo subcommand has three input-resolution rules. Each gets a test.
eq "--demo hello (sample.txt)" "n=5 mean=30.000" "$($DOT --demo hello)"
eq "--demo hello (- means stdin)" "n=3 mean=20.000" "$(printf '10\n20\n30\n' | $DOT --demo hello -)"
eq "--demo hello (DATA arg)" "n=2 mean=15.000" "$($DOT --demo hello <(printf '10\n20\n'))"
Asserts:
demos/hello/sample.txt (5 numbers averaging 30).- → read stdin instead. Sample is ignored.If the sample-fallback ever silently consumes piped stdin (a real bug we caught earlier), the second test fails immediately.
out=$($DOT --demo nonesuch 2>&1 || true)
contains "missing demo errors" "no such demo" "$out"
out=$($DOT -c 2>&1 || true)
contains "-c needs file" "need FILE.awk" "$out"
Asserts: requesting a non-existent demo prints no such demo on stderr (not a cryptic gawk error); calling -c with no filename prints need FILE.awk. Both exit non-zero.
new, arr, zap, .field sugarEach test inlines a tiny awk program (via the run_inline helper) that exercises one runtime function.
eq "new + .field" "7 1.5" "$(run_inline '
BEGIN { N = new("plain"); .N.n = 7; .N.mu = 1.5
printf "%d %.1f\n", .N.n, .N.mu }')"
eq "zap drops slot" "gone" "$(run_inline '
BEGIN { N = new("x"); .N.n = 1; zap(N)
printf "%s\n", ((N in HEAP) ? "kept" : "gone") }')"
eq "arr + nested key" "2" "$(run_inline '
BEGIN { N = new("y"); arr(.N.has); .N.has["a"] = 2
printf "%d\n", .N.has["a"] }')"
Asserts:
new("plain") returns an id; .N.n and .N.mu are real HEAP slots that round-trip values.zap(N), the id is no longer present in HEAP. (Doesn't recycle the id — just clears the slot.)arr(.N.has) makes .N.has a real array, and you can write keys into it. Without arr, gawk would treat the first assignment as scalar and refuse the subscript.o + _oo formattingOne o(x) call per case, capturing the raw printf output.
eq "o(int)" "5" "$(run_inline 'BEGIN{o(5); print ""}')"
eq "o(5.0) collapses" "5" "$(run_inline 'BEGIN{o(5.0); print ""}')"
eq "o(5.123)" "5.123" "$(run_inline 'BEGIN{o(5.123); print ""}')"
eq "o(string)" "hi" "$(run_inline 'BEGIN{o("hi"); print ""}')"
eq "o(list)" "[1, 2, 3]" "$(run_inline 'BEGIN{a[1]=1;a[2]=2;a[3]=3; o(a); print ""}')"
eq "o(dict sorted)" "{a: 1, b: 2}" "$(run_inline 'BEGIN{b["b"]=2; b["a"]=1; o(b); print ""}')"
eq "o(list numeric sort)" "[3, 2, 1]" "$(run_inline 'BEGIN{a[10]=1;a[2]=2;a[1]=3; o(a); print ""}')"
Asserts each format rule:
5 → 5; 5.0 → 5 (whole-valued floats print as %d).5.123 → 5.123 (other numbers as %G)."hi" → hi (strings as %s).1 in a → [..] list; otherwise {..} dict.1, 2, 10) sorts numerically — not lexically (which would put 10 between 1 and 2). The values at those keys (3, 2, 1) appear in that order.-- subcommand is reachable.--demo are independent.new, arr, zap) and .field sugar all behave per spec.If you change anything in lib/, run ./tests/test.sh. Two minutes round-trip, end to end. The tests double as the smallest possible usage examples for every public function.