Files
restic-backup-docker/test/features.sh
T
Tobias KaupatandClaude Opus 5.5 31f06b5d9b test: add container test suites and run them in CI (#111)
Adds structure, compat and feature-gated container test suites (test/run.sh), runs them in CI, and gates image publishing on a green test run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 14:46:42 +02:00

173 lines
6.7 KiB
Bash

#!/usr/bin/env bash
# Feature suite.
#
# Each block is guarded by a detection check against the built image. On master
# every block reports SKIP. As soon as the corresponding pull request is rebased
# onto master, the feature is detected and its tests start running - the PR
# author does not have to write test code for the basics.
#
# After merging a feature, add its gate name to test/required-features.txt.
# From then on a missing feature is a failure instead of a skip.
set -u
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=test/lib.sh
. "${SCRIPT_DIR}/lib.sh"
trap stop_container EXIT
echo "=== feature: stream backup (RESTIC_STREAM) ==="
# Pull request #109 - back up the output of a command via `restic backup --stdin`.
if require_feature stream "PR #109"; then
start_container stream \
-e RESTIC_STREAM=1 \
-e "RESTIC_STREAM_CMD=echo hello-stream" \
-e RESTIC_STREAM_FILENAME=stream.txt
if wait_for_start; then
stream_log="$(in_container /bin/backup 2>&1)"
assert_contains "$stream_log" "Backup Successful" \
"stream backup succeeds"
assert_eq "1" "$(snapshot_count)" \
"stream backup creates exactly one snapshot"
in_container restic restore latest --target /tmp/restore >/dev/null 2>&1
restored="$(in_container sh -c 'cat /tmp/restore/stream.txt 2>/dev/null' 2>&1)"
assert_eq "hello-stream" "$(echo "$restored" | tr -d '\r')" \
"stream content is stored under RESTIC_STREAM_FILENAME"
else
fail "stream backup container starts"
fi
stop_container
# Blocker 1 of PR #109: the stream branch must honour RESTIC_JOB_ARGS, the
# same way the file backup does. --host is used because it is visible in
# the snapshot metadata of a stdin backup.
start_container stream-jobargs \
-e RESTIC_STREAM=1 \
-e "RESTIC_STREAM_CMD=echo hello-stream" \
-e RESTIC_STREAM_FILENAME=stream.txt \
-e "RESTIC_JOB_ARGS=--host=jobargshost"
if wait_for_start; then
in_container /bin/backup >/dev/null 2>&1
# Match on the hostname field itself: restic's own --host filter only
# returns snapshots whose hostname equals the value, so the string
# cannot be satisfied by "jobargshost" appearing in some other field.
# Both directions are checked so the filter is known to discriminate.
assert_eq "1" "$(in_container sh -c \
'restic snapshots --host jobargshost --json 2>/dev/null | grep -o "\"short_id\"" | wc -l' | tr -d ' \r')" \
"RESTIC_JOB_ARGS sets the host of the stream backup snapshot"
assert_eq "0" "$(in_container sh -c \
'restic snapshots --host some-other-host --json 2>/dev/null | grep -o "\"short_id\"" | wc -l' | tr -d ' \r')" \
"the host filter does not match an unrelated hostname"
else
fail "stream RESTIC_JOB_ARGS container starts"
fi
stop_container
# A half-configured stream must not look like a successful backup.
start_container stream-misconfigured -e RESTIC_STREAM=1
if wait_for_start; then
misconfig_log="$(in_container /bin/backup 2>&1)"
assert_not_contains "$misconfig_log" "Backup Successful" \
"incomplete stream configuration does not report success"
assert_eq "0" "$(snapshot_count)" \
"incomplete stream configuration creates no snapshot"
else
fail "stream misconfiguration container starts"
fi
stop_container
# The classic path must keep working while the feature is available.
start_container stream-disabled
if wait_for_start; then
write_file /data/hello.txt "backup me"
in_container /bin/backup >/dev/null 2>&1
assert_eq "1" "$(snapshot_count)" \
"file backup still works while the stream feature is present"
else
fail "stream-disabled container starts"
fi
stop_container
fi
echo
echo "=== feature: docker cli ==="
# Pull request #99 - talk to the host docker daemon from hooks.
if require_feature docker-cli "PR #99"; then
docker_version="$(docker run --rm --entrypoint docker "$IMAGE" --version 2>&1)"
assert_contains "$docker_version" "Docker version" \
"docker cli is usable inside the image"
fi
echo
echo "=== feature: bash ==="
# Pull request #99 - allow bash syntax in pre/post hooks.
if require_feature bash "PR #99"; then
start_container bash-hooks
if wait_for_start; then
write_file /hooks/pre-backup.sh '#!/bin/bash
arr=(one two)
if [[ "${arr[1]}" == "two" ]]; then echo "BASH-HOOK-RAN"; fi
'
in_container chmod +x /hooks/pre-backup.sh
write_file /data/hello.txt "backup me"
bash_log="$(in_container /bin/backup 2>&1)"
assert_contains "$bash_log" "BASH-HOOK-RAN" \
"a hook with a bash shebang and bash syntax runs"
else
fail "bash hook container starts"
fi
stop_container
fi
echo
echo "=== feature: msmtp mail relay ==="
# Pull request #84 - send notification mails through msmtp.
if require_feature msmtp "PR #84"; then
sendmail_target="$(docker run --rm --entrypoint sh "$IMAGE" -c \
'readlink -f /usr/sbin/sendmail' 2>&1 | tr -d '\r')"
assert_contains "$sendmail_target" "msmtp" \
"sendmail is provided by msmtp"
# The config has to be in place when the container starts, entry.sh copies
# it. Re-running entry.sh in a live container would start a second crond.
start_container_with_file msmtp-config /config/msmtprc 'defaults
account default
host localhost
'
if wait_for_start; then
if in_container test -f /etc/msmtprc; then
pass "msmtp config from /config is copied to /etc/msmtprc"
mode="$(in_container stat -c '%a' /etc/msmtprc 2>&1 | tr -d '\r')"
assert_eq "600" "$mode" \
"copied msmtp config is not world readable"
else
fail "msmtp config from /config is copied to /etc/msmtprc" "file is missing"
fi
else
fail "msmtp config container starts"
fi
stop_container
# Users who configured mail before msmtp existed must not lose their backups.
start_container msmtp-missing-config \
-e "MAILX_ARGS=-r from@example.com -s subject to@example.com"
if wait_for_start; then
write_file /data/hello.txt "backup me"
mail_log="$(in_container /bin/backup 2>&1)"
assert_contains "$mail_log" "Backup Successful" \
"backup succeeds with MAILX_ARGS set but no msmtp config"
assert_eq "1" "$(snapshot_count)" \
"snapshot is created even when the mail relay is unconfigured"
else
fail "msmtp missing-config container starts"
fi
stop_container
fi
summary "features"