arch-system/tests/prepare-disk/test.sh

132 lines
9.1 KiB
Bash
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# Copies and strict mocks only: no host block device or privileged command is touched.
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
command -v bwrap >/dev/null || { echo 'bwrap requis' >&2; exit 1; }
BASE="$(mktemp -d)"
trap 'rm -rf -- "$BASE"' EXIT
passes=0
fail() { printf 'ÉCHEC : %s\n' "$*" >&2; exit 1; }
new_case() {
case_dir="$(mktemp -d "$BASE/case.XXXXXXXX")"
mkdir -p "$case_dir"/{repo/scripts,bin,mnt,tmp/fixture,sys/dev/block,sys/firmware/efi,run/archiso,run/lock}
mkdir -p "$case_dir/run/archiso"/{bootmnt/airootfs,cowspace/upper,cowspace/work}
for mm in 8:16 8:17 8:18 8:20; do mkdir -p "$case_dir/sys/dev/block/$mm/holders"; done
cp "$ROOT/scripts/prepare-disk.sh" "$ROOT/scripts/prepare-disk.py" "$case_dir/repo/scripts/"
cp "$ROOT/tests/prepare-disk/mock-command.py" "$case_dir/mock-command.py"
for cmd in readlink lsblk findmnt swapon wipefs parted partprobe udevadm mkfs.fat mkfs.btrfs btrfs mount umount; do
ln -s /opt/mock-command.py "$case_dir/bin/$cmd"
done
for cmd in bash python3 dirname mkdir; do ln -s "/usr/bin/$cmd" "$case_dir/bin/$cmd"; done
printf '{"partitioned":false,"mounts":[]}' > "$case_dir/tmp/fixture/state.json"
: > "$case_dir/tmp/fixture/calls"
}
run_case() {
local label="$1"; shift
local mnt_args=(--bind "$case_dir/mnt" /mnt)
[[ "${CASE_MNT_LINK:-0}" != 1 ]] || mnt_args=(--symlink /tmp/fixture /mnt)
set +e
printf '%s\n' "${ANSWER:-}" | bwrap --unshare-user --unshare-pid --unshare-net --uid "${TEST_UID:-0}" --gid "${TEST_UID:-0}" \
--tmpfs / --ro-bind /usr /usr --symlink usr/lib /lib --symlink usr/lib /lib64 \
--symlink usr/bin /bin "${mnt_args[@]}" --bind "$case_dir/tmp" /tmp \
--bind "$case_dir/sys" /sys --bind "$case_dir/run" /run --tmpfs /opt \
--ro-bind "$case_dir/repo" /opt/repo --ro-bind "$case_dir/mock-command.py" /opt/mock-command.py \
--ro-bind "$case_dir/bin" /opt/bin --dev /dev --proc /proc \
--setenv PATH /opt/bin --setenv CASE_FAIL "${CASE_FAIL:-}" --setenv CASE_STYLE "${CASE_STYLE:-}" \
-- /opt/repo/scripts/prepare-disk.sh "$@" > "$case_dir/out" 2>&1
rc=$?
set -e
printf '[test] %s : rc=%s\n' "$label" "$rc"
}
no_mutation() { ! grep -Eq '^(wipefs|parted|partprobe|udevadm|mkfs\.fat|mkfs\.btrfs|btrfs|mount|umount) ' "$case_dir/tmp/fixture/calls" || fail 'mutation imprévue'; }
expect() { [[ "$rc" == "$1" ]] || fail "code $rc attendu $1: $(cat "$case_dir/out")"; }
new_case; run_case help --help; expect 0; no_mutation; ((passes+=1))
new_case; TEST_UID=1000 run_case dry --dry-run /dev/target; expect 0; no_mutation; ((passes+=1))
new_case; CASE_FAIL=overlay_live TEST_UID=1000 run_case dry_overlay --dry-run /dev/target
expect 0; no_mutation; ((passes+=1))
for scenario in wrong_type mounted_child mounted_disk source_target swap_partition swapfile mnt_occupied overlay_unmapped lsblk:inspection findmnt:inspection swapon:inspection; do
new_case; CASE_FAIL="$scenario" run_case "$scenario" /dev/target; expect 1; no_mutation; ((passes+=1))
done
new_case; touch "$case_dir/sys/dev/block/8:16/holders/dm-test"
run_case holder /dev/target; expect 1; grep -q 'holder actif' "$case_dir/out" || fail 'holder non diagnostiqué'; no_mutation; ((passes+=1))
new_case; rmdir "$case_dir/sys/dev/block/8:16/holders"
run_case holders_unreadable /dev/target; expect 1; grep -q 'holders illisibles' "$case_dir/out" || fail 'lecture holders ignorée'; no_mutation; ((passes+=1))
new_case; touch "$case_dir/mnt/old-data"
run_case mnt_data /dev/target; expect 1; grep -q '/mnt lié, absent ou non vide' "$case_dir/out" || fail 'données /mnt ignorées'; no_mutation; ((passes+=1))
new_case; CASE_MNT_LINK=1 run_case mnt_link /dev/target; expect 1
grep -q '/mnt lié, absent ou non vide' "$case_dir/out" || fail 'lien /mnt ignoré'; no_mutation; ((passes+=1))
new_case; rm "$case_dir/bin/parted"; ANSWER='ERASE /dev/target 8:16' run_case missing_dependency /dev/target
expect 1; grep -q 'dépendance absente : parted' "$case_dir/out" || fail 'dépendance non diagnostiquée'; no_mutation; ((passes+=1))
new_case; rm -r "$case_dir/run/archiso"; run_case no_live /dev/target
expect 1; grep -q 'live Arch UEFI' "$case_dir/out" || fail 'contexte live non vérifié'; no_mutation; ((passes+=1))
new_case; run_case partition /dev/target1; expect 1; no_mutation; ((passes+=1))
new_case; CASE_FAIL=source_target run_case alias_source /dev/alias; expect 1
grep -q 'disque ou descendant monté' "$case_dir/out" || fail 'alias source non protégé'; no_mutation; ((passes+=1))
new_case; CASE_FAIL=readlink run_case alias_missing /dev/alias; expect 1; no_mutation; ((passes+=1))
new_case; ANSWER=no run_case refusal /dev/target; expect 1; no_mutation; ((passes+=1))
new_case; run_case eof /dev/target; expect 1; no_mutation; ((passes+=1))
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL=identity_change run_case identity_change /dev/target
expect 1; grep -q 'identité changée' "$case_dir/out" || fail 'changement identité non bloqué'; no_mutation; ((passes+=1))
new_case; ANSWER='ERASE /dev/target 8:16' run_case nominal /dev/alias; expect 0
[[ "$(grep -c '^mount ' "$case_dir/tmp/fixture/calls")" == 7 ]] || fail 'montages incomplets'
[[ -d "$case_dir/mnt/@/.snapshots" ]] || fail 'sous-volume imbriqué absent'
((passes+=1))
new_case; CASE_STYLE=nvme ANSWER='ERASE /dev/nvme9n9 8:16' run_case nominal_nvme /dev/nvme9n9; expect 0
grep -q '^mkfs.fat -F 32 -n EFI /dev/nvme9n9p1$' "$case_dir/tmp/fixture/calls" || fail 'partition NVMe non vérifiée'
((passes+=1))
new_case; CASE_FAIL=overlay_live ANSWER='ERASE /dev/target 8:16' run_case nominal_overlay /dev/target
expect 0; ((passes+=1))
new_case; CASE_FAIL=canonical_subvol ANSWER='ERASE /dev/target 8:16' run_case canonical_subvol /dev/target
expect 0; ((passes+=1))
new_case
exec 9> "$case_dir/run/lock/arch-system-prepare-8_16.lock"
flock -n 9 || fail 'verrou fixture indisponible'
ANSWER='ERASE /dev/target 8:16' run_case concurrent /dev/target; expect 1
grep -q 'autre invocation coopérante' "$case_dir/out" || fail 'concurrence non bloquée'
no_mutation
exec 9>&-
((passes+=1))
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL=bad_partitions run_case bad_partitions /dev/target; expect 1
! grep -q '^mkfs' "$case_dir/tmp/fixture/calls" || fail 'formatage malgré partitions invalides'
((passes+=1))
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL=mkfs.btrfs run_case fail_mkfs /dev/target; expect 1
! grep -q '^mount ' "$case_dir/tmp/fixture/calls" || fail 'montage après formatage échoué'
((passes+=1))
for scenario in wipefs parted partprobe udevadm mkfs.fat btrfs; do
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL="$scenario" run_case "fail_$scenario" /dev/target; expect 1
grep -q "^$scenario " "$case_dir/tmp/fixture/calls" || fail "étape $scenario non atteinte"
grep -q "étape à vérifier" "$case_dir/out" || fail 'bilan d’échec absent'
case "$scenario" in
wipefs) ! grep -q '^parted ' "$case_dir/tmp/fixture/calls" || fail 'parted après wipefs échoué' ;;
parted) [[ "$(grep -c '^parted ' "$case_dir/tmp/fixture/calls")" == 1 ]] || fail 'parted poursuivi après échec' ;;
partprobe) ! grep -q '^udevadm ' "$case_dir/tmp/fixture/calls" || fail 'udev après partprobe échoué' ;;
udevadm) ! grep -q '^mkfs' "$case_dir/tmp/fixture/calls" || fail 'formatage après udev échoué' ;;
mkfs.fat) ! grep -q '^mkfs.btrfs ' "$case_dir/tmp/fixture/calls" || fail 'Btrfs après FAT32 échoué' ;;
btrfs) [[ "$(grep -c '^btrfs ' "$case_dir/tmp/fixture/calls")" == 1 ]] || fail 'sous-volume poursuivi après échec'
grep -q '^umount -- /mnt$' "$case_dir/tmp/fixture/calls" || fail 'montage top-level non nettoyé' ;;
esac
((passes+=1))
done
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL=snapshot_conflict run_case snapshot_conflict /dev/target; expect 1
grep -q 'subvolume destination already exists' "$case_dir/out" || fail 'précondition .snapshots non testée'
! grep -q '^mount -o' "$case_dir/tmp/fixture/calls" || fail 'montage final malgré conflit .snapshots'
((passes+=1))
for scenario in mount:third mount_cleanup_fail mount_replaced; do
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL="$scenario" run_case "$scenario" /dev/target; expect 1
if [[ "$scenario" == mount:third ]]; then
grep -q '^umount -- /mnt$' "$case_dir/tmp/fixture/calls" || fail 'nettoyage ciblé absent'
elif [[ "$scenario" == mount_cleanup_fail ]]; then
grep -q 'Montages restants à examiner : /mnt' "$case_dir/out" || fail 'échec nettoyage non signalé'
else
grep -q 'montage à examiner, non démonté : /mnt' "$case_dir/out" || fail 'montage remplacé démonté'
[[ "$(grep -c '^umount -- /mnt$' "$case_dir/tmp/fixture/calls")" == 1 ]] || fail 'montage remplacé démonté'
fi
((passes+=1))
done
for scenario in signal_INT signal_TERM; do
new_case; ANSWER='ERASE /dev/target 8:16' CASE_FAIL="$scenario" run_case "$scenario" /dev/target
[[ "$rc" == 130 || "$rc" == 143 ]] || fail "code interruption inattendu : $rc"
! grep -q '^mkfs' "$case_dir/tmp/fixture/calls" || fail 'formatage après interruption'
((passes+=1))
done
printf 'Résultat : %s scénarios confinés de préparation validés.\n' "$passes"