aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar zinc <zinc@downsta.ir> 2026-10-15 14:33:28 +0330
committerGravatar zinc <zinc@downsta.ir> 2026-10-15 14:33:28 +0330
commit25aa655e5b9464e291d542081ed1cf0f8fd666af (patch)
treefb2fa4b3e77914ab00829b39142a2f8cfb3a9d8b
parenthold blocks: top-level hold { shared let ...; fn ... } monitors; shared value... (diff)
hold: fixtures (accept, reject, race, rank order) and tests/drivers/hold_test.py
-rw-r--r--tests/drivers/hold_test.py28
-rw-r--r--tests/hold/accept/isolated_globals.zc18
-rw-r--r--tests/hold/accept/monitor.zc15
-rw-r--r--tests/hold/accept/race.zc21
-rw-r--r--tests/hold/accept/rank_order.zc7
-rw-r--r--tests/hold/accept/reentrant.zc12
-rw-r--r--tests/hold/reject/actor_handler.zc8
-rw-r--r--tests/hold/reject/closure.zc5
-rw-r--r--tests/hold/reject/nested.zc5
-rw-r--r--tests/hold/reject/o3.zc5
-rw-r--r--tests/hold/reject/order.zc7
-rw-r--r--tests/hold/reject/outside.zc5
-rw-r--r--tests/hold/reject/pointer.zc5
13 files changed, 141 insertions, 0 deletions
diff --git a/tests/drivers/hold_test.py b/tests/drivers/hold_test.py
new file mode 100644
index 0000000..1f6ea39
--- /dev/null
+++ b/tests/drivers/hold_test.py
@@ -0,0 +1,28 @@
+#!/usr/bin/env python3
+# hold blocks (spec 4.16): accepted programs print what their first line expects, rejected
+# ones report the error their first line names (exit 1), and trapping ones stop with
+# exit 2 and the message named. usage: hold_test.py [VM]
+from pathlib import Path
+import subprocess, sys
+root = Path(__file__).resolve().parents[2]
+vm = sys.argv[1] if len(sys.argv) > 1 else 'build/bin/zy'
+count = bad = 0
+for case in sorted((root / 'tests/hold').glob('*/*.zc')):
+ src = case.read_text(); first = src.splitlines()[0]
+ p = subprocess.run([str(root / vm) if not Path(vm).is_absolute() else vm], input=src, text=True, capture_output=True, cwd=root, timeout=60)
+ out = p.stdout + p.stderr
+ if first.startswith('// expect: '):
+ ok = p.returncode == 0 and p.stdout.strip() == first[11:].strip()
+ elif first.startswith('// error: '):
+ ok = p.returncode == 1 and first[10:].strip() in out
+ elif first.startswith('// trap: '):
+ ok = p.returncode == 2 and first[9:].strip() in out
+ else:
+ ok = False
+ count += 1
+ if not ok:
+ bad += 1; print(f'FAIL {case.relative_to(root)}: rc={p.returncode} out={out[:300]!r}')
+ else:
+ print(f'ok {case.relative_to(root)}')
+print(f'{count - bad}/{count} hold fixtures passed')
+sys.exit(1 if bad or not count else 0)
diff --git a/tests/hold/accept/isolated_globals.zc b/tests/hold/accept/isolated_globals.zc
new file mode 100644
index 0000000..08a3e5c
--- /dev/null
+++ b/tests/hold/accept/isolated_globals.zc
@@ -0,0 +1,18 @@
+// expect: 0 3
+// a plain global is each actor's own; only a shared variable of a hold block is seen by all
+namespace;
+include rt, actor;
+typecheck on;
+let plain = 0;
+hold { shared let seen = 0; fn note() { seen++; return seen; } fn seen_now() { return seen; } }
+int work(int n) { plain = plain + n; note(); return plain; }
+fn main() {
+ actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>), b = rt.new(actor.Actor<int,int>), c = rt.new(actor.Actor<int,int>);
+ a.spawn(work); b.spawn(work); c.spawn(work);
+ a.send(5); b.send(6); c.send(7);
+ a.recv(fn(int n) { return 0; }); b.recv(fn(int n) { return 0; }); c.recv(fn(int n) { return 0; });
+ a.await(); b.await(); c.await();
+ rt.put_n(plain); rt.put_c(' '); rt.put_n(seen_now()); rt.put_c(10);
+ a.close(); b.close(); c.close();
+ return 0;
+}
diff --git a/tests/hold/accept/monitor.zc b/tests/hold/accept/monitor.zc
new file mode 100644
index 0000000..7dfe6f0
--- /dev/null
+++ b/tests/hold/accept/monitor.zc
@@ -0,0 +1,15 @@
+// expect: 902
+namespace;
+include rt;
+hold {
+ shared let c = 5;
+ shared int[3] hist;
+ fn bump(int k) { c = c + k; hist[0]++; return c; }
+ fn twice(int k) { bump(k); return bump(k); }
+ fn get() { return c * 100 + hist[0]; }
+}
+fn main() {
+ twice(2);
+ rt.put_n(get()); rt.put_c(10);
+ return 0;
+}
diff --git a/tests/hold/accept/race.zc b/tests/hold/accept/race.zc
new file mode 100644
index 0000000..f7fdb61
--- /dev/null
+++ b/tests/hold/accept/race.zc
@@ -0,0 +1,21 @@
+// expect: 90000
+// many actors increment one shared counter through a read, then a write; the hold block's lock makes every increment count
+namespace;
+include rt, actor;
+typecheck on;
+hold {
+ shared let count = 0;
+ fn inc() { let v = count; count = v + 1; return v; }
+ fn total() { return count; }
+}
+int work(int n) { let i = 0; while (i < n) { inc(); i++; } return n; }
+fn main() {
+ let i = 0; actor.Actor<int,int>*[?] xs = rt.new(actor.Actor<int,int>*[?]);
+ while (i < 8) { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(work); push(xs, a); i++; }
+ i = 0; while (i < 8) { xs[i].send(10000); i++; }
+ work(10000);
+ i = 0; while (i < 8) { xs[i].recv(fn(int n) { return 0; }); xs[i].await(); i++; }
+ rt.put_n(total()); rt.put_c(10);
+ i = 0; while (i < 8) { xs[i].close(); i++; }
+ return 0;
+}
diff --git a/tests/hold/accept/rank_order.zc b/tests/hold/accept/rank_order.zc
new file mode 100644
index 0000000..9fc903a
--- /dev/null
+++ b/tests/hold/accept/rank_order.zc
@@ -0,0 +1,7 @@
+// expect: 10
+namespace;
+include rt;
+int gb();
+hold { shared let a; fn ga() { a++; return gb(); } }
+hold { shared let b; fn gb() { b++; return b * 10; } }
+fn main() { rt.put_n(ga()); rt.put_c(10); return 0; }
diff --git a/tests/hold/accept/reentrant.zc b/tests/hold/accept/reentrant.zc
new file mode 100644
index 0000000..f0c8293
--- /dev/null
+++ b/tests/hold/accept/reentrant.zc
@@ -0,0 +1,12 @@
+// expect: 3 1
+// a function of a block calling another of the same block doesn't take the lock again, and a later block may be entered from an earlier one
+namespace;
+include rt;
+int later();
+hold {
+ shared let depth, calls;
+ fn inner() { calls++; return depth; }
+ fn outer() { depth = depth + 1; inner(); inner(); inner(); return calls; }
+}
+hold { shared let seen; fn later() { seen++; return seen; } }
+fn main() { rt.put_n(outer()); rt.put_c(' '); rt.put_n(later()); rt.put_c(10); return 0; }
diff --git a/tests/hold/reject/actor_handler.zc b/tests/hold/reject/actor_handler.zc
new file mode 100644
index 0000000..5548cad
--- /dev/null
+++ b/tests/hold/reject/actor_handler.zc
@@ -0,0 +1,8 @@
+// error: total: is shared in a hold block; only the functions written inside that block can use it
+// shared state reached from an actor's handler without going through its hold block
+namespace;
+include rt, actor;
+typecheck on;
+hold { shared let total; fn add(int n) { total = total + n; return total; } }
+int work(int n) { total = total + n; return n; }
+fn main() { actor.Actor<int,int>* a = rt.new(actor.Actor<int,int>); a.spawn(work); a.send(1); a.close(); return 0; }
diff --git a/tests/hold/reject/closure.zc b/tests/hold/reject/closure.zc
new file mode 100644
index 0000000..5c082d2
--- /dev/null
+++ b/tests/hold/reject/closure.zc
@@ -0,0 +1,5 @@
+// error: c: is shared; a closure can't use it
+namespace;
+include rt;
+hold { shared let c; fn a() { let f = fn() { return c; }; return f(); } }
+fn main() { return a(); }
diff --git a/tests/hold/reject/nested.zc b/tests/hold/reject/nested.zc
new file mode 100644
index 0000000..b8d38a6
--- /dev/null
+++ b/tests/hold/reject/nested.zc
@@ -0,0 +1,5 @@
+// error: hold blocks don't nest
+namespace;
+include rt;
+hold { shared let c; hold { shared let d; } }
+fn main() { return 0; }
diff --git a/tests/hold/reject/o3.zc b/tests/hold/reject/o3.zc
new file mode 100644
index 0000000..87c2dd5
--- /dev/null
+++ b/tests/hold/reject/o3.zc
@@ -0,0 +1,5 @@
+// error: f: is in a hold block and can't be o3
+namespace;
+include rt;
+hold { shared let c; o3 int f(int k) { return k; } }
+fn main() { return f(1); }
diff --git a/tests/hold/reject/order.zc b/tests/hold/reject/order.zc
new file mode 100644
index 0000000..9a2943b
--- /dev/null
+++ b/tests/hold/reject/order.zc
@@ -0,0 +1,7 @@
+// trap: was entered while
+// blocks are entered in declaration order; going back to an earlier block while a later one is held stops the program
+namespace;
+include rt;
+hold { shared let a; fn ga() { a++; return a; } }
+hold { shared let b; fn gb() { b++; return ga(); } }
+fn main() { rt.put_n(gb()); rt.put_c(10); return 0; }
diff --git a/tests/hold/reject/outside.zc b/tests/hold/reject/outside.zc
new file mode 100644
index 0000000..a202898
--- /dev/null
+++ b/tests/hold/reject/outside.zc
@@ -0,0 +1,5 @@
+// error: c: is shared in a hold block; only the functions written inside that block can use it
+namespace;
+include rt;
+hold { shared let c; fn a() { c++; return c; } }
+fn main() { c = 3; return a(); }
diff --git a/tests/hold/reject/pointer.zc b/tests/hold/reject/pointer.zc
new file mode 100644
index 0000000..6306a62
--- /dev/null
+++ b/tests/hold/reject/pointer.zc
@@ -0,0 +1,5 @@
+// error: name: is shared, so it holds numbers
+namespace;
+include rt;
+hold { shared char* name = "x"; fn a() { return 0; } }
+fn main() { return a(); }