aboutsummaryrefslogtreecommitdiff
path: root/tests/test.sh
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test.sh')
-rwxr-xr-xtests/test.sh577
1 files changed, 577 insertions, 0 deletions
diff --git a/tests/test.sh b/tests/test.sh
new file mode 100755
index 0000000..27d60f7
--- /dev/null
+++ b/tests/test.sh
@@ -0,0 +1,577 @@
+#!/bin/bash
+# full check: bootstraps the compiler from zc0, verifies the fixpoint, then runs
+# the examples and tests on the portable interpreter (host), the microcore, the
+# arm64 jit under qemu-user, and the llvm backend. needs gcc. the jit half
+# needs aarch64-linux-gnu-gcc and qemu-aarch64 (linux-user, not qemu-system).
+# the llvm half needs llvm-config and libLLVM. src/baremetal/run.sh boots the
+# microcore on qemu-system-riscv64 and qemu-system-aarch64 when those cross
+# compilers and system emulators are installed. each of these is skipped
+# when its tools are missing.
+#
+# -j N runs N sections at a time, where N is how many cores to use. the
+# sections are independent and each gets its own scratch directory, so they
+# can overlap; the output still reads in the same order either way. -j 1 is
+# the default, and is what make test does unless JOBS says otherwise.
+#
+# -q (make check) is the minimal suite for iterating: the bootstrap and the
+# fixpoint, the interpreter section and the language server's own checks.
+# it skips the other backends, the node protocol and corpus tests, and the
+# js and make sections. run the whole thing (no -q) when the work is done.
+cd "$(dirname "$0")/.."; T=$(mktemp -d); trap 'rm -rf $T' EXIT
+ok=0; bad=0
+chk(){ if [ "$1" = "$2" ]; then echo "ok $3"; ok=$((ok+1)); else echo "FAIL $3: got [$1]"; bad=$((bad+1)); fi; }
+src(){ cat src/runtime/rt.zc src/compiler/compiler.zc; }
+# -j N runs N sections at a time: j is how many cores to use. each section
+# writes its own log, so the output still reads in the order it always did.
+JOBS=1; QUICK=0; SELECT=""
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -j) JOBS=$2; shift 2;;
+ -j*) JOBS=${1#-j}; shift;;
+ -q) QUICK=1; shift;;
+ --section) SELECT=$2; shift 2;;
+ *) echo "usage: $0 [-q] [-j N] [--section NAME]"; exit 2;;
+ esac
+done
+case "$JOBS" in ''|*[!0-9]*|0) JOBS=1;; esac
+# each section gets its own scratch directory, so none of them can tread on
+# another's files when they overlap
+SECS="interp micro small runonly bare jit llvm lsp js make learn"
+[ $QUICK = 1 ] && SECS="interp lsp learn"
+if [ -n "$SELECT" ]; then
+ case " $SECS " in *" $SELECT "*) SECS=$SELECT;; *) echo "unknown test section: $SELECT" >&2; exit 2;; esac
+fi
+ROOT=$T
+
+# built here rather than inside a section: interp runs the program list on
+# it, and the run-only and bare-metal sections read it as the reference.
+gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy || exit 1
+gcc -O2 -w -fwrapv src/tools/genmap.c -Isrc/core -o $T/genmap || exit 1
+# the c helper for tests/programs/structval.zc: structs by value, loaded with cload
+mkdir -p build && gcc -shared -fPIC -w tests/data/sbv.c -o build/sbv.so || exit 1
+
+selfc(){ (cat src/tools/build.zc; printf '`'; src) | $1 > $2; }
+
+# bootstrap: zc0 (c) -> g0, g0 compiles compiler.zc -> g1, g1 compiles it -> g2
+gcc -O1 -w -fwrapv src/tools/zc0.c -Isrc/core -o $T/zc0 && gcc -O1 -w -fwrapv src/tools/mkgenome.c -Isrc/core -o $T/mkg || exit 1
+mkdir $T/s0 $T/s1
+cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s0/; cp src/core/core.c src/core/io_linux.c src/core/memmap.h src/core/fops.h src/core/bops.h src/core/stack.h $T/s1/
+src | $T/zc0 > $T/s0/genome.h || exit 1
+gcc -O2 -w -fwrapv -DJTYPE=int $T/s0/core.c $T/s0/io_linux.c -o $T/zy0
+selfc $T/zy0 $T/g1.bin; $T/mkg < $T/g1.bin > $T/s1/genome.h
+gcc -O2 -w -fwrapv -DJTYPE=int $T/s1/core.c $T/s1/io_linux.c -o $T/zy1
+selfc $T/zy1 $T/g2.bin
+cmp -s $T/g1.bin $T/g2.bin && chk 1 1 "fixpoint: the compiler compiles itself to the same $(($(wc -c < $T/g1.bin) / 8)) words" || chk 0 1 "fixpoint"
+cmp -s $T/s1/genome.h src/core/genome.h && chk 1 1 "genome.h is up to date" || chk 0 1 "genome.h is stale (cp the bootstrap output over it)"
+chk "$($T/zc0 < tests/programs/zc0typed.zc | md5sum)" "$($T/zc0 < tests/programs/zc0plain.zc | md5sum)" "zc0 reads types and throws them away: typed and plain sources give one image"
+
+for n in $SECS; do
+ mkdir -p $ROOT/$n
+ cp $ROOT/g1.bin $ROOT/$n/ # the bootstrap's image, read by micro, jit and llvm
+ cp $ROOT/zy $ROOT/$n/zy # the interpreter, built once above
+ cp $ROOT/genmap $ROOT/$n/genmap
+done
+
+run(){ # $1 = vm command, $2 = label
+ local vm="$1" l="$2"
+ chk "$($vm < examples/hello.zc)" "hello, world!" "$l hello"
+ chk "$($vm < examples/fact.zc | tr '\n' '/')" "1101110101111100000000/3628800/" "$l factorial"
+ chk "$( (cat examples/echo.zc; printf '`abc\n') | $vm)" "abc" "$l echo after the backtick"
+ chk "$( (cat tests/programs/backtick.zc; printf '`xyz') | $vm)" "3 3 96 39 xyz" "$l a backtick in a comment, string or char literal does not end the source"
+ chk "$($vm < examples/selfpatch.zc)" "ABC" "$l self-patching code"
+ chk "$($vm < examples/lists.zc | tr '\n' '/')" "[0 1 4 9 16 25]/live: 7/after the block: 0/" "$l ownership frees everything"
+ chk "$( (cat examples/repl.zc; printf '`fn sq(x) { return x * x; }`put_n(sq(7)); put_c(10);`put_s("hi\\n");') | $vm | tr '\n' '/')" "49/hi/" "$l repl on the resident compiler"
+ chk "$($vm < examples/typed.zc | tr '\n' '/')" "last: 4/point is 2 words: x int, y int/vec<point*> is 3 words: len int, cap int, items point**/push is bool(vec<T>*, T.d)/the heap says v is a vec<point*>, and v.items[0] is a point/" "$l typed example"
+ chk "$($vm < examples/wiring.zc)" "16 1234 25 4/9" "$l enhancement example"
+ chk "$($vm < tests/programs/allocfast.zc)" "0 0 499500 0" "$l constant-size allocation fast path"
+ chk "$($vm < tests/programs/basics.zc | tr '\n' '/')" "285 610 42 512/hey 65 -3 -2 1024 -8/12456/757/9 100-101/5/ok/" "$l language basics"
+ chk "$($vm < tests/programs/umd.zc)" "100 6 0 15 99 5 5" "$l u m d lifetimes"
+ chk "$($vm < tests/programs/names.zc)" "12 1 8" "$l u, m and d are ordinary names"
+ chk "$($vm < tests/programs/gc.zc)" "1000 1 900 1 199900" "$l mark-sweep collector"
+ chk "$($vm < tests/programs/meta.zc | tr '\n' '/')" "1024 19/unless ok/rrr/15 42/7/42/7/144/" "$l parse words, quote, eval, load, operator tables"
+ chk "$($vm < tests/programs/misc.zc | tr '\n' '/')" "45/54321/ok/" "$l compile-time eval, patched mnemonics, unchecked"
+ chk "$($vm < tests/programs/types.zc | tr '\n' '/')" "12 5 2 15 56 42 7 14 3/rect rect* list<int>* list<T>*.u(int) T(list<T>*) fn(word.d)/a:point@0 b:point@2 tag:int@4 /point 1 1/" "$l types, generics, reflection"
+ chk "$($vm < tests/programs/typeok.zc)" "14" "$l a program the typechecker accepts"
+ chk "$($vm < tests/programs/typeoff.zc)" "6" "$l the typechecker is off by default"
+ chk "$($vm < tests/programs/enhance.zc)" "100 0 343 3,2 5 -2 0" "$l virtual enhancements"
+ chk "$($vm < tests/programs/joins.zc)" "0 0 5 80 8 3 9 0" "$l joins free what one path gave away; switch, ?:, no prototypes"
+ chk "$($vm < tests/programs/buddy.zc)" "131072 262144 1 0" "$l buddy allocator merges freed blocks"
+ chk "$($vm < tests/programs/growheap.zc)" "144 1 7 1" "$l the heap grows past its initial size and a bigger block fits after the frees"
+ chk "$($vm < tests/programs/sized.zc | md5sum)" "$(md5sum < tests/programs/sized.expected)" "$l sized integers wrap, and arrays and pointers of them are packed"
+ chk "$($vm < tests/programs/bitops.zc | md5sum)" "$(md5sum < tests/programs/bitops.expected)" "$l ctz, popcnt, bswap, rotates, mulh and float32 conversion micro-ops"
+ chk "$($vm < tests/programs/f32.zc | md5sum)" "$(md5sum < tests/programs/f32.expected)" "$l f32: single precision scalars and packed arrays"
+ chk "$($vm < tests/programs/f32o3.zc)" "10718.750000 1.600000 0" "$l f32 inside an o3 region"
+ chk "$($vm < tests/programs/sizedvec.zc | md5sum)" "$(md5sum < tests/programs/sizedvec.expected)" "$l growable arrays of the sized types and f32"
+ chk "$($vm < tests/programs/packedfields.zc | md5sum)" "$(md5sum < tests/programs/packedfields.expected)" "$l struct fields of the sized types pack into words"
+ chk "$($vm < tests/programs/generics.zc | md5sum)" "$(md5sum < tests/programs/generics.expected)" "$l generic functions are compiled per type arguments"
+ chk "$($vm < tests/programs/genmeth.zc | md5sum)" "$(md5sum < tests/programs/genmeth.expected)" "$l methods of generic structs are compiled per type arguments"
+ chk "$($vm < tests/programs/static.zc)" "14 5 9" "$l static functions of a struct are called as Struct.name(args)"
+ chk "$($vm < tests/programs/methodown.zc)" "7 9" "$l struct methods and functions return owned objects (.u)"
+ chk "$($vm < tests/programs/typedmethods.zc)" "14 5 13" "$l struct methods declare a return type and typed parameters"
+ chk "$($vm < tests/programs/enumscope.zc)" "0 5 6 blue" "$l enum members are reached as Enum.MEMBER, also in a case"
+ chk "$($vm < tests/programs/enumtype.zc)" "6 5 1" "$l typed enums: members take their enum, an enum becomes an int"
+ chk "$($vm < tests/programs/tagenum.zc)" "27 20 0 0 1 2" "$l tagged enums: members carry fields, constructors build them, a case binds the fields"
+ chk "$($vm < tests/programs/tagenum_warn.zc)" "$(printf 'zc: line 5: warning: this switch doesn'"'"'t cover rect\n0')" "$l a switch on a tagged enum that misses a member warns, on the switch's line"
+ chk "$($vm < tests/programs/tagenum_two.zc)" "3 8 12" "$l two tagged enums with the same member name, and a case in an o3 loop"
+ chk "$($vm < tests/programs/o3push.zc | md5sum)" "$(md5sum < tests/programs/o3push.expected)" "$l push and pop of a growable array are inline in o3"
+ chk "$($vm < tests/programs/o3copy.zc)" "520507 1005" "$l o3 copies of a loop variable read the source instead"
+ chk "$($vm < tests/programs/borrowmove.zc)" "6" "$l storing into a borrowed object moves it"
+ chk "$($vm < tests/programs/alias.zc)" "11 22 4 8" "$l aliases of a live owner: arithmetic, field, index"
+ chk "$($vm < tests/programs/quarantine.zc)" "0 1234" "$l quarantine: stale alias reads intact data, second free is a no-op"
+ chk "$($vm < tests/programs/recycle.zc)" "1 0 1" "$l recycle returns a dropped block to its size class"
+ chk "$($vm < tests/programs/roots.zc)" "1 0" "$l the collector marks the live stack, and scrub clears the rest"
+ chk "$($vm < tests/programs/arena.zc)" "4 7 0 1 1" "$l bump arena allocates, rewinds, and refuses a bump past the end"
+ chk "$($vm < tests/programs/csyn.zc)" "13 0 5 6 0" "$l do/while, enum, and a C cast"
+ chk "$($vm < tests/programs/use_std.zc)" "42 -7 1" "$l include and the string helpers"
+ chk "$($vm < tests/programs/stdbuf.zc)" "4 1 266 8" "$l stdlib arena and packed byte buffer"
+ chk "$($vm < tests/programs/stdmore.zc)" "$(printf '5 1 0 0 7 copy me 12 -1 0\n9 mixed 123 9 MIXED 123\n11 [padded line]\n42 3 9 10 12 81 9 10 -1 0')" "$l stdlib text and num helpers"
+ chk "$(ZC_FILE=tests/data/hi.txt $vm < examples/cli.zc)" "6 h" "$l reading a file named by the environment"
+ chk "$(ZC_ARG_TEST=ok $vm < tests/programs/args.zc)" "1 <eval> <eval> 1 1" "$l argv and envp from the host, argv[0] is <eval> on stdin"
+ chk "$(ZC_ARG_TEST=ok $vm < tests/programs/capture.zc)" "0
+1
+15
+hello world!42
+1
+1
+0" "$l capture.start() collects output and end() hands back the string"
+ chk "$($vm < tests/programs/sys.zc)" "2 zy time" "$l syscalls: write, read, time"
+ chk "$($vm < tests/programs/memleak.zc)" $'leak: line 5 3 words\n1' "$l memdebug names a live allocation"
+ chk "$($vm < tests/programs/uaf.zc)" $'zc: line 7: warning: use after free: this storage\'s lifetime ended on line 6\nzy: use after free from line 4' "$l memdebug: use after free"
+ chk "$($vm < tests/programs/uaf_in.zc)" "zy: use after free from line 4" "$l memdebug: use after free through an interior pointer"
+ chk "$($vm < tests/programs/doublefree.zc)" $'zy: double free from line 4\nok' "$l memdebug: double free is reported and does nothing"
+ chk "$($vm < tests/programs/scrub.zc)" $'zc: line 6: warning: use after free: this storage\'s lifetime ended on line 5\n57005' "$l memdebug scrub poisons a freed payload"
+ chk "$($vm < tests/programs/interior.zc)" "0" "$l an interior pointer keeps a quarantined block"
+ chk "$($vm < tests/programs/wide.zc)" "1099511627776 1 1 1 -1" "$l 64-bit shifts and equality"
+ chk "$($vm < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "$l floating point"
+ chk "$($vm < tests/programs/ftruth.zc)" "00111000110107" "$l negative zero is false"
+ chk "$($vm < tests/programs/ftype.zc)" "3.500000 10.500000 3.500000 4.250000 5.250000 4.250000 6.500000 2 4 4.250000 8.500000 1 4" "$l float type through calls, globals, fields"
+ chk "$($vm < tests/programs/fo3.zc)" "5997 5997 4.000000 nan 0 3.250000 1 0 0" "$l o3 lifts float ops"
+ chk "$($vm < tests/programs/ftext.zc)" "1e+308 1e-10 -0 inf -inf nan 0.1 1.50 1.250e+0 100000000000000000000.000000 1e+40 1111111111" "$l float text: shortest, fixed, scientific, 17 digits"
+ chk "$($vm < tests/programs/bytes.zc)" "17 34 43981 16909060 1 205 171 -3674937295934316015 1108152156331 0 0 30768
+ABC" "$l byte addressing"
+ chk "$($vm < tests/programs/o3bytes.zc)" "190 4660 7 2055 16909060 0 0
+ABC" "$l o3 lifts byte loads and stores"
+ chk "$($vm < tests/programs/o3zero.zc)" "1" "$l untyped new inside o3 zeroes its words"
+ chk "$($vm < tests/programs/strings.zc)" "hi! 3 3 104 i! 1 1 1 -42 42 -42 11 1 -9223372036854775808 0 1 3 10 1 1 1 W ab" "$l byte strings"
+ chk "$($vm < tests/programs/share.zc)" "1 2 1 4 1 1 1 1 42 1 1 1 6 0" "$l shared cons cells"
+ chk "$($vm < tests/programs/share_dbg.zc)" "7 1" "$l memdebug accepts a shared cell"
+ chk "$($vm < examples/strings.zc)" "hello, zc
+9 42" "$l byte string example"
+ chk "$(ZC_BSTR=zybyte $vm < tests/programs/bstr_io.zc)" "byte-io 7 7 1 6 122 101" "$l byte-string file and getenv"
+ chk "$($vm < tests/programs/manyerrors.zc | tr '\n' '/')" "zc: line 3: expected an expression, found ';'/zc: line 4: x: m object is never used/zc: line 5: type error: initializer: expected char*, got int/zc: line 6: expected ';', found '}'/zc: 4 errors/" "$l several errors in one run"
+ chk "$($vm < tests/programs/overflow_return.zc 2>&1)" "zy: return stack overflow" "$l return stack overflow is caught"
+ chk "$($vm < tests/programs/overflow_data.zc 2>&1)" "zy: data stack overflow" "$l data stack overflow is caught"
+ chk "$($vm < tests/programs/overflow_frame.zc 2>&1)" "zc: out of heap memory" "$l frame growth stops at a configured memory limit"
+ chk "$($vm < examples/mandel.zc 2>&1 | md5sum | cut -c1-32)" "f41aecbf2c844168050413cfa832db0e" "$l example: mandelbrot with a fixed-point enhancement"
+ chk "$($vm < examples/bf.zc 2>&1 | md5sum | cut -c1-32)" "223570983752b17f7ece32e93d5e19e4" "$l example: brainfuck compiled at runtime"
+ chk "$($vm < examples/dump.zc 2>&1 | md5sum | cut -c1-32)" "757774b00f6b3cd0bfbcba66448a8619" "$l example: struct printer through reflection"
+ chk "$($vm < examples/tree.zc 2>&1 | md5sum | cut -c1-32)" "b86a2e4e9e31acc294a4ddf86be863f2" "$l example: owning tree frees itself"
+ chk "$($vm < examples/lang.zc 2>&1 | md5sum | cut -c1-32)" "6b234dc8a0a1aa0fb02eb55a6ff7ccc0" "$l example: pipe operator, unless, assert, times"
+ chk "$($vm < examples/staged.zc 2>&1 | md5sum | cut -c1-32)" "6f790a125c48b0cc7a897cee73ee5627" "$l example: staged code compiled while running"
+ chk "$($vm < examples/quine.zc 2>&1 | md5sum | cut -c1-32)" "$(md5sum < examples/quine.zc | cut -c1-32)" "$l example: quine"
+ chk "$($vm < examples/primes.zc 2>&1 | md5sum | cut -c1-32)" "814faffc70dad0ae520c0671c33e73d4" "$l example: o3 prime sieve"
+ chk "$($vm < examples/life.zc 2>&1 | md5sum | cut -c1-32)" "aefb036b6f9ad1dadfc3e6bb47a6885d" "$l example: game of life"
+ chk "$($vm < tests/programs/arrays.zc | tr '\n' '/')" "100 128 9801 9801 99 3 4 12 19 8 19 28/0/" "$l growable and fixed arrays"
+ chk "$($vm < tests/programs/o3.zc)" "3142 42 43 9 57 98781 520625 10192 -18782 1152" "$l o3 regions: loops, calls, recursion, switch, arrays, more vars than registers"
+ chk "$($vm < tests/programs/o3scopes.zc)" "116" "$l o3 registers shared by scopes, cached fixed arrays"
+ chk "$($vm < tests/programs/o3fat.zc)" "node node 3 box node [0]int" "$l o3 data gets fattened on the way out"
+ chk "$($vm < tests/programs/o3call.zc)" "0 0 4 23 55 node" "$l o3 calls o3 directly, and new stamps the header"
+ chk "$($vm < tests/programs/o3direct.zc | md5sum)" "$(md5sum < tests/programs/o3direct.expected)" "$l o3 calls: arguments, results, recursion, floats, pointers, the ways out of a fast call"
+ chk "$($vm < tests/programs/o3licm.zc)" "3184200 7950 315 50 260 -56" "$l o3 hoists loop invariants and shares values between blocks"
+ chk "$($vm < tests/programs/o3deep.zc)" "30000" "$l o3 recursion thirty thousand deep"
+ chk "$($vm < tests/programs/o3deep_over.zc 2>&1)" "100000" "$l o3 recursion grows past the original stack"
+ chk "$($vm < tests/programs/o3_bounds.zc 2>&1)" "zy: index 7 out of range (length 5)" "$l o3 bounds check"
+ chk "$($vm < tests/programs/bounds.zc)" "55 55 8" "$l a proved +1 loop skips the bounds check"
+ chk "$($vm < tests/programs/bounds_lo.zc 2>&1)" "zy: index -1 out of range (length 4)" "$l a negative loop index is still checked"
+ chk "$($vm < tests/programs/bounds_hi.zc 2>&1)" "zy: index 4 out of range (length 4)" "$l an index that is not the induction variable is still checked"
+ local r; r=$(bash tests/runerr.sh "$vm" tests/errors | tail -1)
+ chk "$r" "rejected: $(ls tests/errors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l analyzer rejects every bad program"
+ r=$(bash tests/runerr.sh "$vm" tests/typeerrors | tail -1)
+ chk "$r" "rejected: $(ls tests/typeerrors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l typechecker rejects every bad program"
+ r=$(bash tests/runerr.sh "$vm" tests/langerrors | tail -1)
+ chk "$r" "rejected: $(ls tests/langerrors/*.zc | wc -l | tr -d ' ') passed, 0 failed" "$l o3 and array misuse is rejected"
+}
+s_interp() {
+ T=$ROOT/interp
+ gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy
+ run $T/zy "interp"
+ chk "$(ZC_ARG_TEST=ok $T/zy tests/programs/args.zc alpha beta < tests/programs/args.zc)" "4 <eval> beta 1 1" "interp the command line reaches argv, after <eval>"
+ chk "$($T/zy < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "interp native calls and callbacks (extern, cfunc)"
+ chk "$($T/zy < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "interp native calls from o3 code, more arguments than registers"
+ chk "$($T/zy < tests/programs/cabi_sized.zc | md5sum)" "$(md5sum < tests/programs/cabi_sized.expected)" "interp native calls with f32 and the sized integers"
+ chk "$($T/zy < tests/programs/structval.zc)" "34 3520 18 325 810 340" "interp structs by value to a c function (registers, sse, stack)"
+ chk "$($T/zy < tests/programs/structret.zc)" "3 4 3.500000 4.000000 24 8 1.000000 3.500000" "interp structs returned from a c function (registers, sse, memory)"
+ chk "$(ZYTRACE=1 $T/zy < tests/programs/o3.zc 2>&1 | grep -c 'micro-ops$')" "8" "every o3 region lifts"
+ chk "$($T/zy < tests/programs/infer_return.zc)" "3" "an owned return with no .u is inferred from the body"
+ chk "$($T/zy < tests/programs/discard_m.zc)" "7" "m* discards an unnamed u result"
+ chk "$($T/zy < tests/programs/closure.zc | tr '\n' ' ')" "31 35 42 2 7 0 109 30 7 16 33 20 2 " "closures: calls through values, captures, nesting, returns"
+ chk "$($T/zy < tests/programs/decl_undefined.zc 2>&1 | head -1)" "zc: line 1: helper is used but never defined" "a declared function nothing defines fails a build"
+ if [ "$(uname -s)" = Linux ]; then chk "$($T/zy < tests/programs/rawsys.zc)" "-38" "Host.raw: an unknown syscall number gives -ENOSYS"; else chk "$($T/zy < tests/programs/rawsys.zc)" "-1" "Host.raw: unavailable on this host"; fi
+ if [ "$(uname -m)" = x86_64 ]; then chk "$($T/zy < tests/programs/rawpid.zc)" "1" "Host.raw: getpid (39 on x86-64) answers"; fi
+}
+
+s_micro() {
+ T=$ROOT/micro
+ gcc -O2 -w -fwrapv src/micro/micro.c src/micro/port_linux.c -Isrc/core -Isrc/micro -o $T/zym || exit 1
+ run $T/zym "micro"
+ selfc $T/zym $T/gm.bin
+ cmp -s $T/g1.bin $T/gm.bin && chk 1 1 "microcore self-compile matches" || chk 0 1 "microcore self-compile matches"
+ gcc -ffreestanding -w -c src/micro/micro.c -Isrc/core -Isrc/micro -o $T/micro.o && gcc -ffreestanding -w -c src/micro/port_stub.c -Isrc/core -Isrc/micro -o $T/stub.o && chk 1 1 "microcore and the stub port compile freestanding" || chk 0 1 "microcore and the stub port compile freestanding"
+ gcc -O2 -w -fwrapv -DJTYPE=int -DZY_SOFTFLOAT -DZYINTERP src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zysoft || exit 1
+ chk "$($T/zysoft < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "soft-float fallback matches the hardware line"
+ chk "$($T/zysoft < tests/programs/ftruth.zc)" "00111000110107" "soft-float negative zero is false"
+ chk "$($T/zysoft < tests/programs/fo3.zc)" "5997 5997 4.000000 nan 0 3.250000 1 0 0" "soft-float o3 float ops match"
+ chk "$($T/zysoft < tests/programs/ftext.zc)" "1e+308 1e-10 -0 inf -inf nan 0.1 1.50 1.250e+0 100000000000000000000.000000 1e+40 1111111111" "soft-float text matches"
+}
+
+s_small() {
+ T=$ROOT/small
+ # smaller machines. addresses are literals, so each size is its own genome.
+ gcc -O2 -w -fwrapv src/tools/genmap.c -Isrc/core -o $T/genmap || exit 1
+ $T/genmap --preset default --check >/dev/null && chk 1 1 "default map matches the checked-in sources" || chk 0 1 "default map matches the checked-in sources"
+ $T/genmap --preset small -o $T/small || exit 1
+ $T/genmap --mode compiler --name small --heap-lo 8192 --heap 20480 --types 8192 \
+ --genome 245760 --frame 2048 --return 512 --mark 512 --data 3072 \
+ --snap-cap 1000 --quote-cap 300 -o $T/small-flags || exit 1
+ cmp -s $T/small/memmap.h $T/small-flags/memmap.h && cmp -s $T/small/rt.zc $T/small-flags/rt.zc \
+ && cmp -s $T/small/compiler.zc $T/small-flags/compiler.zc && cmp -s $T/small/map.txt $T/small-flags/map.txt \
+ && chk 1 1 "compiler flags reproduce the small map" || chk 0 1 "compiler flags reproduce the small map"
+ cp src/tools/zc0.c src/core/fops.h src/core/bops.h src/core/stack.h $T/small/
+ gcc -O1 -w -fwrapv $T/small/zc0.c -o $T/zc0s || exit 1
+ cat $T/small/rt.zc $T/small/compiler.zc | $T/zc0s > $T/small/genome.h || exit 1
+ cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/core.c src/core/io_linux.c src/core/fops.h src/core/bops.h src/core/stack.h $T/small/
+ gcc -O2 -w -fwrapv $T/small/micro.c $T/small/port_linux.c -o $T/zyms || exit 1
+ gcc -O2 -w -fwrapv -DJTYPE=int $T/small/core.c $T/small/io_linux.c -o $T/zys || exit 1
+ gcc -ffreestanding -w -c $T/small/micro.c -o $T/micro_s.o && gcc -ffreestanding -w -c $T/small/port_stub.c -o $T/stub_s.o && chk 1 1 "small microcore compiles freestanding" || chk 0 1 "small microcore compiles freestanding"
+ sameout() { # $1 = other vm, $2 = label, $3 = source file
+ local a b
+ a=$($T/zy < "$3" 2>&1 | tr '\n' '/')
+ b=$($1 < "$3" 2>&1 | tr '\n' '/')
+ chk "$b" "$a" "$2"
+ }
+ for f in examples/hello.zc examples/fact.zc examples/lists.zc examples/typed.zc examples/wiring.zc examples/life.zc \
+ tests/programs/basics.zc tests/programs/wide.zc tests/programs/types.zc tests/programs/alias.zc tests/programs/meta.zc tests/programs/arrays.zc \
+ tests/programs/joins.zc tests/programs/enhance.zc tests/programs/o3.zc tests/programs/csyn.zc tests/programs/umd.zc tests/programs/quarantine.zc tests/programs/roots.zc \
+ tests/programs/float.zc tests/programs/ftruth.zc tests/programs/ftype.zc tests/programs/fo3.zc tests/programs/ftext.zc tests/programs/bytes.zc \
+ tests/programs/strings.zc examples/strings.zc tests/programs/share.zc tests/programs/share_dbg.zc; do
+ sameout $T/zyms "small $(basename "$f" .zc)" "$f"
+ sameout $T/zys "small core $(basename "$f" .zc)" "$f"
+ done
+
+ # code literals can look like pointers on a compact map. test collection and
+ # the retained root without assuming the default map's exact reclaim count.
+ for vm in $T/zyms $T/zys; do
+ out=$(sed 's/f >= 990/f > 0/' tests/programs/gc.zc | $vm | tr '\n' '/')
+ chk "$out" "1000 1 900 1 199900/" "small collector makes progress and preserves its root"
+ done
+
+ $T/genmap --preset run -o $T/run || exit 1
+ $T/genmap --mode run --words 20480 -o $T/run-flags || exit 1
+ cmp -s $T/run/memmap.h $T/run-flags/memmap.h && cmp -s $T/run/rt.zc $T/run-flags/rt.zc \
+ && cmp -s $T/run/map.txt $T/run-flags/map.txt \
+ && chk 1 1 "run flags reproduce the growing run-only map" || chk 0 1 "run flags reproduce the growing run-only map"
+}
+
+s_runonly() {
+ T=$ROOT/runonly
+ # its own run map: the run-only image is built against this one, and the
+ # small-machine section lays out its own rather than sharing this
+ $T/genmap --preset run -o $T/run || exit 1
+ cp src/tools/zc0.c src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $T/run/
+ gcc -O1 -w -fwrapv $T/run/zc0.c -o $T/zc0r || exit 1
+ runonly() { # $1 label, $2 source. the program is already in the image, so it reads no source.
+ # the map is sized to the program: src/tools/automap.sh lays it out for the image's own length
+ local a b d=$T/run-$1 input=$2
+ if [ "$1" = gc ]; then input=$T/collector-progress.zc; sed 's/f >= 990/f > 0/' "$2" > "$input"; fi
+ GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc "$input" >/dev/null || { chk 0 1 "run $1 builds"; return; }
+ cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/
+ gcc -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr || { chk 0 1 "run $1 links"; return; }
+ a=$($T/zy < "$input" 2>&1 | tr '\n' '/')
+ b=$($T/zymr </dev/null 2>&1 | tr '\n' '/')
+ chk "$b" "$a" "run $1"
+ }
+ runonly hello examples/hello.zc
+ runonly fact examples/fact.zc
+ runonly wide tests/programs/wide.zc
+ runonly selfpatch examples/selfpatch.zc
+ runonly gc tests/programs/gc.zc
+ runonly float tests/programs/float.zc
+ runonly ftruth tests/programs/ftruth.zc
+ runonly ftext tests/programs/ftext.zc
+ runonly bytes tests/programs/bytes.zc
+ runonly strings tests/programs/strings.zc
+ runonly strings-ex examples/strings.zc
+ runonly share tests/programs/share.zc
+ d=$T/run-echo; GENMAP=$T/genmap bash src/tools/automap.sh run $d src/runtime/run.zc examples/echo.zc >/dev/null && cp src/micro/micro.c src/micro/micro_port.h src/micro/port_linux.c src/micro/port_stub.c src/core/fops.h src/core/bops.h src/core/stack.h $d/ \
+ && gcc -O2 -w -fwrapv $d/micro.c $d/port_linux.c -o $T/zymr \
+ && chk "$(printf 'abc\n' | $T/zymr | tr '\n' '/')" "$( (cat examples/echo.zc; printf '`abc\n') | $T/zy | tr '\n' '/')" "run echo" \
+ || chk 0 1 "run echo"
+ # zcc: a program becomes a static binary that runs without stdin and with a held-open pipe
+ if CC=gcc src/tools/zcc -o $T/zcc-hello examples/hello.zc >/dev/null 2>&1; then
+ chk "$($T/zcc-hello < /dev/null)" "hello, world!" "zcc: hello, static"
+ chk "$(sleep 2 | timeout 5 $T/zcc-hello; echo $?)" "hello, world!
+0" "zcc: a program that never reads stdin does not wait for it"
+ if [ "$(uname -s)" = Linux ]; then chk "$(file -b $T/zcc-hello | grep -c 'statically linked')" "1" "zcc: the binary is statically linked"; else echo "skip zcc static linkage (unavailable on this host)"; fi
+ else chk 0 1 "zcc builds examples/hello.zc"; fi
+ printf 'fn main() {\n\tput_n(1\n\treturn 0;\n}\n' > $T/zcc-bad.zc
+ src/tools/zcc -o $T/zcc-bad $T/zcc-bad.zc </dev/null >/dev/null 2>&1; r=$?
+ chk "$r $( [ -e $T/zcc-bad ] && echo yes || echo no)" "1 no" "zcc: a bad program exits 1 and writes no binary"
+ gcc -ffreestanding -w -c $T/run-echo/micro.c -o $T/micro_r.o && gcc -ffreestanding -w -c $T/run-echo/port_stub.c -o $T/stub_r.o && chk 1 1 "run-only microcore compiles freestanding" || chk 0 1 "run-only microcore compiles freestanding"
+}
+
+s_bare() {
+ T=$ROOT/bare
+ bare() { # $1 target, $2 run|compile, $3 source, $4 label. compare UART stdout to the hosted interpreter.
+ local a b
+ a=$($T/zy < "$3" 2>&1 | tr '\n' '/')
+ b=$(bash src/baremetal/run.sh "$1" "$2" "$3" 2>/dev/null | tr '\n' '/')
+ chk "$b" "$a" "$4"
+ }
+ bareio() { # clock, RAM file, random, BOARD, MW, a long echo, and exit status
+ local mw
+ mw=$(GENMAP=$T/genmap bash src/tools/automap.sh run $T/run-bareio src/runtime/run.zc tests/programs/bareio.zc | sed -n 's/.*MW \([0-9]*\) .*/\1/p')
+ local t="$1" pay a b out st
+ pay=abcdefghijklmnopqrstuvwxyz0123456789
+ a=$( (cat examples/echo.zc; printf '`%s' "$pay") | $T/zy | tr -d '\n')
+ b=$(printf '%s' "$pay" | bash src/baremetal/run.sh "$t" io examples/echo.zc 2>/dev/null | tr -d '\n')
+ chk "$b" "$a" "bare $t echo until eof"
+ b=$(bash src/baremetal/run.sh "$t" run tests/programs/bareio.zc 2>/dev/null | tr -d '\n')
+ chk "$b" "tick 2 zy rand board $mw" "bare $t clock, files, random, memory"
+ out=$(bash src/baremetal/run.sh "$t" run tests/programs/bareexit.zc 2>/dev/null)
+ st=$?
+ chk "$(printf '%s' "$out" | tr -d '\n')" "bye" "bare $t exit status text"
+ chk "$st" "3" "bare $t exit status code"
+ }
+ if bash src/baremetal/run.sh ready riscv; then
+ bare riscv run examples/hello.zc "bare riscv hello"
+ bare riscv run examples/fact.zc "bare riscv fact"
+ bare riscv run tests/programs/wide.zc "bare riscv wide"
+ bare riscv run tests/programs/gc.zc "bare riscv gc"
+ bare riscv run tests/programs/float.zc "bare riscv float"
+ bare riscv run tests/programs/ftruth.zc "bare riscv negative zero"
+ bare riscv run tests/programs/ftext.zc "bare riscv float text"
+ bare riscv run tests/programs/bytes.zc "bare riscv bytes"
+ bare riscv run tests/programs/strings.zc "bare riscv strings"
+ bare riscv run tests/programs/share.zc "bare riscv share"
+ bare riscv compile tests/programs/float.zc "bare riscv compile float"
+ bare riscv compile tests/programs/ftruth.zc "bare riscv compile negative zero"
+ bare riscv compile tests/programs/ftype.zc "bare riscv compile float type"
+ bare riscv compile tests/programs/fo3.zc "bare riscv compile o3 floats"
+ bare riscv compile tests/programs/ftext.zc "bare riscv compile float text"
+ bare riscv compile tests/programs/bytes.zc "bare riscv compile bytes"
+ bare riscv compile tests/programs/strings.zc "bare riscv compile strings"
+ bare riscv compile tests/programs/share.zc "bare riscv compile share"
+ bare riscv compile examples/hello.zc "bare riscv compile hello"
+ bare riscv compile examples/fact.zc "bare riscv compile fact"
+ bare riscv compile tests/programs/wide.zc "bare riscv compile wide"
+ bareio riscv
+ else echo "skip baremetal riscv (no riscv64-linux-gnu-gcc or qemu-system-riscv64)"; fi
+ if bash src/baremetal/run.sh ready aarch64; then
+ bare aarch64 run examples/hello.zc "bare aarch64 hello"
+ bare aarch64 run examples/fact.zc "bare aarch64 fact"
+ bare aarch64 run tests/programs/wide.zc "bare aarch64 wide"
+ bare aarch64 run tests/programs/gc.zc "bare aarch64 gc"
+ bare aarch64 run tests/programs/float.zc "bare aarch64 float"
+ bare aarch64 run tests/programs/ftruth.zc "bare aarch64 negative zero"
+ bare aarch64 run tests/programs/ftext.zc "bare aarch64 float text"
+ bare aarch64 run tests/programs/bytes.zc "bare aarch64 bytes"
+ bare aarch64 run tests/programs/strings.zc "bare aarch64 strings"
+ bare aarch64 run tests/programs/share.zc "bare aarch64 share"
+ bare aarch64 compile tests/programs/float.zc "bare aarch64 compile float"
+ bare aarch64 compile tests/programs/ftruth.zc "bare aarch64 compile negative zero"
+ bare aarch64 compile tests/programs/ftype.zc "bare aarch64 compile float type"
+ bare aarch64 compile tests/programs/fo3.zc "bare aarch64 compile o3 floats"
+ bare aarch64 compile tests/programs/ftext.zc "bare aarch64 compile float text"
+ bare aarch64 compile tests/programs/bytes.zc "bare aarch64 compile bytes"
+ bare aarch64 compile tests/programs/strings.zc "bare aarch64 compile strings"
+ bare aarch64 compile tests/programs/share.zc "bare aarch64 compile share"
+ bare aarch64 compile examples/hello.zc "bare aarch64 compile hello"
+ bare aarch64 compile examples/fact.zc "bare aarch64 compile fact"
+ bare aarch64 compile tests/programs/wide.zc "bare aarch64 compile wide"
+ bareio aarch64
+ else echo "skip baremetal aarch64 (no aarch64-linux-gnu-gcc or qemu-system-aarch64)"; fi
+}
+
+s_jit() {
+ T=$ROOT/jit
+ if command -v aarch64-linux-gnu-gcc >/dev/null && command -v qemu-aarch64 >/dev/null; then
+ aarch64-linux-gnu-gcc -O2 -w -fwrapv -DJTYPE=int -static src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyj || exit 1
+ printf '#!/bin/sh\nexec qemu-aarch64 %s "$@"\n' $T/zyj > $T/jit; chmod +x $T/jit
+ run $T/jit "jit"
+ chk "$(ZYTRACE=1 $T/jit < tests/programs/o3.zc 2>&1 | grep -c 'bytes of arm64')" "8" "jit compiles every o3 region to arm64"
+ selfc $T/jit $T/gj.bin; cmp -s $T/g1.bin $T/gj.bin && chk 1 1 "jit self-compile matches" || chk 0 1 "jit self-compile matches"
+ # native calls need a dynamic binary (a static one has no dlsym), run against the aarch64 libc
+ if [ -d /usr/aarch64-linux-gnu/lib ] && aarch64-linux-gnu-gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyjd -ldl 2>/dev/null; then
+ chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "jit native calls and callbacks (extern, cfunc)"
+ chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "jit native calls from o3 code, stack arguments"
+ chk "$(qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < tests/programs/cabi_sized.zc | md5sum)" "$(md5sum < tests/programs/cabi_sized.expected)" "jit native calls with f32 and the sized integers"
+ # structs by value: the helper library has to be arm64, and cload reads build/sbv.so from the working directory
+ R=$(pwd); aarch64-linux-gnu-gcc -shared -fPIC -w tests/data/sbv.c -o $T/sbv_arm.so && mkdir -p $T/build && cp $T/sbv_arm.so $T/build/sbv.so
+ chk "$(cd $T && qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < $R/tests/programs/structval.zc)" "34 3520 18 325 810 340" "jit structs by value to a c function (aapcs64: registers, hfa, reference, stack)"
+ chk "$(cd $T && qemu-aarch64 -L /usr/aarch64-linux-gnu $T/zyjd < $R/tests/programs/structret.zc)" "3 4 3.500000 4.000000 24 8 1.000000 3.500000" "jit structs returned from a c function (aapcs64 results)"
+ fi
+ else echo "skip jit (no aarch64 toolchain or qemu)"; fi
+}
+
+s_llvm() {
+ T=$ROOT/llvm
+ # llvm-config can exist without the headers and static libs (a runtime-only install), so the gate is a real build
+ llvm_ok=0
+ if command -v llvm-config >/dev/null && gcc -O2 -w -fwrapv -DJTYPE=int -DZYLLVM $(llvm-config --cflags) src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zyl $(llvm-config --ldflags --libs) 2>/dev/null; then llvm_ok=1; fi
+ if [ $llvm_ok = 1 ]; then
+ run $T/zyl "llvm"
+ chk "$(ZYTRACE=1 $T/zyl < tests/programs/o3.zc 2>&1 | grep -c 'compiled by llvm')" "8" "llvm compiles every o3 region"
+ printf '#!/bin/sh\nZYHOT=1 exec %s "$@"\n' $T/zyl > $T/llall; chmod +x $T/llall
+ chk "$($T/zyl < tests/programs/structval.zc)" "34 3520 18 325 810 340" "llvm structs by value (the call goes through the interpreter's native call)"
+ chk "$($T/zyl < tests/programs/structret.zc)" "3 4 3.500000 4.000000 24 8 1.000000 3.500000" "llvm structs returned from a c function"
+ chk "$($T/llall < tests/programs/types.zc | md5sum)" "$($T/zy < tests/programs/types.zc | md5sum)" "llvm compiling every block agrees"
+ chk "$($T/llall < tests/programs/float.zc)" "4.000000 4.500000 3.000000 2.500000 2.500000 0.001500 0.100000 -0.125000 inf -inf nan 1 1 1 0 0 3 -3 3.000000 -2.000000 2" "llvm compiling every block agrees on floats"
+ chk "$($T/llall < tests/programs/ftruth.zc)" "00111000110107" "llvm compiling every block agrees on negative zero"
+ chk "$($T/llall < tests/programs/ftype.zc)" "3.500000 10.500000 3.500000 4.250000 5.250000 4.250000 6.500000 2 4 4.250000 8.500000 1 4" "llvm compiling every block agrees on the float type"
+ chk "$($T/llall < tests/programs/fo3.zc)" "5997 5997 4.000000 nan 0 3.250000 1 0 0" "llvm compiling every block agrees on o3 floats"
+ chk "$($T/llall < tests/programs/ftext.zc)" "1e+308 1e-10 -0 inf -inf nan 0.1 1.50 1.250e+0 100000000000000000000.000000 1e+40 1111111111" "llvm compiling every block agrees on float text"
+ chk "$($T/llall < tests/programs/bytes.zc)" "17 34 43981 16909060 1 205 171 -3674937295934316015 1108152156331 0 0 30768
+ABC" "llvm compiling every block agrees on bytes"
+ chk "$($T/llall < tests/programs/strings.zc)" "hi! 3 3 104 i! 1 1 1 -42 42 -42 11 1 -9223372036854775808 0 1 3 10 1 1 1 W ab" "llvm compiling every block agrees on strings"
+ chk "$($T/llall < tests/programs/share.zc)" "1 2 1 4 1 1 1 1 42 1 1 1 6 0" "llvm compiling every block agrees on shared cells"
+ chk "$($T/zyl < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "llvm native calls and callbacks (extern, cfunc)"
+ chk "$($T/llall < tests/programs/cabi.zc | md5sum)" "$(md5sum < tests/programs/cabi.expected)" "llvm compiling every block agrees on native calls"
+ chk "$($T/zyl < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "llvm calls natives from o3 regions directly"
+ chk "$(ZYTRACE=1 $T/zyl < tests/programs/cabi_o3.zc 2>&1 | grep -c 'compiled by llvm')" "2" "llvm compiles the o3 regions that call natives"
+ chk "$($T/llall < tests/programs/cabi_o3.zc | md5sum)" "$(md5sum < tests/programs/cabi_o3.expected)" "llvm compiling every block agrees on o3 native calls"
+ chk "$($T/zyl < tests/programs/cabi_sized.zc | md5sum)" "$(md5sum < tests/programs/cabi_sized.expected)" "llvm native calls with f32 and the sized integers"
+ chk "$($T/llall < tests/programs/sized.zc | md5sum)" "$(md5sum < tests/programs/sized.expected)" "llvm compiling every block agrees on sized integers"
+ chk "$($T/llall < tests/programs/bitops.zc | md5sum)" "$(md5sum < tests/programs/bitops.expected)" "llvm compiling every block agrees on the new micro-ops"
+ chk "$($T/llall < tests/programs/f32.zc | md5sum)" "$(md5sum < tests/programs/f32.expected)" "llvm compiling every block agrees on f32"
+ chk "$($T/llall < tests/programs/sizedvec.zc | md5sum)" "$(md5sum < tests/programs/sizedvec.expected)" "llvm compiling every block agrees on growable sized arrays"
+ chk "$($T/llall < tests/programs/packedfields.zc | md5sum)" "$(md5sum < tests/programs/packedfields.expected)" "llvm compiling every block agrees on packed fields"
+ chk "$($T/llall < tests/programs/generics.zc | md5sum)" "$(md5sum < tests/programs/generics.expected)" "llvm compiling every block agrees on generic instances"
+ chk "$($T/llall < tests/programs/genmeth.zc | md5sum)" "$(md5sum < tests/programs/genmeth.expected)" "llvm compiling every block agrees on generic struct methods"
+ chk "$($T/llall < tests/programs/o3push.zc | md5sum)" "$(md5sum < tests/programs/o3push.expected)" "llvm compiling every block agrees on inline push and pop"
+ selfc $T/zyl $T/gl.bin; cmp -s $T/g1.bin $T/gl.bin && chk 1 1 "llvm self-compile matches" || chk 0 1 "llvm self-compile matches"
+ else echo "skip llvm (no llvm-config, or its headers and libraries are missing)"; fi
+}
+
+s_lsp() {
+ T=$ROOT/lsp
+ # the language server: the compiler itself in check mode, so these are the real compiler's findings
+ if bash src/tools/mklsp.sh $T/lsp >/dev/null 2>&1; then
+ lsp=$T/lsp/zclsp
+ chk "$($lsp < examples/hello.zc)" "no errors" "lsp: a clean program"
+ chk "$($lsp < tests/errors/back_to_m.zc | head -1 | cut -d' ' -f2-)" "error: $(head -1 tests/errors/back_to_m.zc | sed 's#^// expect: ##')" "lsp: the compiler's own message"
+ h=$($lsp < examples/opt_test.zc)
+ for w in "is never read" "is never reassigned; make it a const" "is a shift; write << 3" "the number 1234 has no name" "new inside a loop" "stack buffer"; do
+ case "$h" in *"$w"*) chk 1 1 "lsp hint: $w";; *) chk 0 1 "lsp hint: $w";; esac
+ done
+ chk "$($lsp < examples/struct_closure_opt.zc | grep -c error)" "0" "lsp: methods and closures check clean"
+ # a file of a larger build checks on its own: its bodyless declarations are not errors in check mode
+ for f in src/runtime/rt.zc src/runtime/run.zc src/lsp/server.zc; do chk "$($lsp < $f | grep -c ': error:')" "0" "lsp: $f checks alone"; done
+ chk "$($lsp < tests/programs/decl_undefined.zc)" "no errors" "lsp: a bodyless declaration is not an error in check mode"
+ chk "$($lsp < tests/programs/undefined_call.zc | grep -c ': error:')" "1" "lsp: a call to a name nothing defines is still an error"
+ chk "$($lsp --annotate < tests/programs/annotate.zc | md5sum)" "$(md5sum < tests/programs/annotate.expected)" "lsp --annotate writes the types the compiler can see into a program"
+ chk "$($lsp --annotate < tests/programs/annotate.zc | $T/zy)" "$($T/zy < tests/programs/annotate.zc)" "an annotated program means what the original did"
+ chk "$($lsp --types < tests/programs/annotate.zc | head -3 | tr '\n' '/')" "9:1 3 int/10:1 3 char*/11:1 3 int/" "lsp --types lists the edits without making them"
+ chk "$($lsp --annotate < tests/programs/annotate.expected | md5sum)" "$(md5sum < tests/programs/annotate.expected)" "annotating an annotated program changes nothing more"
+ printf 'Content-Length: 40\r\n\r\n{"jsonrpc": [2.0", "method": "x"}' | timeout 10 $lsp > /dev/null 2>&1; chk $? 0 "lsp: a stray brace in an array ends the message, and the server exits"
+ if [ $QUICK = 1 ]; then echo "skip lsp protocol and corpus tests (quick)"
+ elif command -v node >/dev/null; then
+ node tests/drivers/storage_analysis_test.js $lsp > $T/storage.out 2>&1; r=$?
+ grep -v '^ok' $T/storage.out
+ chk $r 0 "lsp: definite storage warnings and reference lifetime hovers"
+ node tests/drivers/lsp_test.js $lsp > $T/lsp.out 2>&1; r=$?
+ grep -v '^ok' $T/lsp.out
+ chk $r 0 "lsp: protocol and corpus tests ($(grep -c '^ok' $T/lsp.out) checks)"
+ node tests/drivers/lsp_live.js $lsp > $T/lsplive.out 2>&1; r=$?
+ grep -v '^ok' $T/lsplive.out
+ chk $r 0 "lsp: the native server answers while the client keeps the pipe open"
+ node tests/drivers/lsp_sandbox.js $lsp > $T/lspsb.out 2>&1; r=$?
+ grep -v '^ok' $T/lspsb.out
+ chk $r 0 "lsp: compile-time code in a checked file cannot write files"
+ # the diagnostics cache: its key rules, then the extension's client end to end on the built-in server
+ node tests/drivers/diagcache_test.js > $T/dcache.out 2>&1; r=$?
+ grep -v '^ok' $T/dcache.out
+ chk $r 0 "lsp: the diagnostics cache keys and store ($(grep -c '^ok' $T/dcache.out) checks)"
+ node tests/drivers/ext_cache_test.js > $T/ecache.out 2>&1; r=$?
+ grep -v '^ok' $T/ecache.out
+ chk $r 0 "lsp: the extension's cache, end to end ($(grep -c '^ok' $T/ecache.out) checks)"
+ # the same server as WebAssembly, through the extension's host (needs clang)
+ if command -v clang >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32 && LSP_BUILD_DIR=$T/lsp LSP_REUSE_BUILD=1 bash src/tools/mklspwasm.sh $T/lsp/lsp.wasm >/dev/null 2>&1; then
+ node tests/drivers/lsp_wasm.js $T/lsp/lsp.wasm > $T/lspw.out 2>&1; r=$?
+ grep -v '^ok' $T/lspw.out
+ chk $r 0 "lsp: wasm server through the extension's host ($(grep -c '^ok' $T/lspw.out) checks)"
+ else echo "skip lsp wasm tests (no clang with wasm32)"; fi
+ # the same server run by the javascript vm, the extension's fallback when wasm is not there.
+ # the vm starts at the image's heap and grows on the heap's request, so a long document is the test
+ node src/js/extract_lsp.js >/dev/null && mkdir -p $T/vmserver && cp src/js/tinyvm.js src/js/layout.js src/js/lsp.json $T/vmserver/
+ node tests/drivers/lsp_vm.js $T/vmserver > $T/lspvm.out 2>&1; r=$?
+ grep -v '^ok' $T/lspvm.out
+ chk $r 0 "lsp: the server in the javascript vm, through the extension's host ($(grep -c '^ok' $T/lspvm.out) checks)"
+ else echo "skip lsp protocol tests (no node)"; fi
+ else chk 0 1 "lsp server builds"; fi
+}
+
+s_js() {
+ T=$ROOT/js
+ # the javascript vm, and the microcore as webassembly. both run the same genome
+ if command -v node >/dev/null; then
+ chk "$(node -e "console.log(require('./src/js/genome.json').glen)")" "$(grep -m1 '#define GLEN' src/core/genome.h | awk '{print $3}')" "src/js/genome.json is up to date"
+ chk "$(node src/js/tinyvm.js examples/hello.zc)" "hello, world!" "js vm: hello"
+ chk "$(node src/js/tinyvm.js examples/fact.zc | tail -1)" "3628800" "js vm: factorial"
+ chk "$(node src/js/tinyvm.js examples/primes.zc | tail -1)" "widest gap: 72, after 31397" "js vm: primes"
+ chk "$(node src/js/tinyvm.js tests/programs/growone.zc)" "12 1" "js vm: a block past the initial heap grows the machine"
+ if command -v clang >/dev/null && command -v wasm-ld >/dev/null && clang --print-targets 2>/dev/null | grep -q wasm32; then
+ bash src/web/build.sh $T/zc.wasm >/dev/null && node tests/drivers/wasm_test.js $T/zc.wasm > $T/wasm.out 2>&1; r=$?
+ grep -v '^ok' $T/wasm.out
+ chk $r 0 "wasm: the microcore in webassembly ($(grep -c '^ok' $T/wasm.out) checks)"
+ else echo "skip wasm (no clang with the wasm32 target, or no wasm-ld)"; fi
+ else echo "skip js and wasm (no node)"; fi
+}
+
+s_make() {
+ T=$ROOT/make
+ # the makefile builds the programs and the run-only image tool
+ if command -v make >/dev/null; then
+ make -s BIN=$T/mbin B=$T/mbuild all >/dev/null 2>&1 && chk "$($T/mbin/zy < examples/hello.zc)" "hello, world!" "make: bin/zy runs a program" || chk 0 1 "make: all builds"
+ make -s BIN=$T/mbin B=$T/mbuild run PROG=examples/hello.zc >/dev/null 2>&1 && chk "$($T/mbuild/run/zymr </dev/null)" "hello, world!" "make: run PROG= builds a run-only image" || chk 0 1 "make: run builds"
+ else echo "skip make (not installed)"; fi
+}
+
+# docs/LEARN.md: every example it shows runs and prints what the guide says, and
+# every block in it is one of those examples (tests/drivers/learn.py)
+s_learn() {
+ T=$ROOT/learn
+ gcc -O2 -w -fwrapv -DJTYPE=int src/core/core.c src/core/io_linux.c -Isrc/core -o $T/zy
+ # learn.py prints its own ok and FAIL lines, which the tally below counts
+ python3 tests/drivers/learn.py $T/zy; true
+}
+
+# ---- run the sections ----
+# each section runs in its own subshell with its own counters and its own log,
+# so several can share the machine. the logs are printed afterwards in the
+# order below, which is the order the checks used to run in.
+pids=(); names=()
+reap(){
+ local p=${pids[0]} n=${names[0]} st
+ wait "$p"; st=$?
+ [ "$st" = 0 ] || echo "FAIL $n: the section exited with status $st" >> $T/log.$n
+ pids=("${pids[@]:1}"); names=("${names[@]:1}")
+}
+sec(){ # $1 = the name the section reports under, $2 = the function to run
+ local n=$1
+ "$2" > $T/log.$n 2>&1 &
+ pids+=($!); names+=("$n")
+ [ ${#pids[@]} -ge $JOBS ] && reap
+ return 0
+}
+# the bootstrap's own checks stay counted; the sections are tallied from their
+# logs below, so a section that dies half way still reports what it got through.
+b_ok=$ok; b_bad=$bad; ok=0; bad=0
+for n in $SECS; do sec $n s_$n; done
+while [ ${#pids[@]} -gt 0 ]; do reap; done
+for n in $SECS; do
+ cat $T/log.$n
+ ok=$((ok + $(grep -c '^ok ' $T/log.$n)))
+ bad=$((bad + $(grep -c '^FAIL' $T/log.$n)))
+done
+ok=$((ok + b_ok)); bad=$((bad + b_bad))
+echo "$ok passed, $bad failed"; [ $bad = 0 ]