main zmx / test / reattach.bats
Eric Bower  ·  2026-08-30
 1#!/usr/bin/env bats
 2# Re-attach behavior. `zmx attach` needs a tty, so a small python client that
 3# connects, sends .Init and holds the socket stands in for one here.
 4
 5load test_helper
 6
 7# fake_attach <session> <rows> <cols> [hold_seconds]
 8fake_attach() {
 9  local session="$1" rows="$2" cols="$3" hold="${4:-0.2}"
10  python3 - "$ZMX_DIR" "$session" "$rows" "$cols" "$hold" 3>&- <<'PY'
11import os, socket, struct, sys, time
12sock_dir, name, rows, cols, hold = sys.argv[1], sys.argv[2], int(sys.argv[3]), int(sys.argv[4]), float(sys.argv[5])
13s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14s.connect(os.path.join(sock_dir, name))
15payload = struct.pack("<HHHH", rows, cols, 0, 0)            # ipc.Resize
16s.sendall(struct.pack("<BIxxx", 7, len(payload)) + payload)  # ipc.Header, tag .Init = 7
17time.sleep(hold)
18s.close()
19PY
20}
21
22# Shell snippet: send DA1 and report whether a reply arrived within $1
23# seconds. The result word is assembled by printf so that it only appears in
24# history as program output, never as part of the echoed command line.
25da1_probe() {
26  echo "printf '\033[c'; if IFS= read -r -s -t $1 -d c r; then printf 'DA1_%s\n' ANSWERED; else printf 'DA1_%s\n' TIMEOUT; fi"
27}
28
29count() { "$ZMX" history "$1" | grep -o "$2" | wc -l | tr -d ' '; }
30
31@test "daemon answers DA1 while detached, also after a client has detached" {
32  "$ZMX" run test-da -d bash -c "$(da1_probe 5)"
33  wait_for_output test-da "DA1_ANSWERED"
34
35  fake_attach test-da 24 80          # attach + detach
36  "$ZMX" run test-da bash -c "$(da1_probe 5)"
37  for _ in $(seq 60); do [ "$(count test-da DA1_ANSWERED)" -eq 2 ] && break; sleep 0.1; done
38  [ "$(count test-da DA1_ANSWERED)" -eq 2 ]
39  [ "$(count test-da DA1_TIMEOUT)" -eq 0 ]
40}
41
42@test "daemon leaves DA1 to the client while one is attached" {
43  # The session's own command probes after a delay; a client is attached by
44  # then, so the daemon must stay quiet and (with no real terminal behind the
45  # fake client) the probe times out.
46  "$ZMX" run test-da2 -d bash -c "sleep 1; $(da1_probe 1); sleep 30"
47  wait_for_session test-da2
48  fake_attach test-da2 24 80 5 &
49  wait_for_output test-da2 "DA1_TIMEOUT" 5
50  kill %1 2>/dev/null || true
51}
52
53@test "re-attach at the same size delivers SIGWINCH to foreground process" {
54  # The trap prints a word assembled at runtime so it can't be confused with
55  # the echoed command line in history.
56  "$ZMX" run test-winch -d bash -c 'trap "printf \"GOT_%s\n\" WINCH" WINCH; echo READY; while :; do sleep 0.1; done'
57  wait_for_output test-winch "READY"
58
59  fake_attach test-winch 24 80       # first attach: sets the initial size
60  sleep 0.5
61  before=$(count test-winch GOT_WINCH)
62
63  fake_attach test-winch 24 80       # re-attach at the same size
64  for _ in $(seq 30); do
65    after=$(count test-winch GOT_WINCH)
66    [ "$after" -gt "$before" ] && break
67    sleep 0.1
68  done
69  [ "$after" -gt "$before" ]
70}