commit 71047ef6710aec9257e20f0c0e66d458a6d0f2d0
parent f97898ee3989f8bcd180c27a6efa2e44e748446b
Author: cowmonk <cowmonk@based.pt>
Date: Fri, 9 Oct 2026 12:48:59 -0700
isolate fixtures and cover control socket regressions
Diffstat:
| M | tests/Makefile | | | 33 | ++++++++++++++++++++------------- |
| A | tests/control.c | | | 60 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | tests/run.sh | | | 134 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- |
3 files changed, 172 insertions(+), 55 deletions(-)
diff --git a/tests/Makefile b/tests/Makefile
@@ -4,23 +4,30 @@
include ../config.mk
TOP = ..
-CC ?= cc
+TEST_DIR = sandbox
-all: serva-test svc-test
+all: $(TEST_DIR)/serva-test $(TEST_DIR)/svc-test $(TEST_DIR)/control-test
-serva-test: $(TOP)/serva.c $(TOP)/util.o
- $(CC) $(CFLAGS) -DSVC_DIR='"$(CURDIR)/ssv"' \
- -DSOCK_PATH='"$(CURDIR)/serva.sock"' \
- $(LDFLAGS) -o $@ $(TOP)/serva.c $(TOP)/util.o
+$(TEST_DIR)/serva-test: $(TOP)/serva.c $(TOP)/util.c $(TOP)/util.h Makefile $(TOP)/config.mk
+ mkdir -p "$(TEST_DIR)"
+ $(CC) $(CPPFLAGS) $(CFLAGS) -DSVC_DIR='"$(TEST_DIR)/ssv"' \
+ -DSOCK_PATH='"$(TEST_DIR)/serva.sock"' \
+ $(LDFLAGS) -o $@ $(TOP)/serva.c $(TOP)/util.c $(LDLIBS)
-svc-test: $(TOP)/svc.c $(TOP)/util.o
- $(CC) $(CFLAGS) -DSOCK_PATH='"$(CURDIR)/serva.sock"' \
- $(LDFLAGS) -o $@ $(TOP)/svc.c $(TOP)/util.o
+$(TEST_DIR)/svc-test: $(TOP)/svc.c $(TOP)/util.c $(TOP)/util.h $(TOP)/arg.h Makefile $(TOP)/config.mk
+ mkdir -p "$(TEST_DIR)"
+ $(CC) $(CPPFLAGS) $(CFLAGS) -DSOCK_PATH='"$(TEST_DIR)/serva.sock"' \
+ $(LDFLAGS) -o $@ $(TOP)/svc.c $(TOP)/util.c $(LDLIBS)
-$(TOP)/util.o: $(TOP)/util.c $(TOP)/util.h
- cd $(TOP) && $(MAKE) util.o
+$(TEST_DIR)/control-test: control.c Makefile $(TOP)/config.mk
+ mkdir -p "$(TEST_DIR)"
+ $(CC) $(CPPFLAGS) $(CFLAGS) -DSOCK_PATH='"$(TEST_DIR)/serva.sock"' \
+ $(LDFLAGS) -o $@ control.c $(LDLIBS)
+
+check:
+ MAKE='$(MAKE)' ./run.sh
clean:
rm -f serva-test svc-test serva.log
-
-.PHONY: all clean run-tests
+ rm -f sandbox/serva-test sandbox/svc-test sandbox/control-test
+ rm -f sandbox/*/serva-test sandbox/*/svc-test sandbox/*/control-test
diff --git a/tests/control.c b/tests/control.c
@@ -0,0 +1,60 @@
+#ifndef _POSIX_C_SOURCE
+#define _POSIX_C_SOURCE 200809L
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ struct sockaddr_un addr;
+ struct stat st;
+ struct sigaction sa;
+
+ if (argc == 1) {
+ for (fd = 3; fd < 256; fd++) {
+ errno = 0;
+ if (fcntl(fd, F_GETFD) >= 0 || errno != EBADF) {
+ printf("inherited descriptor: %d\n", fd);
+ return 1;
+ }
+ }
+ if (sigaction(SIGPIPE, NULL, &sa) < 0 || sa.sa_handler != SIG_DFL) {
+ puts("SIGPIPE disposition was not restored");
+ return 1;
+ }
+ puts("OK: no supervisor descriptors inherited");
+ return 0;
+ }
+ if (argc != 2)
+ return 1;
+ if (strcmp(argv[1], "-mode") == 0)
+ return stat(SOCK_PATH, &st) < 0 || (st.st_mode & 0777) != 0600;
+ if (strcmp(argv[1], "-disconnect") != 0)
+ return 1;
+
+ fd = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (fd < 0)
+ return 1;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ if (strlen(SOCK_PATH) >= sizeof(addr.sun_path))
+ return 1;
+ memcpy(addr.sun_path, SOCK_PATH, sizeof(SOCK_PATH));
+ if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
+ return 1;
+ if (write(fd, "s\n", 2) != 2)
+ return 1;
+ shutdown(fd, SHUT_RDWR);
+ close(fd);
+ return 0;
+}
diff --git a/tests/run.sh b/tests/run.sh
@@ -1,16 +1,19 @@
#!/bin/sh
# tests/run.sh: integration test for serva + svc
#
-# Boots a sandboxed serva against ./ssv and drives it with svc-test.
+# Copies ./ssv into a sandbox and drives serva with svc-test.
# No root needed: everything lives under tests/.
set -u
cd "$(dirname "$0")"
-SOCK="$PWD/serva.sock"
-SERVA=./serva-test
-SVC=./svc-test
-LOG=serva.log
+SANDBOX="$PWD/sandbox/$$"
+SOCK="$SANDBOX/serva.sock"
+SERVA="$SANDBOX/serva-test"
+SVC="$SANDBOX/svc-test"
+LOG="$SANDBOX/serva.log"
+CONTROL_PROBE="$SANDBOX/control-test"
+export CONTROL_PROBE
PASS=0
FAIL=0
@@ -19,58 +22,58 @@ ok() { PASS=$((PASS + 1)); say "ok - $1"; }
bad() { FAIL=$((FAIL + 1)); say "FAIL - $1"; }
cleanup() {
- [ -n "${SERVA_PID:-}" ] && kill "$SERVA_PID" 2>/dev/null
- wait 2>/dev/null
+ if [ -n "${SERVA_PID:-}" ]; then
+ kill "$SERVA_PID" 2>/dev/null
+ wait "$SERVA_PID" 2>/dev/null
+ fi
rm -f "$SOCK"
}
-trap cleanup EXIT
+trap cleanup 0
+trap 'exit 1' HUP INT TERM
# --- setup sandbox -----------------------------------------------------------
-rm -rf ssv
-mkdir -p ssv/boot/crashme ssv/default/sleeper ssv/default/oneshot ssv/default/depender/need
-
-cat > ssv/boot/crashme/run <<'EOF'
-#!/bin/sh
-echo "crashme starting"
-exit 42
-EOF
-
-cat > ssv/default/sleeper/run <<'EOF'
-#!/bin/sh
-echo "sleeper starting"
-trap 'exit 0' TERM
-while :; do sleep 1; done
-EOF
+umask 077
+mkdir -p "$PWD/sandbox" || exit 1
+mkdir "$SANDBOX" || exit 1
+cp -R ssv "$SANDBOX/ssv" || exit 1
+
+for i in 1 2 3 4 5 6 7 8; do
+ service="$SANDBOX/ssv/default/status$i"
+ mkdir -p "$service/need" "$service/after" || exit 1
+ cp ssv/default/oneshot/run "$service/run" || exit 1
+ touch "$service/down"
+ for j in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do
+ dep=$(printf 'dep%02d_%057d' "$j" 0)
+ touch "$service/need/$dep" "$service/after/$dep"
+ done
+done
-cat > ssv/default/oneshot/run <<'EOF'
+mkdir "$SANDBOX/ssv/default/fdprobe" || exit 1
+cat > "$SANDBOX/ssv/default/fdprobe/run" <<'EOF'
#!/bin/sh
-echo "oneshot ran"
-touch "$PWD.marker" 2>/dev/null || true
-exit 0
+"$CONTROL_PROBE" >probe.out
+exec sleep 30
EOF
-touch ssv/default/oneshot/once
-
-cat > ssv/default/depender/run <<'EOF'
+cat > "$SANDBOX/ssv/default/fdprobe/log" <<'EOF'
#!/bin/sh
-trap 'exit 0' TERM
-while :; do sleep 1; done
+"$CONTROL_PROBE" >logger.out
+exec cat >/dev/null
EOF
-touch ssv/default/depender/need/sleeper
-
-chmod +x ssv/*/*/run
+touch "$SANDBOX/ssv/default/fdprobe/down" "$SANDBOX/ssv/default/fdprobe/once"
+chmod +x "$SANDBOX/ssv/default/fdprobe/run" "$SANDBOX/ssv/default/fdprobe/log"
# --- build + launch ----------------------------------------------------------
-make -s all || { say "build failed"; exit 1; }
+"${MAKE:-make}" -s TEST_DIR="$SANDBOX" all || { say "build failed"; exit 1; }
-"$SERVA" >"$LOG" 2>&1 &
+(umask 000; exec "$SERVA") >"$LOG" 2>&1 &
SERVA_PID=$!
for i in 1 2 3 4 5 6 7 8 9 10; do
[ -S "$SOCK" ] && break
- sleep 0.2
+ sleep 1
done
[ -S "$SOCK" ] || { bad "serva socket never appeared"; exit 1; }
-sleep 1 # let services start (and crashme crash once)
+sleep 2 # let services start and crashme restart
# --- tests -------------------------------------------------------------------
out=$("$SVC" -s)
@@ -108,7 +111,7 @@ case $out in
*) bad "oneshot should be DONE: $out" ;;
esac
-out=$("$SVC" -d sleeper 2>&1) && sleep 0.5 && out=$("$SVC" -s sleeper)
+out=$("$SVC" -d sleeper 2>&1) && sleep 1 && out=$("$SVC" -s sleeper)
case $out in
*DOWN*) ok "svc -d brings sleeper down" ;;
*) bad "sleeper should be DOWN after svc -d: $out" ;;
@@ -120,7 +123,7 @@ case $out in
*) bad "depender should be WAIT after sleeper down: $out" ;;
esac
-"$SVC" -u sleeper >/dev/null && sleep 0.5
+"$SVC" -u sleeper >/dev/null && sleep 1
out=$("$SVC" -s depender)
case $out in
*RUN*) ok "depender restarts when dep comes back" ;;
@@ -133,12 +136,59 @@ case $out in
*) bad "expected ERR for unknown service: $out" ;;
esac
-out=$("$SVC" -r sleeper 2>&1) && sleep 0.5
+out=$("$SVC" -r sleeper 2>&1) && sleep 1
runs=$("$SVC" -s sleeper | sed -n 's/.*runs=\([0-9]*\).*/\1/p')
[ "${runs:-0}" -ge 2 ] && ok "svc -r restarts sleeper (runs=$runs)" \
|| bad "svc -r did not restart sleeper: runs=${runs:-?}"
+out=$("$SVC" -s)
+rows=$(printf '%s\n' "$out" | sed -n '/default\/status[1-8]/p' | wc -l)
+[ "$rows" -eq 8 ] && ok "large status response includes every service" \
+ || bad "large status response lost services: rows=$rows"
+
+longname=$(printf '%05000d' 0)
+if out=$("$SVC" -u "$longname" 2>&1); then
+ bad "oversized service name should fail"
+else
+ case $out in
+ *'command too long'*) ok "oversized service name is rejected safely" ;;
+ *) bad "unexpected error for oversized service name: $out" ;;
+ esac
+fi
+
+out=$("$SVC" -s sleeper depender)
+case $out in
+*default/sleeper*RUN*default/depender*RUN*) ok "svc handles multiple service names" ;;
+*) bad "svc did not return both services: $out" ;;
+esac
+
+if "$SVC" -s nosuchsvc >/dev/null; then
+ bad "unknown service should return failure"
+else
+ ok "unknown service returns failure"
+fi
+
+"$CONTROL_PROBE" -mode && ok "control socket has mode 0600 under umask 000" \
+ || bad "control socket permissions are too broad"
+
+"$SVC" -u fdprobe >/dev/null
+sleep 1
+for file in probe.out logger.out; do
+ out=$(cat "$SANDBOX/ssv/default/fdprobe/$file" 2>/dev/null)
+ case $out in
+ 'OK: no supervisor descriptors inherited') ok "$file: supervisor descriptors closed and SIGPIPE restored" ;;
+ *) bad "$file: unexpected inherited state: $out" ;;
+ esac
+done
+
+if "$CONTROL_PROBE" -disconnect && "$SVC" -s sleeper >/dev/null; then
+ ok "serva survives a client disconnect during its response"
+else
+ bad "serva died after a client disconnected"
+fi
+
# --- summary -----------------------------------------------------------------
say ""
say "passed: $PASS failed: $FAIL"
+say "test files: $SANDBOX"
[ "$FAIL" -eq 0 ]