diff options
| author | Craig Jennings <c@cjennings.net> | 2026-04-26 01:09:01 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-04-26 01:09:01 -0500 |
| commit | 1a261b0c220903c8bb628e7f2b94cf75a843f688 (patch) | |
| tree | a4d6d7db9148bb450738818ef59755a5e2eebee3 | |
| parent | 6b65665eca8a4b36b0b6eae4d761fccd7b4c1fc4 (diff) | |
| download | archangel-1a261b0c220903c8bb628e7f2b94cf75a843f688.tar.gz archangel-1a261b0c220903c8bb628e7f2b94cf75a843f688.zip | |
test: expand bats coverage across installer modules
Added unit tests for `disk.sh`, `btrfs.sh`, the archangel monolith's `gather_input` unattended branch, and filled gap cases in `config.sh`. The suite grew from 71 to 110 tests.
`installer/lib/disk.sh` was completely uncovered. New `tests/unit/test_disk.bats` covers the four pure partition-path helpers (`get_efi_partition`, `get_root_partition`, `get_efi_partitions`, `get_root_partitions`) across SATA, virtio, and NVMe inputs, mixed arrays, and the empty-input behavior. Side-effecting functions in the same file (sgdisk, mkfs.fat, partprobe, and fzf wrappers) stay deliberately VM-tested.
`installer/lib/btrfs.sh` had no bats coverage. New `tests/unit/test_btrfs.bats` covers `get_luks_devices`, the only pure helper in the file. It pins the asymmetric naming convention where the first device gets the bare `LUKS_MAPPER_NAME` and subsequent devices append the index.
The archangel monolith was un-source-able for tests because its top-level code created a /tmp log file and redirected stdout via `exec > >(tee...)`, plus called `main "$@"` unconditionally at the bottom. I extracted the logging setup into an `init_logging` function called from `main`, and wrapped the main call in a `[[ "${BASH_SOURCE[0]}" == "${0}" ]]` guard. Sourcing the script now loads function definitions silently, with no log file and no banner. Running it directly works exactly as before. Verified both paths.
That refactor unlocks `tests/unit/test_archangel.bats`, which covers `gather_input` in unattended mode. Required-field validation for HOSTNAME, TIMEZONE, ROOT_PASSWORD, and DISKS. Optional-field defaulting (FILESYSTEM to zfs, LOCALE to en_US.UTF-8, KEYMAP to us, ENABLE_SSH to yes). Filesystem-specific encryption checks (ZFS_PASSPHRASE required when not NO_ENCRYPT, same for LUKS_PASSPHRASE on Btrfs). Filesystem validity. RAID_LEVEL defaulting for multi-disk installs. The interactive branch stays out of scope per the testing-strategy policy.
`tests/unit/test_config.bats` got five gap tests: `check_config` when CONFIG_FILE is set, `validate_config` against a non-block-device entry (e.g. /dev/null) and a missing path, and `parse_args` accepting `--color` and `--config-file` together in either order.
`testing-strategy.org` got an expanded "What bats does NOT cover" section. The doc previously named six tools (mkfs, cryptsetup, zpool create, pacstrap, arch-chroot, grub-install). The new list adds sgdisk, partprobe, blkid, mkfs.fat, mkfs.btrfs, snapper, efibootmgr, mount, umount, findmnt, mountpoint, and fzf. It also names the conditions (root needed, real /dev or /sys state) that make a function VM-only. The coverage table at the top now lists the three new test files.
No behavior change in production code. The init_logging extraction preserves the existing log path and banner format byte-for-byte.
| -rwxr-xr-x | installer/archangel | 31 | ||||
| -rw-r--r-- | testing-strategy.org | 36 | ||||
| -rw-r--r-- | tests/unit/test_archangel.bats | 214 | ||||
| -rw-r--r-- | tests/unit/test_btrfs.bats | 56 | ||||
| -rw-r--r-- | tests/unit/test_config.bats | 47 | ||||
| -rw-r--r-- | tests/unit/test_disk.bats | 132 |
6 files changed, 501 insertions, 15 deletions
diff --git a/installer/archangel b/installer/archangel index a40710b..b2fa2c0 100755 --- a/installer/archangel +++ b/installer/archangel @@ -61,16 +61,21 @@ RAID_LEVEL="" # "", "mirror", "raidz1", "raidz2", "raidz3" ENABLE_SSH="yes" # Enable SSH with root login (default yes for headless) NO_ENCRYPT="no" # Skip ZFS encryption (for testing only) -# Logging -LOGFILE="/tmp/archangel-$(date +'%Y-%m-%d-%H-%M-%S').log" -exec > >(tee -a "$LOGFILE") 2>&1 +# Logging — initialized by init_logging() at install time only. Sourcing +# this script (e.g. from a bats test) loads the function definitions +# without redirecting stdout or creating a log file in /tmp. +LOGFILE="" -# Log header with timestamp -echo "" -echo "================================================================================" -echo "archangel started @ $(date +'%Y-%m-%d %H:%M:%S')" -echo "================================================================================" -echo "" +init_logging() { + LOGFILE="/tmp/archangel-$(date +'%Y-%m-%d-%H-%M-%S').log" + exec > >(tee -a "$LOGFILE") 2>&1 + + echo "" + echo "================================================================================" + echo "archangel started @ $(date +'%Y-%m-%d %H:%M:%S')" + echo "================================================================================" + echo "" +} # Output functions now in lib/common.sh # Config functions now in lib/config.sh @@ -1385,6 +1390,7 @@ print_btrfs_summary() { ############################# main() { + init_logging parse_args "$@" preflight_checks check_config @@ -1490,4 +1496,9 @@ install_btrfs() { trap 'error "Installation interrupted!"' INT TERM -main "$@" +# Only invoke main when archangel is executed directly. When sourced +# (e.g. from a bats test) the function definitions load but main does +# not run, so tests can exercise individual helpers like gather_input. +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi diff --git a/testing-strategy.org b/testing-strategy.org index 4b948fd..0f81a3a 100644 --- a/testing-strategy.org +++ b/testing-strategy.org @@ -61,16 +61,42 @@ Current coverage lives in =tests/unit/=: | File | What it covers | |------+----------------| -| =test_common.bats= | =command_exists=, =require_command=, =info=/=warn=/=error=, =enable_color=, =log=, =prompt_password=, =pacstrap_packages= | +| =test_common.bats= | =command_exists=, =require_command=, =info=/=warn=/=error=, =enable_color=, =log=, =prompt_password=, =pacstrap_packages=, =install_dropin= | | =test_config.bats= | =parse_args=, =load_config=, =validate_config=, =check_config= | | =test_raid.bats= | =raid_valid_levels_for_count=, =raid_is_valid=, =raid_usable_bytes=, =raid_fault_tolerance= | +| =test_disk.bats= | =get_efi_partition=, =get_root_partition=, =get_efi_partitions=, =get_root_partitions= | +| =test_btrfs.bats= | =get_luks_devices= | +| =test_archangel.bats= | =gather_input= (unattended branch only) | ** What bats does NOT cover (deliberately) -Anything that shells out to =mkfs=, =cryptsetup=, =zpool create=, -=pacstrap=, =arch-chroot=, =grub-install=, or needs root. Those behaviors -only mean anything against real partitions on real (virtual) hardware and -belong in the VM integration tests below. +Anything that shells out to a tool whose behavior only means something +against real partitions, real hardware, or root privileges. Those +behaviors belong in the VM integration tests below. + +The full list of deliberately-skipped tools and conditions: + +- *Filesystem creation*: =mkfs.fat=, =mkfs.btrfs=, =mkfs.ext4=, generic =mkfs= +- *Encryption*: =cryptsetup= (LUKS), ZFS native encryption via =zfs create -O encryption= +- *ZFS pool / dataset operations*: =zpool create=, =zpool import=, =zfs create=, =zfs snapshot=, =zfs rollback= +- *Partitioning*: =sgdisk=, =partprobe=, =blkid= +- *Bootstrap and chroot*: =pacstrap=, =arch-chroot=, =genfstab= +- *Bootloader*: =grub-install=, =grub-mkconfig=, =efibootmgr= +- *Snapshot management*: =snapper=, =zfs-pre-snapshot= +- *Mount / unmount of real filesystems*: =mount=, =umount=, =findmnt=, + =mountpoint= +- *Interactive UI*: =fzf= (the helper-fallback path is also skipped because + the value is in the user-facing flow) +- Anything that needs =root= privileges to do its real work +- Anything that depends on real =/dev= or =/sys= state (=lsblk= against + actual disks, =/sys/block/= holders) + +Pure helpers adjacent to these tools — ones that compute values +without invoking the tool — are in scope for bats. =pacstrap_packages= +is the canonical example: it builds the package list that =pacstrap= +will install, but doesn't run pacstrap itself, so it's covered by +=test_common.bats=. Same pattern when a future helper parses a captured +fixture of a tool's output instead of running the tool live. ** Running diff --git a/tests/unit/test_archangel.bats b/tests/unit/test_archangel.bats new file mode 100644 index 0000000..c31cebb --- /dev/null +++ b/tests/unit/test_archangel.bats @@ -0,0 +1,214 @@ +#!/usr/bin/env bats +# Unit tests for the installer/archangel monolith. +# +# Coverage scope: gather_input() in unattended mode — the validation +# of required config values, defaulting of optional ones, and the +# filesystem-specific encryption checks. The interactive branch +# (everything reachable via `if [[ "$UNATTENDED" != true ]]`) is not +# unit-tested per the project's testing-strategy.org policy on +# fzf / arch-chroot / mkfs / cryptsetup wrappers. +# +# Sourcing archangel relies on the source-guard at the bottom of +# the script: when sourced, function definitions load but main is +# not called, init_logging is not run (so /tmp/archangel-*.log is +# not created), and the banner is not printed. + +setup() { + # shellcheck disable=SC1091 + source "${BATS_TEST_DIRNAME}/../../installer/archangel" + UNATTENDED=true +} + +############################# +# Required-field validation +############################# + +@test "gather_input unattended errors when HOSTNAME is missing" { + HOSTNAME="" + TIMEZONE=UTC + ROOT_PASSWORD=secret + SELECTED_DISKS=(/dev/sda) + NO_ENCRYPT=yes + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"HOSTNAME"* ]] +} + +@test "gather_input unattended errors when TIMEZONE is missing" { + HOSTNAME=h + TIMEZONE="" + ROOT_PASSWORD=secret + SELECTED_DISKS=(/dev/sda) + NO_ENCRYPT=yes + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"TIMEZONE"* ]] +} + +@test "gather_input unattended errors when ROOT_PASSWORD is missing" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD="" + SELECTED_DISKS=(/dev/sda) + NO_ENCRYPT=yes + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"ROOT_PASSWORD"* ]] +} + +@test "gather_input unattended errors when SELECTED_DISKS is empty" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=secret + SELECTED_DISKS=() + NO_ENCRYPT=yes + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"DISKS"* ]] +} + +############################# +# Optional-field defaults +############################# + +@test "gather_input unattended defaults FILESYSTEM to zfs when empty" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM="" + NO_ENCRYPT=yes + gather_input >/dev/null + [ "$FILESYSTEM" = "zfs" ] +} + +@test "gather_input unattended defaults LOCALE, KEYMAP, ENABLE_SSH when empty" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=zfs + NO_ENCRYPT=yes + LOCALE="" + KEYMAP="" + ENABLE_SSH="" + gather_input >/dev/null + [ "$LOCALE" = "en_US.UTF-8" ] + [ "$KEYMAP" = "us" ] + [ "$ENABLE_SSH" = "yes" ] +} + +@test "gather_input unattended preserves explicit non-default values" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=btrfs + NO_ENCRYPT=yes + LOCALE="en_GB.UTF-8" + KEYMAP="dvorak" + ENABLE_SSH="no" + gather_input >/dev/null + [ "$FILESYSTEM" = "btrfs" ] + [ "$LOCALE" = "en_GB.UTF-8" ] + [ "$KEYMAP" = "dvorak" ] + [ "$ENABLE_SSH" = "no" ] +} + +############################# +# Filesystem-specific encryption validation +############################# + +@test "gather_input unattended errors when ZFS without ZFS_PASSPHRASE and encryption on" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=zfs + NO_ENCRYPT=no + ZFS_PASSPHRASE="" + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"ZFS_PASSPHRASE"* ]] +} + +@test "gather_input unattended errors when Btrfs without LUKS_PASSPHRASE and encryption on" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=btrfs + NO_ENCRYPT=no + LUKS_PASSPHRASE="" + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"LUKS_PASSPHRASE"* ]] +} + +@test "gather_input unattended accepts ZFS with NO_ENCRYPT=yes and no passphrase" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=zfs + NO_ENCRYPT=yes + ZFS_PASSPHRASE="" + run gather_input + [ "$status" -eq 0 ] +} + +############################# +# Filesystem validity +############################# + +@test "gather_input unattended errors when FILESYSTEM is neither zfs nor btrfs" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=ext4 + NO_ENCRYPT=yes + run gather_input + [ "$status" -eq 1 ] + [[ "$output" == *"Invalid FILESYSTEM"* ]] +} + +############################# +# RAID-level defaulting +############################# + +@test "gather_input unattended defaults RAID_LEVEL to mirror for multi-disk install" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda /dev/sdb) + FILESYSTEM=zfs + NO_ENCRYPT=yes + RAID_LEVEL="" + gather_input >/dev/null + [ "$RAID_LEVEL" = "mirror" ] +} + +@test "gather_input unattended preserves an explicit RAID_LEVEL on multi-disk install" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda /dev/sdb /dev/sdc) + FILESYSTEM=zfs + NO_ENCRYPT=yes + RAID_LEVEL="raidz1" + gather_input >/dev/null + [ "$RAID_LEVEL" = "raidz1" ] +} + +@test "gather_input unattended leaves RAID_LEVEL empty for single-disk install" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/sda) + FILESYSTEM=zfs + NO_ENCRYPT=yes + RAID_LEVEL="" + gather_input >/dev/null + [ -z "$RAID_LEVEL" ] +} diff --git a/tests/unit/test_btrfs.bats b/tests/unit/test_btrfs.bats new file mode 100644 index 0000000..890bba2 --- /dev/null +++ b/tests/unit/test_btrfs.bats @@ -0,0 +1,56 @@ +#!/usr/bin/env bats +# Unit tests for installer/lib/btrfs.sh +# +# Coverage scope: pure helpers only. Most of btrfs.sh wraps cryptsetup, +# mkfs.btrfs, snapper, grub-install, and arch-chroot — all deliberately +# VM-tested per the project's testing-strategy.org policy. Only +# get_luks_devices is covered here. + +setup() { + # shellcheck disable=SC1091 + source "${BATS_TEST_DIRNAME}/../../installer/lib/common.sh" + # shellcheck disable=SC1091 + source "${BATS_TEST_DIRNAME}/../../installer/lib/btrfs.sh" +} + +############################# +# get_luks_devices +############################# +# Asymmetric naming: index 0 uses the bare LUKS_MAPPER_NAME (no +# suffix), subsequent indices append the index. Tests pin both the +# bare-first behavior and the suffix-on-rest behavior. + +@test "get_luks_devices: count=1 emits the bare-named mapper device" { + LUKS_MAPPER_NAME="cryptroot" + run get_luks_devices 1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/mapper/cryptroot" ] +} + +@test "get_luks_devices: count=3 emits bare name + suffixed entries" { + LUKS_MAPPER_NAME="cryptroot" + run get_luks_devices 3 + [ "$status" -eq 0 ] + [ "$output" = "/dev/mapper/cryptroot /dev/mapper/cryptroot1 /dev/mapper/cryptroot2" ] +} + +@test "get_luks_devices: count=0 emits empty output" { + LUKS_MAPPER_NAME="cryptroot" + run get_luks_devices 0 + [ "$status" -eq 0 ] + [ -z "$output" ] +} + +@test "get_luks_devices: count=5 emits five entries with correct suffix progression" { + LUKS_MAPPER_NAME="cryptroot" + run get_luks_devices 5 + [ "$status" -eq 0 ] + [ "$output" = "/dev/mapper/cryptroot /dev/mapper/cryptroot1 /dev/mapper/cryptroot2 /dev/mapper/cryptroot3 /dev/mapper/cryptroot4" ] +} + +@test "get_luks_devices: non-numeric count is treated as zero (no crash)" { + LUKS_MAPPER_NAME="cryptroot" + run get_luks_devices abc + [ "$status" -eq 0 ] + [ -z "$output" ] +} diff --git a/tests/unit/test_config.bats b/tests/unit/test_config.bats index c659fdc..c0c4e42 100644 --- a/tests/unit/test_config.bats +++ b/tests/unit/test_config.bats @@ -109,3 +109,50 @@ EOF [ "$status" -eq 0 ] [ -z "$output" ] } + +@test "check_config loads the config file when CONFIG_FILE is set" { + local tmp + tmp=$(mktemp) + cat >"$tmp" <<'EOF' +HOSTNAME=fromcheckconfig +TIMEZONE=UTC +EOF + CONFIG_FILE="$tmp" + check_config + [ "$HOSTNAME" = "fromcheckconfig" ] + [ "$TIMEZONE" = "UTC" ] + [ "$UNATTENDED" = "true" ] + rm -f "$tmp" +} + +@test "validate_config flags an existing-but-not-block path in SELECTED_DISKS" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/dev/null) + run validate_config + [ "$status" -eq 1 ] + [[ "$output" == *"Disk not found: /dev/null"* ]] +} + +@test "validate_config flags a missing path in SELECTED_DISKS" { + HOSTNAME=h + TIMEZONE=UTC + ROOT_PASSWORD=x + SELECTED_DISKS=(/nonexistent/disk-xyz-42) + run validate_config + [ "$status" -eq 1 ] + [[ "$output" == *"Disk not found"* ]] +} + +@test "parse_args accepts --color and --config-file together (color first)" { + parse_args --color --config-file /tmp/foo.conf + [ "$CONFIG_FILE" = "/tmp/foo.conf" ] + [ -n "$RED" ] +} + +@test "parse_args accepts --config-file and --color together (config first)" { + parse_args --config-file /tmp/foo.conf --color + [ "$CONFIG_FILE" = "/tmp/foo.conf" ] + [ -n "$RED" ] +} diff --git a/tests/unit/test_disk.bats b/tests/unit/test_disk.bats new file mode 100644 index 0000000..f4e6929 --- /dev/null +++ b/tests/unit/test_disk.bats @@ -0,0 +1,132 @@ +#!/usr/bin/env bats +# Unit tests for installer/lib/disk.sh +# +# Coverage scope: pure partition-path helpers only. Side-effecting +# functions (partition_disk, partition_disks, format_efi, +# format_efi_partitions, select_disks) wrap sgdisk / mkfs.fat / +# partprobe / fzf and are validated by VM integration per the +# project's testing-strategy.org policy. + +setup() { + # shellcheck disable=SC1091 + source "${BATS_TEST_DIRNAME}/../../installer/lib/common.sh" + # shellcheck disable=SC1091 + source "${BATS_TEST_DIRNAME}/../../installer/lib/disk.sh" +} + +############################# +# get_efi_partition +############################# + +@test "get_efi_partition: SATA disk gets numeric suffix 1" { + run get_efi_partition /dev/sda + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda1" ] +} + +@test "get_efi_partition: virtio disk gets numeric suffix 1" { + run get_efi_partition /dev/vda + [ "$status" -eq 0 ] + [ "$output" = "/dev/vda1" ] +} + +@test "get_efi_partition: NVMe disk gets p1 suffix" { + run get_efi_partition /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme0n1p1" ] +} + +@test "get_efi_partition: NVMe with multi-namespace disk gets p1 suffix" { + run get_efi_partition /dev/nvme1n2 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme1n2p1" ] +} + +@test "get_efi_partition: empty input documents current behavior" { + # Empty input misses the nvme regex so the bare-suffix branch fires, + # producing just "1". This pins the existing behavior; the function + # is never called with empty in production but pinning catches a + # change in suffix-rule logic. + run get_efi_partition "" + [ "$status" -eq 0 ] + [ "$output" = "1" ] +} + +############################# +# get_root_partition +############################# + +@test "get_root_partition: SATA disk gets numeric suffix 2" { + run get_root_partition /dev/sda + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda2" ] +} + +@test "get_root_partition: NVMe disk gets p2 suffix" { + run get_root_partition /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme0n1p2" ] +} + +@test "get_root_partition: virtio disk gets numeric suffix 2" { + run get_root_partition /dev/vdb + [ "$status" -eq 0 ] + [ "$output" = "/dev/vdb2" ] +} + +############################# +# get_efi_partitions +############################# + +@test "get_efi_partitions: two SATA disks emit two suffixed partitions" { + run get_efi_partitions /dev/sda /dev/sdb + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda1 +/dev/sdb1" ] +} + +@test "get_efi_partitions: mixed SATA + NVMe gets correct per-disk suffix" { + run get_efi_partitions /dev/sda /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda1 +/dev/nvme0n1p1" ] +} + +@test "get_efi_partitions: single NVMe disk emits one p1 partition" { + run get_efi_partitions /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme0n1p1" ] +} + +@test "get_efi_partitions: three disks emit three lines in order" { + run get_efi_partitions /dev/sda /dev/sdb /dev/sdc + [ "$status" -eq 0 ] + local lines + lines=$(echo "$output" | wc -l) + [ "$lines" -eq 3 ] +} + +############################# +# get_root_partitions +############################# + +@test "get_root_partitions: two SATA disks emit two suffix-2 partitions" { + run get_root_partitions /dev/sda /dev/sdb + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda2 +/dev/sdb2" ] +} + +@test "get_root_partitions: mixed SATA + NVMe applies correct suffix per disk" { + run get_root_partitions /dev/sda /dev/nvme0n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/sda2 +/dev/nvme0n1p2" ] +} + +@test "get_root_partitions: NVMe array gets p2 suffix on each entry" { + run get_root_partitions /dev/nvme0n1 /dev/nvme1n1 + [ "$status" -eq 0 ] + [ "$output" = "/dev/nvme0n1p2 +/dev/nvme1n1p2" ] +} |
