diff options
| author | 2026-10-12 08:46:23 +0330 | |
|---|---|---|
| committer | 2026-10-12 08:46:23 +0330 | |
| commit | ecc350ca5c2eb50be8309c5f14f8bf0d87db9a02 (patch) | |
| tree | 70e0af76a1b13affff7c6cc8aac7b3e6ef960478 /tests/test.sh | |
a
Diffstat (limited to 'tests/test.sh')
| -rwxr-xr-x | tests/test.sh | 577 |
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 ] |
