ci: add isolated ARM proposal test lane
This commit is contained in:
@@ -2,19 +2,78 @@
|
||||
# Compile normally; execute unit tests away from real wallets, service buses,
|
||||
# container storage, processes and networking. Never silently fall back to host.
|
||||
set -euo pipefail
|
||||
REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
command -v systemd-run >/dev/null
|
||||
command -v unshare >/dev/null
|
||||
command -v setpriv >/dev/null
|
||||
sudo -n true || { echo 'Isolated backend tests require noninteractive sudo for systemd namespaces.' >&2; exit 1; }
|
||||
metadata=$(mktemp)
|
||||
trap 'rm -f "$metadata"' EXIT
|
||||
SCRIPT_REPO=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
REPO=${ARCHY_TEST_REPO:-$SCRIPT_REPO}
|
||||
REPO=$(cd "$REPO" && pwd)
|
||||
case "${ARCHY_TEST_PACKAGE:-archipelago}" in
|
||||
archipelago) test_target=(-p archipelago --bin archipelago) ;;
|
||||
archipelago-publishing-tests) test_target=(-p archipelago-publishing-tests --lib) ;;
|
||||
archipelago-container) test_target=(-p archipelago-container --lib) ;;
|
||||
*) echo 'Unsupported isolated test package' >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
if [[ ${ARCHY_TEST_ISOLATOR:-systemd} == podman ]]; then
|
||||
command -v podman >/dev/null
|
||||
image=${ARCHY_TEST_IMAGE:?ARCHY_TEST_IMAGE is required for podman isolation}
|
||||
cargo_home=${ARCHY_TEST_CARGO_HOME:?ARCHY_TEST_CARGO_HOME is required for podman isolation}
|
||||
cargo_home=$(mkdir -p "$cargo_home" && cd "$cargo_home" && pwd)
|
||||
artifacts=$(mktemp -d)
|
||||
trap 'rm -rf -- "$artifacts"' EXIT
|
||||
|
||||
podman run --rm \
|
||||
--cpus="${ARCHY_TEST_CPUS:-4}" --memory="${ARCHY_TEST_MEMORY:-4g}" --pids-limit=2048 \
|
||||
--cap-drop=all --security-opt=no-new-privileges --read-only \
|
||||
--tmpfs /tmp:rw,size=512m --tmpfs /root:rw,size=512m \
|
||||
--network=pasta --env CARGO_HOME=/cargo-home \
|
||||
--volume "$cargo_home:/cargo-home:rw,Z" \
|
||||
--volume "$REPO:/workspace:rw,Z" --volume "$artifacts:/artifacts:rw,Z" \
|
||||
--workdir /workspace \
|
||||
"$image" \
|
||||
cargo test --manifest-path core/Cargo.toml "${test_target[@]}" \
|
||||
--locked --no-run --message-format=json \
|
||||
--config 'profile.test.package.archipelago.opt-level=0' \
|
||||
--config 'profile.test.package.archipelago.debug=0' \
|
||||
>"$artifacts/metadata"
|
||||
|
||||
executable=$(python3 - "$artifacts/metadata" <<'PY'
|
||||
import json,sys
|
||||
found=[]
|
||||
for line in open(sys.argv[1]):
|
||||
try: item=json.loads(line)
|
||||
except json.JSONDecodeError: continue
|
||||
if item.get('reason')=='compiler-artifact' and item.get('profile',{}).get('test') and item.get('executable'):
|
||||
found.append(item['executable'])
|
||||
assert len(found)==1, f'Expected one unit test executable, got {len(found)}'
|
||||
print(found[0])
|
||||
PY
|
||||
)
|
||||
case "$executable" in
|
||||
/workspace/*) ;;
|
||||
*) echo 'Compiled test executable escaped the workspace' >&2; exit 1 ;;
|
||||
esac
|
||||
|
||||
podman run --rm \
|
||||
--cpus="${ARCHY_TEST_CPUS:-4}" --memory="${ARCHY_TEST_MEMORY:-4g}" --pids-limit=1024 \
|
||||
--cap-drop=all --security-opt=no-new-privileges --read-only \
|
||||
--tmpfs /tmp:rw,size=512m --tmpfs /run:rw,size=64m \
|
||||
--tmpfs /var/lib/archipelago:rw,size=256m --tmpfs /var/lib/containers:rw,size=256m \
|
||||
--tmpfs /root:rw,size=64m --network=none \
|
||||
--volume "$REPO:/workspace:ro,Z" --workdir /workspace/core \
|
||||
--env ARCHY_TEST_ISOLATED=1 \
|
||||
"$image" "$executable" --test-threads="${ARCHY_TEST_THREADS:-4}" "$@"
|
||||
exit
|
||||
fi
|
||||
|
||||
[[ ${ARCHY_TEST_ISOLATOR:-systemd} == systemd ]] || {
|
||||
echo 'ARCHY_TEST_ISOLATOR must be systemd or podman' >&2
|
||||
exit 2
|
||||
}
|
||||
command -v systemd-run >/dev/null
|
||||
command -v unshare >/dev/null
|
||||
command -v setpriv >/dev/null
|
||||
sudo -n true || { echo 'Isolated backend tests require noninteractive sudo for systemd namespaces.' >&2; exit 1; }
|
||||
metadata=$(mktemp)
|
||||
trap 'rm -f "$metadata"' EXIT
|
||||
if ! cargo test --manifest-path "$REPO/core/Cargo.toml" "${test_target[@]}" \
|
||||
--locked --no-run --message-format=json --config 'profile.test.package.archipelago.opt-level=0' --config 'profile.test.package.archipelago.debug=0' > "$metadata"; then
|
||||
python3 - "$metadata" <<'PYDIAG'
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
|
||||
ROOT = pathlib.Path(__file__).resolve().parents[2]
|
||||
RUNNER = ROOT / "scripts" / "test-backend-isolated.sh"
|
||||
|
||||
|
||||
class BackendIsolationRunnerTests(unittest.TestCase):
|
||||
def test_podman_mode_separates_build_and_networkless_execution(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
temp = pathlib.Path(temporary)
|
||||
fake_bin = temp / "bin"
|
||||
fake_bin.mkdir()
|
||||
log = temp / "podman.log"
|
||||
podman = fake_bin / "podman"
|
||||
podman.write_text(
|
||||
"#!/usr/bin/env bash\n"
|
||||
"printf '%s\\0' \"$@\" >> \"$PODMAN_TEST_LOG\"\n"
|
||||
"printf '\\n' >> \"$PODMAN_TEST_LOG\"\n"
|
||||
"if [[ \" $* \" == *' cargo test '* ]]; then\n"
|
||||
" printf '%s\\n' '{\"reason\":\"compiler-artifact\",\"profile\":{\"test\":true},\"executable\":\"/workspace/core/target/debug/archy-test\"}'\n"
|
||||
"fi\n"
|
||||
)
|
||||
podman.chmod(0o700)
|
||||
cargo_home = temp / "cargo"
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"PATH": f"{fake_bin}:{env['PATH']}",
|
||||
"PODMAN_TEST_LOG": str(log),
|
||||
"ARCHY_TEST_ISOLATOR": "podman",
|
||||
"ARCHY_TEST_REPO": str(ROOT),
|
||||
"ARCHY_TEST_IMAGE": "example.invalid/ci@sha256:test",
|
||||
"ARCHY_TEST_CARGO_HOME": str(cargo_home),
|
||||
}
|
||||
)
|
||||
|
||||
subprocess.run([str(RUNNER)], env=env, check=True)
|
||||
invocations = [
|
||||
line.replace("\0", " ") for line in log.read_text().splitlines()
|
||||
]
|
||||
self.assertEqual(len(invocations), 2)
|
||||
self.assertIn("--network=pasta", invocations[0])
|
||||
self.assertIn(f"{ROOT}:/workspace:rw,Z", invocations[0])
|
||||
self.assertIn("--network=none", invocations[1])
|
||||
self.assertIn(f"{ROOT}:/workspace:ro,Z", invocations[1])
|
||||
self.assertIn("ARCHY_TEST_ISOLATED=1", invocations[1])
|
||||
|
||||
def test_unknown_isolator_fails_closed(self):
|
||||
result = subprocess.run(
|
||||
[str(RUNNER)],
|
||||
env={**os.environ, "ARCHY_TEST_ISOLATOR": "unknown"},
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
self.assertEqual(result.returncode, 2)
|
||||
self.assertIn("must be systemd or podman", result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user