mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-04 02:57:27 +09:00
The layer below moved the unit suite to bats(1), which enforces `set -e` but not `set -u`. cozytest.sh ran every test body under `set -eu -x`, so without this a test that reads an unset variable silently sees an empty string and passes. There is no runner-level way to inject it, which is why this needs a per-file load rather than a Makefile flag. Two mechanisms were measured and rejected: a setup() defined in --setup-suite-file does not reach test files at all, and SHELLOPTS=nounset does reach them but is exported, so it also applies to the hack/*.sh scripts the tests exercise as subprocesses -- changing the behaviour of the code under test rather than the tests, and dropping two tests outright. `set -u` inside setup() is a shell option rather than an exported one, so it stays scoped to the test body. hack/test_helper.bash therefore defines the shared setup(), and all 32 unit files load it. The restoration is mutation-checked, not assumed: a canary test reading an unset variable aborts with "unbound variable" with the load in place and passes vacuously without it. Suite is 328/328 green either way, so nothing in the tree was relying on the laxer behaviour. The headers are the other half. 27 of the 32 files documented the cozytest contract in prose -- "there is no bats `run` or `$status`", "setup()/teardown() are not honored", "Run with: hack/cozytest.sh ...", title-sanitization rules from the awk parser, and instructions to keep `}` off column 0 so the parser would not truncate a test. Every one of those statements is now false or vacuous, and a false comment about the harness is worse than none: the next author reads "setup() is not honored" and hand-rolls per-test cleanup that the runner would have done. Where a constraint merely stopped binding rather than reversing, the comment says so instead of deleting the line -- the column-0 heredoc indentation and the manual status capture in migration-seaweedfs-db-adopt are both kept, now marked belt-and-braces. Claims that survived the move are left alone: `! cmd` really is still vacuous under `set -e`, since bash suppresses errexit for a negated command exactly as dash did. hack/seaweedfs-naming-audit.bats gains a note that its subject is POSIX sh executed by /bin/sh but sourced into bats' bash, so shellcheck's shell=sh directive is now the only thing guarding that gap. Refs: #3453 Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
408 lines
13 KiB
Bash
408 lines
13 KiB
Bash
#!/usr/bin/env bats
|
|
# -----------------------------------------------------------------------------
|
|
# Unit tests for hack/check-host-runtime.sh
|
|
#
|
|
# The script warns when a standalone containerd.service or docker.service is
|
|
# active alongside the embedded k3s runtime on Ubuntu hosts running the
|
|
# cozystack "generic" variant. Warnings go to stderr; exit code is always 0.
|
|
#
|
|
# Test strategy: each test builds its own temporary stub directory and prepends
|
|
# it to PATH to inject a fake `systemctl` (and optionally `du`) binary. The
|
|
# script itself honors a small set of COZYSTACK_PREFLIGHT_* environment
|
|
# variables to redirect socket/dir probes into the stub tree, so tests do not
|
|
# need root privileges or a real systemd host.
|
|
#
|
|
# Each test installs a `trap 'rm -rf "$STUB_DIR"' EXIT` immediately after
|
|
# creating the stub dir so cleanup runs even when an assertion fails mid-test
|
|
# under `set -e`. bats runs each @test in its own subshell, so traps scope per
|
|
# test and do not leak across tests.
|
|
#
|
|
# Tests are otherwise self-contained: each provisions its own stub dir rather
|
|
# than sharing a setup()/teardown() pair. hack/test_helper.bash supplies the
|
|
# only shared setup(), restoring the `set -u` cozytest.sh applied before #3453.
|
|
#
|
|
# Run with: bats hack/check-host-runtime.bats
|
|
# -----------------------------------------------------------------------------
|
|
|
|
load test_helper
|
|
|
|
@test "clean host with no runtime services exits silently" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker1.sock $STUB_DIR/missing-docker2.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh >"$STUB_DIR/stdout" 2>"$STDERR_FILE"
|
|
|
|
[ ! -s "$STDERR_FILE" ]
|
|
[ ! -s "$STUB_DIR/stdout" ]
|
|
}
|
|
|
|
@test "standalone containerd service active prints warning" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "containerd.service" ]; then
|
|
echo active
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-containerd"
|
|
echo dummy >"$STUB_DIR/var-lib-containerd/dummy"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/var-lib-containerd" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
grep -q 'standalone containerd.service' "$STDERR_FILE"
|
|
if grep -q 'standalone docker.service' "$STDERR_FILE"; then
|
|
echo "unexpected docker warning found:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
# HINT line must name only the detected service, not advise disabling
|
|
# docker.service when only containerd.service is running. The sudo
|
|
# prefix is also required — without it the command silently no-ops
|
|
# for a non-root operator, so the prefix is part of the contract.
|
|
grep -q 'sudo systemctl disable --now containerd.service' "$STDERR_FILE"
|
|
if grep -q 'systemctl disable --now.*docker' "$STDERR_FILE"; then
|
|
echo "HINT unexpectedly mentions docker:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "standalone docker service active prints warning" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "docker.service" ]; then
|
|
echo active
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-docker"
|
|
echo dummy >"$STUB_DIR/var-lib-docker/dummy"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/var-lib-docker" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
grep -q 'standalone docker.service' "$STDERR_FILE"
|
|
if grep -q 'standalone containerd.service' "$STDERR_FILE"; then
|
|
echo "unexpected containerd warning found:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
# HINT line must name only the detected service. As in the
|
|
# containerd test, the sudo prefix is part of the contract.
|
|
grep -q 'sudo systemctl disable --now docker.service' "$STDERR_FILE"
|
|
if grep -q 'systemctl disable --now.*containerd' "$STDERR_FILE"; then
|
|
echo "HINT unexpectedly mentions containerd:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "both services active prints two warnings and the HINT block" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ]; then
|
|
case "$2" in
|
|
containerd.service|docker.service) echo active; exit 0 ;;
|
|
esac
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-containerd" "$STUB_DIR/var-lib-docker"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
# Capture exit code explicitly: the script contract says exit 0
|
|
# unconditionally (warning, not blocker). `set -e` in the test
|
|
# function body would already fail on a nonzero exit, but an
|
|
# explicit status check locks in the contract and makes a
|
|
# regression show up as "expected 0, got N" rather than as a
|
|
# generic test failure.
|
|
status=0
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/var-lib-containerd" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/var-lib-docker" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE" || status=$?
|
|
|
|
[ "$status" -eq 0 ]
|
|
grep -q 'standalone containerd.service' "$STDERR_FILE"
|
|
grep -q 'standalone docker.service' "$STDERR_FILE"
|
|
# HINT block must fire whenever warnings exist; otherwise a future silent
|
|
# removal of the HINT would go unnoticed. When both services fire the HINT
|
|
# must list both in a single sudo systemctl disable invocation — the sudo
|
|
# prefix is as important as the systemctl verb, otherwise the operator
|
|
# would be told to run it as a non-root user and quietly fail.
|
|
grep -q 'HINT:' "$STDERR_FILE"
|
|
grep -q 'sudo systemctl disable --now containerd.service docker.service' "$STDERR_FILE"
|
|
}
|
|
|
|
@test "failing du does not suppress the containerd warning" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "containerd.service" ]; then
|
|
echo active
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
cat >"$STUB_DIR/du" <<'DUEOF'
|
|
#!/bin/sh
|
|
exit 1
|
|
DUEOF
|
|
chmod +x "$STUB_DIR/du"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-containerd"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/var-lib-containerd" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
grep -q 'standalone containerd.service' "$STDERR_FILE"
|
|
}
|
|
|
|
@test "containerd socket fallback fires when systemctl is unavailable" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
# The script uses `[ -e "$sock" ]`, not `[ -S ... ]`, so a regular
|
|
# file is a valid stand-in for a unix socket in tests. This also
|
|
# removes any optional runtime dependency on python3 and makes the
|
|
# test unconditional on every CI runner.
|
|
SOCK="$STUB_DIR/containerd.sock"
|
|
touch "$SOCK"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_PREFLIGHT_FORCE_NO_SYSTEMCTL=1 \
|
|
COZYSTACK_CONTAINERD_SOCKET="$SOCK" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
grep -q 'standalone containerd.service' "$STDERR_FILE"
|
|
}
|
|
|
|
@test "docker socket fallback fires when systemctl is unavailable" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
SOCK="$STUB_DIR/docker.sock"
|
|
touch "$SOCK"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_PREFLIGHT_FORCE_NO_SYSTEMCTL=1 \
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$SOCK" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
grep -q 'standalone docker.service' "$STDERR_FILE"
|
|
if grep -q 'standalone containerd.service' "$STDERR_FILE"; then
|
|
echo "unexpected containerd warning found:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "clean host without systemctl exits silently" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_PREFLIGHT_FORCE_NO_SYSTEMCTL=1 \
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker1.sock $STUB_DIR/missing-docker2.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
bash hack/check-host-runtime.sh >"$STUB_DIR/stdout" 2>"$STDERR_FILE"
|
|
|
|
[ ! -s "$STDERR_FILE" ]
|
|
[ ! -s "$STUB_DIR/stdout" ]
|
|
}
|
|
|
|
@test "docker service plus socket still emits exactly one warning" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "docker.service" ]; then
|
|
echo active
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
SOCK="$STUB_DIR/docker.sock"
|
|
touch "$SOCK"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-docker"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$SOCK" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/var-lib-docker" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
count=$(grep -c 'standalone docker.service' "$STDERR_FILE")
|
|
if [ "$count" != "1" ]; then
|
|
echo "expected exactly one docker warning, got $count" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "docker socket paths with glob chars do not expand" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
# Create two directories that a naive `for sock in $PATHS` loop
|
|
# would glob-expand and treat as existing "sockets". With the
|
|
# array-based parsing the literal path "$STUB_DIR/var-lib-*" does
|
|
# not exist and no warning must fire.
|
|
mkdir -p "$STUB_DIR/var-lib-docker" "$STUB_DIR/var-lib-containerd"
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$STUB_DIR/missing-containerd.sock" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/var-lib-*" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/missing-containerd-dir" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
if grep -q 'standalone docker.service' "$STDERR_FILE"; then
|
|
echo "glob pattern expanded — docker warning should not fire:" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
@test "containerd service plus socket still emits exactly one warning" {
|
|
STUB_DIR=$(mktemp -d)
|
|
trap 'rm -rf "$STUB_DIR"' EXIT
|
|
|
|
cat >"$STUB_DIR/systemctl" <<'STUBEOF'
|
|
#!/bin/sh
|
|
if [ "$1" = "--version" ]; then
|
|
echo "systemd stub"
|
|
exit 0
|
|
fi
|
|
if [ "$1" = "is-active" ] && [ "$2" = "containerd.service" ]; then
|
|
echo active
|
|
exit 0
|
|
fi
|
|
exit 1
|
|
STUBEOF
|
|
chmod +x "$STUB_DIR/systemctl"
|
|
|
|
# The script uses `[ -e "$sock" ]`, not `[ -S ... ]`, so a regular
|
|
# file is a valid stand-in for a unix socket in tests. This also
|
|
# removes any optional runtime dependency on python3 and makes the
|
|
# test unconditional on every CI runner.
|
|
SOCK="$STUB_DIR/containerd.sock"
|
|
touch "$SOCK"
|
|
|
|
mkdir -p "$STUB_DIR/var-lib-containerd"
|
|
|
|
STDERR_FILE="$STUB_DIR/stderr"
|
|
COZYSTACK_CONTAINERD_SOCKET="$SOCK" \
|
|
COZYSTACK_DOCKER_SOCKET_PATHS="$STUB_DIR/missing-docker.sock" \
|
|
COZYSTACK_CONTAINERD_DIR="$STUB_DIR/var-lib-containerd" \
|
|
COZYSTACK_DOCKER_DIR="$STUB_DIR/missing-docker-dir" \
|
|
PATH="$STUB_DIR:$PATH" \
|
|
bash hack/check-host-runtime.sh 2>"$STDERR_FILE"
|
|
|
|
count=$(grep -c 'standalone containerd.service' "$STDERR_FILE")
|
|
if [ "$count" != "1" ]; then
|
|
echo "expected exactly one containerd warning, got $count" >&2
|
|
cat "$STDERR_FILE" >&2
|
|
exit 1
|
|
fi
|
|
}
|