aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-14 15:54:04 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-14 15:54:04 +0330
commit0879d0a42d137054057cd2a1e37437e6be78ed0b (patch)
treefcb16d3185c1f80fae8d54cd9062bd68d6c0c88d /tests
parentmerge spec conformance suite (diff)
parentmerge main into work/line-tables (diff)
merge line tables
Diffstat (limited to 'tests')
-rw-r--r--tests/drivers/runtime_image_test.py1
-rw-r--r--tests/programs/gc.zc2
-rw-r--r--tests/programs/trace.expected9
-rw-r--r--tests/programs/trace.zc36
-rw-r--r--tests/programs/trace_jit.expected6
-rw-r--r--tests/programs/trace_jit.zc24
-rw-r--r--tests/programs/trace_more.expected38
-rw-r--r--tests/programs/trace_more.zc40
-rw-r--r--tests/programs/trace_run.zc36
-rwxr-xr-xtests/test.sh24
10 files changed, 207 insertions, 9 deletions
diff --git a/tests/drivers/runtime_image_test.py b/tests/drivers/runtime_image_test.py
index 6c44349..64aa353 100644
--- a/tests/drivers/runtime_image_test.py
+++ b/tests/drivers/runtime_image_test.py
@@ -17,6 +17,7 @@ args = parser.parse_args()
cases = [
('enhancements', 'include rt; fn main() { rt.put_n(17 % 5); rt.put_c(32); rt.put_n(17 / 5); }', b'2 3'),
('array algorithms', Path('examples/learn/31-array-algorithms.zc').read_text(), Path('examples/learn/31-array-algorithms.out').read_bytes()),
+ ('line tables', '\n//:lines trace.zc\n' + Path('tests/programs/trace.zc').read_text(), Path('tests/programs/trace.expected').read_bytes().replace(b'<stdin>', b'trace.zc')),
('owned pop', 'include rt; int*.u take() { int*[?] a = rt.new(int*[?]); int* p = rt.new(int); *p = 8; a.push(p); return a.pop(); } fn main() { let p = take(); rt.put_n(rt.isobj(p)); rt.put_c(32); rt.put_n(*p); }', b'1 8'),
# main may be void: run.zc's prototype of main must not fix its type (docs/SPEC.md 5.2.5)
('void main', Path('tests/programs/void_main.zc').read_text(), b'void main\n'),
diff --git a/tests/programs/gc.zc b/tests/programs/gc.zc
index 7d2441b..44fde5d 100644
--- a/tests/programs/gc.zc
+++ b/tests/programs/gc.zc
@@ -12,7 +12,7 @@ fn main() {
churn(1000);
rt.put_n(rt.live_objects() - base); rt.put_c(' ');
f = rt.gc();
- rt.put_n(f >= 990); rt.put_c(' '); // conservative: a stale word or two may keep one alive
+ rt.put_n(f >= 950); rt.put_c(' '); // conservative: stale words, and genome code words that happen to look like heap addresses, keep a few alive
rt.put_n(keep[0]); rt.put_c(' '); // the one a global still points at survives
churn(200000); // 3.2m words through a 393k word heap: needs the automatic collector
rt.put_n(rt.gc_runs > 1); rt.put_c(' ');
diff --git a/tests/programs/trace.expected b/tests/programs/trace.expected
new file mode 100644
index 0000000..2c7dce3
--- /dev/null
+++ b/tests/programs/trace.expected
@@ -0,0 +1,9 @@
+ at <stdin>:18 in where
+ at <stdin>:23 in outer
+ at <stdin>:30 in main
+1
+10
+zy: index 4 out of range (length 4)
+ at <stdin>:5 in get
+ at <stdin>:11 in sum
+ at <stdin>:34 in main
diff --git a/tests/programs/trace.zc b/tests/programs/trace.zc
new file mode 100644
index 0000000..c283f75
--- /dev/null
+++ b/tests/programs/trace.zc
@@ -0,0 +1,36 @@
+namespace;
+include rt;
+
+int get(int[?] t, int i) {
+ return t[i];
+}
+
+int sum(int[?] t, int n) {
+ int s = 0, i = 0;
+ while (i < n) {
+ s = s + get(t, i);
+ i++;
+ }
+ return s;
+}
+
+int where() {
+ rt.trace();
+ return 1;
+}
+
+int outer() {
+ int r = where();
+ return r;
+}
+
+int main() {
+ int[?] t = rt.new(int[?]);
+ t.push(1); t.push(2); t.push(3); t.push(4);
+ rt.put_n(outer());
+ rt.put_c(10);
+ rt.put_n(sum(t, 4));
+ rt.put_c(10);
+ rt.put_n(sum(t, 5));
+ return 0;
+}
diff --git a/tests/programs/trace_jit.expected b/tests/programs/trace_jit.expected
new file mode 100644
index 0000000..68e2eb4
--- /dev/null
+++ b/tests/programs/trace_jit.expected
@@ -0,0 +1,6 @@
+ at <stdin>:9 in down
+ at <stdin>:12 in down
+ at <stdin>:12 in down
+ at <stdin>:12 in down
+ at <stdin>:18 in main
+20000
diff --git a/tests/programs/trace_jit.zc b/tests/programs/trace_jit.zc
new file mode 100644
index 0000000..e864c50
--- /dev/null
+++ b/tests/programs/trace_jit.zc
@@ -0,0 +1,24 @@
+namespace;
+include rt;
+
+// a hot path: the jits have translated down() and the loop in main long before
+// the last call, so the return stack pointer comes from translated code (op 56
+// reads it at the gate store, cb_r on both jits)
+int down(int n, int last) {
+ if (n == 0) {
+ if (last) rt.trace();
+ return 1;
+ }
+ return down(n - 1, last) + 1;
+}
+
+int main() {
+ int i = 0, s = 0;
+ while (i < 5000) {
+ s = s + down(3, i == 4999);
+ i++;
+ }
+ rt.put_n(s);
+ rt.put_c(10);
+ return 0;
+}
diff --git a/tests/programs/trace_more.expected b/tests/programs/trace_more.expected
new file mode 100644
index 0000000..b35d42d
--- /dev/null
+++ b/tests/programs/trace_more.expected
@@ -0,0 +1,38 @@
+ at <stdin>:6 in pt.get
+ at <stdin>:31 in main
+3
+ at <stdin>:34 in <closure>
+ at <stdin>:11 in apply
+ at <stdin>:32 in main
+9
+ at <stdin>:14 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ ... 35 more frames
+ at <stdin>:15 in rec
+ at <stdin>:15 in rec
+ at <stdin>:37 in main
+ at ADDR in quiet
+ at <stdin>:24 in loud
+ at <stdin>:38 in main
diff --git a/tests/programs/trace_more.zc b/tests/programs/trace_more.zc
new file mode 100644
index 0000000..512f93f
--- /dev/null
+++ b/tests/programs/trace_more.zc
@@ -0,0 +1,40 @@
+namespace;
+include rt;
+struct pt {
+ int x;
+ int get() {
+ rt.trace();
+ return this.x;
+ }
+};
+int apply(fn(int) f, int v) {
+ return f(v);
+}
+int rec(int n) {
+ if (n == 0) { rt.trace(); return 0; }
+ return rec(n - 1) + 1;
+}
+lines off;
+int quiet(int n) {
+ rt.trace();
+ return n;
+}
+lines on;
+int loud(int n) {
+ return quiet(n) +
+ 1;
+}
+int main() {
+ pt* p = rt.new(pt);
+ p.x = 3;
+ int k = 5;
+ rt.put_n(p.get()); rt.put_c(10);
+ rt.put_n(apply(fn(int a) {
+ int b = a + k;
+ rt.trace();
+ return b;
+ }, 4)); rt.put_c(10);
+ rec(60);
+ loud(2);
+ return 0;
+}
diff --git a/tests/programs/trace_run.zc b/tests/programs/trace_run.zc
new file mode 100644
index 0000000..1d42e26
--- /dev/null
+++ b/tests/programs/trace_run.zc
@@ -0,0 +1,36 @@
+namespace;
+include rt;
+
+int get(word t, int i) {
+ if (i >= 4) rt.oob(i, 4); return t[i];
+}
+
+int sum(word t, int n) {
+ int s = 0, i = 0;
+ while (i < n) {
+ s = s + get(t, i);
+ i++;
+ }
+ return s;
+}
+
+int where() {
+ rt.trace();
+ return 1;
+}
+
+int outer() {
+ int r = where();
+ return r;
+}
+
+int main() {
+ word t = rt.new(4);
+ t[0] = 1; t[1] = 2; t[2] = 3; t[3] = 4;
+ rt.put_n(outer());
+ rt.put_c(10);
+ rt.put_n(sum(t, 4));
+ rt.put_c(10);
+ rt.put_n(sum(t, 5));
+ return 0;
+}
diff --git a/tests/test.sh b/tests/test.sh
index d6d8f06..a1a5453 100755
--- a/tests/test.sh
+++ b/tests/test.sh
@@ -166,9 +166,12 @@ hello world!42
v" "$l a full capture is still terminated inside its buffer"
chk "$($vm < tests/programs/sys.zc)" "2 zy time" "$l syscalls: write, read, time"
chk "$($vm < tests/programs/memleak.zc)" $'leak: line 8 3 words\n1' "$l memdebug names a live allocation"
- chk "$($vm < tests/programs/uaf.zc)" $'zc: line 10: warning: use after free: this storage\'s lifetime ended on line 9\nzy: use after free from line 7' "$l memdebug: use after free"
- chk "$($vm < tests/programs/uaf_in.zc)" "zy: use after free from line 7" "$l memdebug: use after free through an interior pointer"
- chk "$($vm < tests/programs/doublefree.zc)" $'zy: double free from line 7\nok' "$l memdebug: double free is reported and does nothing"
+ chk "$($vm < tests/programs/uaf.zc)" $'zc: line 10: warning: use after free: this storage\'s lifetime ended on line 9\nzy: use after free from line 7\n at <stdin>:10 in main' "$l memdebug: use after free"
+ chk "$($vm < tests/programs/uaf_in.zc)" $'zy: use after free from line 7\n at <stdin>:11 in main' "$l memdebug: use after free through an interior pointer"
+ chk "$($vm < tests/programs/doublefree.zc)" $'zy: double free from line 7\n at <stdin>:9 in main\nok' "$l memdebug: double free is reported and does nothing"
+ chk "$($vm < tests/programs/trace.zc 2>&1 | md5sum)" "$(md5sum < tests/programs/trace.expected)" "$l rt.trace() and a fault print the call stack with file, line and function"
+ chk "$($vm < tests/programs/trace_more.zc 2>&1 | sed 's/0x[0-9a-f]*/ADDR/' | md5sum)" "$(md5sum < tests/programs/trace_more.expected)" "$l backtraces: methods, closures, lines off, a deep stack is cut to its ends"
+ chk "$($vm < tests/programs/trace_jit.zc 2>&1 | md5sum)" "$(md5sum < tests/programs/trace_jit.expected)" "$l rt.trace() from code the jit has translated: op 56 reads the return stack pointer there too"
chk "$($vm < tests/programs/scrub.zc)" $'zc: line 9: warning: use after free: this storage\'s lifetime ended on line 8\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"
@@ -193,7 +196,7 @@ ABC" "$l o3 lifts byte loads and stores"
chk "$($vm < tests/programs/manyerrors.zc | tr '\n' '/')" "zc: line 6: expected an expression, found ';'/zc: line 7: x: owned object is never used/zc: line 8: type error: initializer: expected char*, got int/zc: line 9: 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 < tests/programs/overflow_frame.zc 2>&1 | head -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"
@@ -216,10 +219,10 @@ ABC" "$l o3 lifts byte loads and stores"
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/o3_bounds.zc 2>&1 | head -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"
+ chk "$($vm < tests/programs/bounds_lo.zc 2>&1 | head -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 | head -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)
@@ -354,7 +357,7 @@ s_runonly() {
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 src/core/genome.h $d/
$CC -pthread -DZY_THREADS -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' '/')
+ a=$($T/zy < "$input" 2>&1 | sed "s#<stdin>#$input#g" | tr '\n' '/')
b=$($T/zymr </dev/null 2>&1 | tr '\n' '/')
chk "$b" "$a" "run $1"
}
@@ -364,6 +367,7 @@ s_runonly() {
runonly selfpatch examples/selfpatch.zc
runonly gc tests/programs/gc.zc
runonly voidmain tests/programs/void_main.zc
+ runonly trace tests/programs/trace_run.zc # the image carries line tables: a fault and rt.trace() print the same backtrace as the resident compiler's
runonly float tests/programs/float.zc
runonly ftruth tests/programs/ftruth.zc
runonly ftext tests/programs/ftext.zc
@@ -386,6 +390,9 @@ s_runonly() {
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
+ if src/tools/zcc -o $T/zcc-trace tests/programs/trace_run.zc >/dev/null 2>&1; then
+ chk "$($T/zcc-trace </dev/null 2>&1 | sed "s#$PWD/##g" | md5sum)" "$($T/zy < tests/programs/trace_run.zc 2>&1 | sed 's#<stdin>#tests/programs/trace_run.zc#g' | md5sum)" "zcc: a fault in a compiled program prints its backtrace"
+ else chk 0 1 "zcc builds tests/programs/trace_run.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"
@@ -660,6 +667,7 @@ s_js() {
chk "$(node $T/jsvm/tinyvm.js examples/fact.zc | tail -1)" "3628800" "js vm: factorial"
chk "$(node $T/jsvm/tinyvm.js examples/primes.zc | tail -1)" "widest gap: 72, after 31397" "js vm: primes"
chk "$(node $T/jsvm/tinyvm.js tests/programs/growone.zc)" "12 1" "js vm: a block past the initial heap grows the machine"
+ chk "$(node $T/jsvm/tinyvm.js tests/programs/trace.zc 2>&1 | md5sum)" "$(md5sum < tests/programs/trace.expected)" "js vm: backtraces read the return stack through gate op 56"
chk "$(node $T/jsvm/tinyvm.js tests/programs/replace_owner.zc)" "$(cat tests/programs/replace_owner.expected)" "js vm: replacement ownership and cleanup"
chk "$(node $T/jsvm/tinyvm.js tests/programs/embed.zc | tr '\n' '/')" "hello/world 11 42 5 255 8 -1 0 119 15 5 255 3 98 321 63/" "js vm: embed declarations read their files through node"
if sh src/tools/findcc.sh --wasm >/dev/null 2>&1 && command -v wasm-ld >/dev/null; then