- commit
- d63cf5b
- parent
- e70e8b2
- author
- x1f9
- date
- 2026-04-02 17:32:46 -0400 EDT
test: add BATS session lifecycle tests and mise.toml 14 tests covering session creation, listing, killing, history, wait, ZMX_DIR isolation, and rapid churn. These tests create real daemon processes — without the inherited-FD close fix, every test that calls `zmx run` would hang indefinitely. mise.toml declares bats 1.13.0 as a dev dependency, establishing the pattern for managing project tooling.
3 files changed,
+226,
-0
+2,
-0
1@@ -0,0 +1,2 @@
2+[tools]
3+bats = "1.13.0"
+184,
-0
1@@ -0,0 +1,184 @@
2+#!/usr/bin/env bats
3+# Session lifecycle tests for zmx.
4+#
5+# These tests create real zmx sessions — forking daemon processes, allocating
6+# PTYs, running commands. Without the inherited-FD close fix, every test that
7+# calls `zmx run` would hang indefinitely because bats waits for its internal
8+# FDs (3+) to close, and the daemon inherits them.
9+#
10+# If this test suite completes at all, the FD fix is working.
11+
12+load test_helper
13+
14+# ============================================================================
15+# Session creation
16+# ============================================================================
17+
18+@test "run: creates a session" {
19+ run "$ZMX" run test-create echo hello
20+ [ "$status" -eq 0 ]
21+ [[ "$output" == *"session \"test-create\" created"* ]]
22+
23+ wait_for_session test-create
24+ run "$ZMX" list --short
25+ [[ "$output" == "test-create" ]]
26+}
27+
28+@test "run: sends command to existing session" {
29+ "$ZMX" run test-send echo first
30+ wait_for_session test-send
31+
32+ run "$ZMX" run test-send echo second
33+ [ "$status" -eq 0 ]
34+ [[ "$output" == *"command sent"* ]]
35+ # Should NOT say "created" — session already exists
36+ [[ "$output" != *"created"* ]]
37+}
38+
39+@test "run: requires a command argument" {
40+ run "$ZMX" run test-nocmd
41+ [ "$status" -ne 0 ]
42+}
43+
44+# ============================================================================
45+# Session listing
46+# ============================================================================
47+
48+@test "list: no sessions returns cleanly" {
49+ run "$ZMX" list
50+ [ "$status" -eq 0 ]
51+ [[ "$output" == *"no sessions found"* ]]
52+}
53+
54+@test "list: shows session details" {
55+ "$ZMX" run test-list echo hello
56+ wait_for_session test-list
57+
58+ run "$ZMX" list
59+ [ "$status" -eq 0 ]
60+ [[ "$output" == *"test-list"* ]]
61+ [[ "$output" == *"pid="* ]]
62+ [[ "$output" == *"cmd=echo hello"* ]]
63+}
64+
65+@test "list --short: shows only session names" {
66+ "$ZMX" run test-short-a true
67+ "$ZMX" run test-short-b true
68+ wait_for_session test-short-a
69+ wait_for_session test-short-b
70+
71+ run "$ZMX" list --short
72+ [ "$status" -eq 0 ]
73+ [[ "$output" == *"test-short-a"* ]]
74+ [[ "$output" == *"test-short-b"* ]]
75+}
76+
77+@test "list --short: empty when no sessions" {
78+ run "$ZMX" list --short
79+ [ "$status" -eq 0 ]
80+ [ -z "$output" ]
81+}
82+
83+# ============================================================================
84+# Session kill
85+# ============================================================================
86+
87+@test "kill: removes a session" {
88+ "$ZMX" run test-kill true
89+ wait_for_session test-kill
90+
91+ run "$ZMX" kill test-kill
92+ [ "$status" -eq 0 ]
93+ [[ "$output" == *"killed session test-kill"* ]]
94+
95+ run "$ZMX" list --short
96+ [[ "$output" != *"test-kill"* ]]
97+}
98+
99+@test "kill: multiple sessions at once" {
100+ "$ZMX" run kill-a true
101+ "$ZMX" run kill-b true
102+ wait_for_session kill-a
103+ wait_for_session kill-b
104+
105+ run "$ZMX" kill kill-a kill-b
106+ [ "$status" -eq 0 ]
107+ [[ "$output" == *"killed session kill-a"* ]]
108+ [[ "$output" == *"killed session kill-b"* ]]
109+}
110+
111+@test "kill --force: removes socket file for dead session" {
112+ "$ZMX" run test-force true
113+ wait_for_session test-force
114+
115+ # Get the daemon PID and kill it directly (simulating a crash)
116+ local pid
117+ pid=$("$ZMX" list 2>/dev/null | grep test-force | sed 's/.*pid=\([0-9]*\).*/\1/')
118+ if [[ -n "$pid" ]]; then
119+ kill -9 "$pid" 2>/dev/null || true
120+ sleep 0.5
121+ fi
122+
123+ # Regular kill may fail on the dead session; --force cleans up
124+ run "$ZMX" kill --force test-force
125+ [ "$status" -eq 0 ]
126+}
127+
128+# ============================================================================
129+# Session isolation (ZMX_DIR)
130+# ============================================================================
131+
132+@test "ZMX_DIR isolation: sessions in one dir are invisible to another" {
133+ "$ZMX" run test-isolated true
134+ wait_for_session test-isolated
135+
136+ # A different ZMX_DIR should see no sessions
137+ local other_dir="$BATS_TEST_TMPDIR/zmx-other"
138+ mkdir -p "$other_dir"
139+ run env ZMX_DIR="$other_dir" "$ZMX" list --short
140+ [ "$status" -eq 0 ]
141+ [ -z "$output" ]
142+}
143+
144+# ============================================================================
145+# History
146+# ============================================================================
147+
148+@test "history: captures session output" {
149+ "$ZMX" run test-hist echo "bats-marker-xyzzy"
150+ wait_for_session test-hist
151+ sleep 0.5 # give the command time to produce output
152+
153+ run "$ZMX" history test-hist
154+ [ "$status" -eq 0 ]
155+ [[ "$output" == *"bats-marker-xyzzy"* ]]
156+}
157+
158+# ============================================================================
159+# Wait
160+# ============================================================================
161+
162+@test "wait: returns after session command completes" {
163+ "$ZMX" run test-wait echo done
164+ wait_for_session test-wait
165+
166+ # `wait` should return once the command finishes
167+ run timeout 10 "$ZMX" wait test-wait
168+ [ "$status" -eq 0 ]
169+}
170+
171+# ============================================================================
172+# Rapid session churn (stress test for FD handling)
173+# ============================================================================
174+
175+@test "churn: create and kill 5 sessions in sequence" {
176+ for i in 1 2 3 4 5; do
177+ "$ZMX" run "churn-$i" echo "iteration $i"
178+ wait_for_session "churn-$i"
179+ "$ZMX" kill "churn-$i"
180+ done
181+
182+ run "$ZMX" list --short
183+ [ "$status" -eq 0 ]
184+ [ -z "$output" ]
185+}
+40,
-0
1@@ -0,0 +1,40 @@
2+# test_helper.bash — shared setup/teardown for zmx BATS tests
3+
4+REPO_DIR="$(cd "$BATS_TEST_DIRNAME/.." && pwd)"
5+
6+setup() {
7+ # Build once per test suite (skips if already built)
8+ if [[ ! -x "$REPO_DIR/zig-out/bin/zmx" ]]; then
9+ cd "$REPO_DIR" && zig build
10+ fi
11+ ZMX="$REPO_DIR/zig-out/bin/zmx"
12+
13+ # Isolate socket dir so tests don't interfere with real sessions or each other
14+ export ZMX_DIR="$BATS_TEST_TMPDIR/zmx-sockets"
15+ mkdir -p "$ZMX_DIR"
16+}
17+
18+teardown() {
19+ # Kill any sessions created during this test
20+ if [[ -d "$ZMX_DIR" ]]; then
21+ local sessions
22+ sessions=$("$ZMX" list --short 2>/dev/null) || true
23+ if [[ -n "$sessions" ]]; then
24+ echo "$sessions" | xargs "$ZMX" kill --force 2>/dev/null || true
25+ fi
26+ fi
27+}
28+
29+# Helper: wait for a session to appear in list (up to N seconds)
30+wait_for_session() {
31+ local name="$1" timeout="${2:-5}" i=0
32+ while (( i < timeout * 10 )); do
33+ if "$ZMX" list --short 2>/dev/null | grep -qx "$name"; then
34+ return 0
35+ fi
36+ sleep 0.1
37+ (( i++ )) || true
38+ done
39+ echo "Timed out waiting for session '$name'" >&2
40+ return 1
41+}