summaryrefslogtreecommitdiff
path: root/9player/test/adv_ns_process.sh
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-19 23:28:22 -0300
committerGabriel Schneider <[email protected]>2026-09-19 23:28:22 -0300
commitba996acfcad1698adbf4a1834fe50e73b1c6cab9 (patch)
tree282ba00ce5b10d7416aecb9f2f0f0a439340a57d /9player/test/adv_ns_process.sh
parentb05abcba3ea09ea106ad28364c6e40a3ec31b890 (diff)
downloadcloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.tar.gz
cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.zip
Rename programs: 9player -> 9ns, introspect -> 9proc, app -> web (9web)
Directories, binaries, build options (-D9ns, -D9proc), step names, module name (9proc), thread and fs names, env var NINEPLAYER_MOUNT -> NINE_MOUNT, docs and test scripts. Browser assets move to web/static. Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9player/test/adv_ns_process.sh')
-rwxr-xr-x9player/test/adv_ns_process.sh202
1 files changed, 0 insertions, 202 deletions
diff --git a/9player/test/adv_ns_process.sh b/9player/test/adv_ns_process.sh
deleted file mode 100755
index 4ce0ae8..0000000
--- a/9player/test/adv_ns_process.sh
+++ /dev/null
@@ -1,202 +0,0 @@
-#!/usr/bin/env bash
-# Adversarial regression tests for 9player/src/ns.zig and 9player/src/main.zig: process,
-# namespace, signal and CLI handling. Real namespaces, real FUSE.
-# Usage: bash 9player/test/adv_ns_process.sh <9player> <introspect> (part of zig build 9player-adv)
-# Exit 0 on success (or when the machine cannot run the tests), 1 on failure.
-set -u
-
-PLAYER=$(realpath "${1:?path to 9player}")
-INTROSPECT=$(realpath "${2:?path to introspect}")
-# Unix socket paths are limited to ~107 bytes; keep the temp dir short.
-TMP=$(mktemp -d "${TMPDIR:-/tmp}/9padv.XXXXXX")
-PIDS=()
-FAILED=0
-PASSED=0
-
-cleanup() {
- for p in "${PIDS[@]:-}"; do [ -n "$p" ] && kill "$p" 2>/dev/null; done
- rm -rf "$TMP"
-}
-trap cleanup EXIT
-
-if ! unshare -Urm true 2>/dev/null; then echo "SKIP: unprivileged user namespaces unavailable"; exit 0; fi
-if [ ! -c /dev/fuse ]; then echo "SKIP: /dev/fuse missing"; exit 0; fi
-
-pass() { PASSED=$((PASSED + 1)); echo "ok - $1"; }
-fail() { FAILED=$((FAILED + 1)); echo "FAIL - $1"; shift; [ $# -gt 0 ] && printf ' %s\n' "$@"; }
-expect_eq() { if [ "$2" = "$3" ]; then pass "$1"; else fail "$1" "expected: $(printf %q "$2")" "actual: $(printf %q "$3")"; fi; }
-expect_contains() { case "$3" in *"$2"*) pass "$1" ;; *) fail "$1" "missing: $(printf %q "$2")" "in: $(printf %q "$3")" ;; esac; }
-
-SOCK=$TMP/s
-"$INTROSPECT" --unix "$SOCK" &
-PIDS+=($!)
-for _ in $(seq 1 100); do [ -S "$SOCK" ] && break; sleep 0.05; done
-[ -S "$SOCK" ] || { echo "introspect did not create $SOCK"; exit 1; }
-MI_BEFORE=$(grep -v " $TMP" /proc/self/mountinfo | sort)
-
-TIMEOUT=$(command -v timeout)
-run() { "$TIMEOUT" 60 "$PLAYER" --unix "$SOCK" "$@"; }
-
-echo "# CLI"
-expect_eq "--help goes to stdout, exit 0" "Usage: 9player" "$(run --help 2>/dev/null | head -1 | cut -c1-14; )"
-expect_eq "--help exit code" "0" "$("$PLAYER" --help >/dev/null 2>&1; echo $?)"
-expect_eq "--version on stdout" "9player" "$("$PLAYER" --version 2>/dev/null | cut -d' ' -f1)"
-expect_eq "single-dash typo is a usage error, not a program" "125" "$(run -mount /x -- true 2>/dev/null; echo $?)"
-expect_contains "single-dash typo message" "unknown option -mount" "$(run -mount /x -- true 2>&1)"
-expect_eq "--unix= empty is a usage error" "125" "$("$PLAYER" --unix= -- true 2>/dev/null; echo $?)"
-expect_contains "--unix= message" "socket path" "$("$PLAYER" --unix= -- true 2>&1)"
-expect_eq "--mount '' is a usage error" "125" "$(run --mount '' -- true 2>/dev/null; echo $?)"
-expect_eq "--msize huge rejected" "125" "$(run --msize 4294967295 -- true 2>/dev/null; echo $?)"
-expect_eq "--msize 16 MiB accepted" "ok" "$(run --msize 16777216 -- sh -c 'echo ok')"
-expect_contains "empty program name is reported" "empty program name" "$(run -- '' 2>&1)"
-expect_eq "empty program name exit" "125" "$(run -- '' 2>/dev/null; echo $?)"
-expect_eq "empty \$SHELL falls back to /bin/sh" "0" "$(SHELL= run -- </dev/null >/dev/null 2>&1; echo $?)"
-expect_eq "--fd with a closed descriptor fails early" "125" "$("$PLAYER" --fd 987 -- true 2>/dev/null; echo $?)"
-expect_contains "--fd bad descriptor message" "--fd 987: EBADF" "$("$PLAYER" --fd 987 -- true 2>&1)"
-
-echo "# exec failures"
-expect_eq "not found is 127" "127" "$(run -- no-such-program-9player 2>/dev/null; echo $?)"
-expect_eq "PATH element that is a file: still 127" "127" "$(PATH=/etc/passwd run -- true 2>/dev/null; echo $?)"
-expect_contains "PATH element that is a file: message" "exec true: E" "$(PATH=/etc/passwd run -- true 2>&1)"
-printf '#!/bin/sh\necho no\n' >"$TMP/nx"; chmod 644 "$TMP/nx"
-expect_eq "non-executable is 126" "126" "$(run -- "$TMP/nx" 2>/dev/null; echo $?)"
-mkdir -p "$TMP/p1" "$TMP/p2"; cp "$TMP/nx" "$TMP/p1/prog"; printf '#!/bin/sh\necho right\n' >"$TMP/p2/prog"; chmod 755 "$TMP/p2/prog"
-expect_eq "non-executable first in PATH, executable later" "right" "$(PATH=$TMP/p1:$TMP/p2 run -- prog)"
-expect_eq "non-executable only in PATH is 126" "126" "$(PATH=$TMP/p1 run -- prog 2>/dev/null; echo $?)"
-expect_eq "argv[0] preserved" "sh" "$(run -- sh -c 'echo $0')"
-expect_eq "PATH unset uses default" "ok" "$(env -u PATH "$PLAYER" --unix "$SOCK" -- sh -c 'echo ok')"
-
-echo "# fd hygiene"
-# 9player passes inherited descriptors through untouched, so compare with what a
-# plain child of this script sees (the runner may itself hold extra fds).
-FD_LIST='ls /proc/self/fd | grep -v "^3$" | sort -n | tr "\n" " " | sed "s/ $//"'
-FD_BASE=$(sh -c "$FD_LIST")
-expect_eq "no extra fds in the program (unix)" "$FD_BASE" "$(run -- sh -c "$FD_LIST")"
-expect_eq "no extra fds in the program (spawn)" "$FD_BASE" "$("$TIMEOUT" 60 "$PLAYER" --spawn "$INTROSPECT --stdio" -- sh -c "$FD_LIST")"
-expect_eq "--fd transport does not leak into the program" "0 1 2" "$(python3 - "$PLAYER" "$SOCK" <<'EOF'
-import socket, subprocess, sys, os
-s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.connect(sys.argv[2])
-r = subprocess.run([sys.argv[1], "--fd", str(s.fileno()), "--", "sh", "-c",
- 'ls /proc/self/fd | grep -v "^3$" | sort -n | tr "\n" " " | sed "s/ $//"'],
- pass_fds=[s.fileno()], capture_output=True, text=True)
-print(r.stdout.strip())
-EOF
-)"
-
-echo "# signals"
-expect_eq "SIGTERM forwarded" "143" "$(run -- sh -c 'kill -TERM $PPID; sleep 5; echo alive' >/dev/null 2>&1; echo $?)"
-expect_eq "SIGHUP forwarded" "129" "$(run -- sh -c 'kill -HUP $PPID; sleep 5; echo alive' >/dev/null 2>&1; echo $?)"
-expect_eq "SIGINT to 9player is ignored while the child lives" "still-here" "$(run -- sh -c 'kill -INT $PPID; sleep 0.3; echo still-here')"
-# Ctrl-C from the tty must not kill the --spawn server (same process group).
-expect_eq "Ctrl-C on the tty leaves the --spawn server alive" "ok" "$(timeout 30 python3 - "$PLAYER" "$INTROSPECT" <<'EOF'
-import os, pty, sys, time, select
-P, I = sys.argv[1], sys.argv[2]
-prog = ["python3", "-c", """
-import os, signal, sys, time
-signal.signal(signal.SIGINT, lambda *a: None)
-m = os.environ['NINEPLAYER_MOUNT']
-open(m + '/build/optimize').read()
-sys.stdin.readline()
-try:
- open(m + '/build/optimize').read(); print('ok', flush=True)
-except Exception as e:
- print('mount dead:', e, flush=True)
-"""]
-pid, fd = pty.fork()
-if pid == 0:
- os.execv(P, [P, "--spawn", I + " --stdio", "--"] + prog)
-out = b""
-def rd(t):
- global out
- end = time.time() + t
- while time.time() < end:
- r, _, _ = select.select([fd], [], [], 0.1)
- if r:
- try: d = os.read(fd, 4096)
- except OSError: return
- if not d: return
- out += d
-rd(1.5); os.write(fd, b"\x03"); rd(0.7); os.write(fd, b"\n"); rd(3)
-os.waitpid(pid, 0)
-print(out.decode(errors="replace").replace("^C", "").strip().splitlines()[-1] if out.strip() else "no output")
-EOF
-)"
-# The --spawn server dying mid-session is reaped (no zombie) and does not end the session.
-OUT=$("$TIMEOUT" 60 "$PLAYER" --spawn "$INTROSPECT --stdio" -- sh -c 'srv=$(cat $NINEPLAYER_MOUNT/runtime/pid); kill -TERM $srv; sleep 0.5; st=$(ps -o stat= -p $srv 2>/dev/null); echo "${st:-gone}"; exit 5' 2>/dev/null); RC=$?
-expect_eq "server death mid-session: exit status still the child's, server reaped (no zombie)" "5 gone" "$RC $OUT"
-# A server that never answers: once the child is dead, SIGTERM must end 9player.
-cat >"$TMP/hang.py" <<'EOF'
-import struct, os, sys, time
-def rd(n):
- b = b""
- while len(b) < n:
- c = os.read(0, n - len(b))
- if not c: sys.exit(0)
- b += c
- return b
-while True:
- size, = struct.unpack("<I", rd(4)); body = rd(size - 4)
- typ, tag = struct.unpack("<BH", body[:3])
- if typ == 100:
- msize, = struct.unpack("<I", body[3:7]); v = b"9P2000"
- r = struct.pack("<BHI", 101, tag, msize) + struct.pack("<H", len(v)) + v
- elif typ == 104:
- r = struct.pack("<BH", 105, tag) + bytes([0x80]) + struct.pack("<IQ", 0, 0)
- else:
- time.sleep(3600)
- os.write(1, struct.pack("<I", 4 + len(r)) + r)
-EOF
-"$PLAYER" --spawn "python3 $TMP/hang.py" -- true 2>/dev/null &
-HP=$!
-sleep 1; kill -TERM $HP
-START=$(date +%s)
-for _ in $(seq 1 100); do kill -0 $HP 2>/dev/null || break; sleep 0.1; done
-if kill -0 $HP 2>/dev/null; then kill -KILL $HP; RC=hung; else wait $HP; RC=$?; fi
-expect_eq "hung server: one SIGTERM ends 9player once the child is dead (watchdog)" "143" "$RC"
-expect_eq "hung server: exit was prompt" "yes" "$([ $(( $(date +%s) - START )) -lt 8 ] && echo yes)"
-pkill -f "$TMP/hang.py" 2>/dev/null
-
-echo "# child/parent protocol"
-if command -v strace >/dev/null 2>&1 && strace -qq -e trace=none true 2>/dev/null; then
- expect_eq "child killed before handoff" "125" "$(strace -f -qq -e trace=unshare -e inject=unshare:signal=KILL -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>/dev/null; echo $?)"
- expect_contains "child killed before handoff: message" "child exited before reporting" "$(strace -f -qq -e trace=unshare -e inject=unshare:signal=KILL -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>&1)"
- expect_eq "status handoff fails" "125" "$(strace -f -qq -e trace=sendmsg -e inject=sendmsg:error=EPIPE -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>/dev/null; echo $?)"
- # recvmsg skipped (returns 1 without the fd): the child must be killed, not exec'd onto a dead mount.
- OUT=$(strace -f -qq -e trace=recvmsg -e inject=recvmsg:retval=1:when=1 -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- sh -c 'echo child-ran' 2>&1; echo "rc=$?")
- expect_contains "truncated fd handoff: child not exec'd" "rc=125" "$OUT"
- expect_eq "truncated fd handoff: program never ran" "no" "$(case "$OUT" in *child-ran*) echo yes;; *) echo no;; esac)"
- expect_contains "fuse mount failure is reported" "mount fuse: EPERM" "$(strace -f -qq -e trace=mount -e inject=mount:error=EPERM:when=2 -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" --mount "$TMP/mp" -- true 2>&1)"
-else
- echo "skip - strace unavailable (child failure injection)"
-fi
-expect_contains "fork failure is reported" "fork: E" "$(python3 -c "
-import resource, os
-resource.setrlimit(resource.RLIMIT_NPROC, (1, 1))
-os.execv('$PLAYER', ['$PLAYER', '--unix', '$SOCK', '--', 'true'])" 2>&1)"
-
-echo "# mountpoint policy"
-ln -s /nonexistent "$TMP/dangling"
-expect_contains "dangling symlink mountpoint" "dangling symlink" "$(run --mount "$TMP/dangling" -- true 2>&1)"
-expect_eq "refuse to shadow / via /proc/self/root" "125" "$(run --mount /proc/self/root/x9p -- true 2>/dev/null; echo $?)"
-expect_contains "refuse to shadow / via /proc/self/root: message" "refusing to shadow /" "$(run --mount /proc/self/root/x9p -- true 2>&1)"
-expect_eq "refuse to shadow under /proc" "125" "$(run --mount /proc/self/fd/x9p -- true 2>/dev/null; echo $?)"
-if [ "$(ls -A /usr/lib | wc -l)" -gt 4096 ]; then
- expect_contains "parent with >4096 entries refused" "more than 4096 entries" "$(run --mount /usr/lib/x9p -- true 2>&1)"
-else
- echo "skip - no root-owned directory with >4096 entries"
-fi
-expect_eq "shadowed /run keeps its entries" "$(ls -A /run | sort | tr '\n' ' ')" "$(run --mount /run/x9p -- sh -c 'ls -A /run | grep -v "^x9p$" | sort | tr "\n" " "')"
-expect_eq "mountpoint with spaces" "ok" "$(mkdir -p "$TMP/with space" && run --mount "$TMP/with space" -- sh -c '[ -f "$NINEPLAYER_MOUNT/README" ] && echo ok')"
-expect_eq "mountpoint is a file" "125" "$(run --mount "$TMP/nx" -- true 2>/dev/null; echo $?)"
-
-echo "# leaks"
-for i in $(seq 1 30); do run -- sh -c 'cat $NINEPLAYER_MOUNT/build/optimize >/dev/null' 2>/dev/null; done
-BG=(); for i in $(seq 1 10); do ( run -- sh -c 'cat $NINEPLAYER_MOUNT/build/optimize >/dev/null' 2>/dev/null ) & BG+=($!); done; wait "${BG[@]}" # not a bare wait: that would also wait for the server
-sleep 0.3
-expect_eq "no stray 9player processes" "" "$(pgrep -f "^$PLAYER " | tr '\n' ' ')"
-expect_eq "no stray --stdio servers" "" "$(pgrep -f "$INTROSPECT --stdio" | tr '\n' ' ')"
-expect_eq "host mount table untouched" "same" "$([ "$MI_BEFORE" = "$(grep -v " $TMP" /proc/self/mountinfo | sort)" ] && echo same || echo changed)"
-
-echo
-echo "passed=$PASSED failed=$FAILED"
-[ "$FAILED" -eq 0 ]