#!/usr/bin/env bats # Unit tests for parse-hcn.sh # Tests the improved HCN dracut module (renamed from 99hcnmgr to 99hcn) # Test fixture location FIXTURE_DIR="$(cd "$(dirname "$BATS_TEST_FILENAME")" && pwd)" SCRIPT_PATH="${FIXTURE_DIR}/../../live-root/usr/lib/dracut/modules.d/99hcn/parse-hcn.sh" # Mock directories MOCK_PROC="${FIXTURE_DIR}/proc" MOCK_SYS="${FIXTURE_DIR}/sys" MOCK_NM_INITRD_DIR="${FIXTURE_DIR}/nm-initrd-generator-connections" MOCK_HCN_DIR="${FIXTURE_DIR}/hcn-connections" # Helper functions for testing setup_file() { # Create a temporary directory for test runs export BATS_TMPDIR="${BATS_TEST_TMPDIR:-/tmp}/hcn-tests" mkdir -p "$BATS_TMPDIR" } teardown_file() { # Clean up temporary directory [ -d "$BATS_TMPDIR" ] && rm -rf "$BATS_TMPDIR" } setup() { # Create test-specific temporary directory TEST_WORK_DIR="$(mktemp -d "$BATS_TMPDIR/test.XXXXXX")" export TEST_WORK_DIR # Mock directories for each test export MOCK_RUN_DIR="$TEST_WORK_DIR/run" export MOCK_ETC_DIR="$TEST_WORK_DIR/etc" mkdir -p "$MOCK_RUN_DIR/NetworkManager/system-connections" mkdir -p "$MOCK_ETC_DIR/cmdline.d" } teardown() { # Clean up test-specific directory [ -d "$TEST_WORK_DIR" ] && rm -rf "$TEST_WORK_DIR" } # Source helper functions from the script load_script_functions() { # Extract and source only the helper functions from parse-hcn.sh # This allows us to test individual functions source <(sed -n '/^gkeyfile_get()/,/^}/p' "$SCRIPT_PATH") source <(sed -n '/^gkeyfile_has()/,/^}/p' "$SCRIPT_PATH") source <(sed -n '/^gkeyfile_set()/,/^}/p' "$SCRIPT_PATH") source <(sed -n '/^xdump4()/,/^}/p' "$SCRIPT_PATH") source <(sed -n '/^get_mac()/,/^}/p' "$SCRIPT_PATH") source <(sed -n '/^parse_nm_connection()/,/^}/p' "$SCRIPT_PATH") } # Mock dracut library functions info() { echo "INFO: $*" >&2; } warn() { echo "WARN: $*" >&2; } # getargs now supports multiple values for rd.hcn.ip and rd.hcn.route getargs() { # Convert dots and dashes to underscores for variable name local var_name="MOCK_GETARGS_$(echo "$1" | tr '.-' '__')" eval echo "\$$var_name" } strstr() { [ "${1#*"$2"*}" != "$1" ]; } export -f info warn getargs strstr # ======================================== # Test: xdump4 helper function # ======================================== @test "xdump4: reads 4-byte HCN ID correctly" { load_script_functions result=$(xdump4 "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ibm,hcn-id") [ "$result" = "333e80f5" ] } @test "xdump4: reads PCI HCN ID correctly" { load_script_functions result=$(xdump4 "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ibm,hcn-id") [ "$result" = "333e80f5" ] } # ======================================== # Test: get_mac helper function # ======================================== @test "get_mac: extracts MAC address from vnic device" { load_script_functions result=$(get_mac "$MOCK_PROC/device-tree/vdevice/vnic@30000006") [ "$result" = "2e:7a:32:2d:3d:06" ] } @test "get_mac: extracts MAC address from PCI device" { load_script_functions result=$(get_mac "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0") [ "$result" = "2e:7a:30:83:f5:00" ] } @test "get_mac: returns empty for missing MAC file" { load_script_functions result=$(get_mac "$TEST_WORK_DIR/nonexistent") [ -z "$result" ] } # ======================================== # Test: gkeyfile helper functions # ======================================== @test "gkeyfile_get: retrieves correct value from section" { load_script_functions local test_file="$TEST_WORK_DIR/test_get.nmconnection" cat << 'EOF' > "$test_file" [connection] id=env6 uuid=c201ab5d-802d-4b7e-ae5c-2be903eab719 type=ethernet controller=58bf4ee6-2df6-48e1-93c4-84ab96db479b [ethernet] cloned-mac-address=00:11:22:33:44:55 EOF [ "$(gkeyfile_get "$test_file" connection id)" = "env6" ] [ "$(gkeyfile_get "$test_file" connection controller)" = "58bf4ee6-2df6-48e1-93c4-84ab96db479b" ] [ "$(gkeyfile_get "$test_file" ethernet cloned-mac-address)" = "00:11:22:33:44:55" ] [ -z "$(gkeyfile_get "$test_file" connection non_existent_key)" ] } @test "gkeyfile_has: checks key existence in section" { load_script_functions local test_file="$TEST_WORK_DIR/test_has.nmconnection" cat << 'EOF' > "$test_file" [connection] id=env6 uuid=c201ab5d-802d-4b7e-ae5c-2be903eab719 type=ethernet [ethernet] EOF run gkeyfile_has "$test_file" connection id [ "$status" -eq 0 ] run gkeyfile_has "$test_file" connection uuid [ "$status" -eq 0 ] run gkeyfile_has "$test_file" connection non_existent_key [ "$status" -ne 0 ] run gkeyfile_has "$test_file" ethernet id [ "$status" -ne 0 ] } @test "gkeyfile_set: updates existing key or appends new key" { load_script_functions local test_file="$TEST_WORK_DIR/test_set.nmconnection" cat << 'EOF' > "$test_file" [connection] id=env6 uuid=c201ab5d-802d-4b7e-ae5c-2be903eab719 type=ethernet [ethernet] EOF # Update an existing key gkeyfile_set "$test_file" connection id "bond156efe56-env6" [ "$(gkeyfile_get "$test_file" connection id)" = "bond156efe56-env6" ] # Ensure no duplicates of "id" were created local id_count=$(grep -c "^id=" "$test_file") [ "$id_count" -eq 1 ] # Set a new key gkeyfile_set "$test_file" connection controller "bond156efe56" [ "$(gkeyfile_get "$test_file" connection controller)" = "bond156efe56" ] local controller_count=$(grep -c "^controller=" "$test_file") [ "$controller_count" -eq 1 ] } # ======================================== # Test: parse_nm_connection function # ======================================== @test "parse_nm_connection: parses bond connection correctly" { load_script_functions IFS='|' read -r id uuid ifname master controller mac < "$TEST_WORK_DIR/bin/test" << 'EOSCRIPT' #!/bin/sh case "$1" in -e) [ -e "$2" ] && exit 0 || exit 1 ;; *) /usr/bin/test "$@" ;; esac EOSCRIPT chmod +x "$TEST_WORK_DIR/bin/test" # Note: This test requires proper mocking of the wait loop # For now, we skip the actual execution due to complexity skip "Requires complex mocking of ofpathname and sysfs interaction" } # ======================================== # Test: fixup_nm_connections function # ======================================== @test "fixup_nm_connections: converts UUID controller to bond name" { # Copy nm-initrd-generator connections to test directory cp -r "$MOCK_NM_INITRD_DIR"/* "$MOCK_RUN_DIR/NetworkManager/system-connections/" # Set up mock environment export MAPPINGS="bond333e80f5 enP32775p1s0 2e7a3083f500 primary bond333e80f5 env6 2e7a322d3d06 none" export BOND_NAMES="bond333e80f5" # Source fixup function and its helpers source <(sed -n '/^gkeyfile_get()/,/^}/p; /^gkeyfile_has()/,/^}/p; /^gkeyfile_set()/,/^}/p; /^parse_nm_connection()/,/^}/p; /^fixup_nm_connections()/,/^}/p' "$SCRIPT_PATH") # Set mocked connection directory (now uses HCN_RUNTIME_CONN_DIR) export HCN_RUNTIME_CONN_DIR="$MOCK_RUN_DIR/NetworkManager/system-connections" # Execute fixup cd "$TEST_WORK_DIR" fixup_nm_connections # Verify the slave connection was renamed [ -f "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-enP32775p1s0.nmconnection" ] # Verify controller was changed from UUID to bond name grep -q "controller=bond333e80f5" "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-enP32775p1s0.nmconnection" } @test "fixup_nm_connections: renames connection ID to bond-slave format" { # Copy nm-initrd-generator connections to test directory cp -r "$MOCK_NM_INITRD_DIR"/* "$MOCK_RUN_DIR/NetworkManager/system-connections/" # Set up mock environment export MAPPINGS="bond333e80f5 enP32775p1s0 2e7a3083f500 primary bond333e80f5 env6 2e7a322d3d06 none" export BOND_NAMES="bond333e80f5" # Source fixup function and its helpers source <(sed -n '/^gkeyfile_get()/,/^}/p; /^gkeyfile_has()/,/^}/p; /^gkeyfile_set()/,/^}/p; /^parse_nm_connection()/,/^}/p; /^fixup_nm_connections()/,/^}/p' "$SCRIPT_PATH") # Set mocked connection directory (now uses HCN_RUNTIME_CONN_DIR) export HCN_RUNTIME_CONN_DIR="$MOCK_RUN_DIR/NetworkManager/system-connections" # Execute fixup cd "$TEST_WORK_DIR" fixup_nm_connections # Verify renamed files exist [ -f "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-enP32775p1s0.nmconnection" ] [ -f "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-env6.nmconnection" ] # Verify connection IDs were updated grep -q "^id=bond333e80f5-enP32775p1s0$" "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-enP32775p1s0.nmconnection" grep -q "^id=bond333e80f5-env6$" "$MOCK_RUN_DIR/NetworkManager/system-connections/bond333e80f5-env6.nmconnection" } @test "fixup_nm_connections: matches by MAC address when no interface name" { # Create a connection file without interface-name but with MAC cat > "$MOCK_RUN_DIR/NetworkManager/system-connections/test-slave.nmconnection" << 'EOF' [connection] id=test-slave uuid=test-uuid-1234 type=ethernet controller=e2a7b4b8-8297-4185-8421-ed32cf47db1c port-type=bond [ethernet] mac-address=2E:7A:30:83:F5:00 EOF # Also copy the bond master cp "$MOCK_NM_INITRD_DIR/bond333e80f5.nmconnection" "$MOCK_RUN_DIR/NetworkManager/system-connections/" export MAPPINGS="bond333e80f5 enP32775p1s0 2e7a3083f500 primary" export BOND_NAMES="bond333e80f5" source <(sed -n '/^parse_nm_connection()/,/^}/p' "$SCRIPT_PATH") # Test MAC parsing with our mock file IFS='|' read -r id uuid ifname master controller mac < "$TEST_WORK_DIR/test.nmconnection" << 'EOF' [connection] id=test uuid=test-uuid [ethernet] mac-address=2E:7A:30:83:F5:00 EOF IFS='|' read -r id uuid ifname master controller mac </dev/null) # Note: The fixture has "backup " with trailing space and null bytes # The actual value should be trimmed [ -n "$mode" ] } # ======================================== # Test: Performance improvements # ======================================== @test "performance: parse_nm_connection uses single awk invocation" { load_script_functions # Count the number of commands executed (rough measure) # The old implementation used 7+ sed calls # The new implementation uses 1 awk call # We verify this by checking the function only contains one awk call function_body=$(sed -n '/^parse_nm_connection()/,/^}/p' "$SCRIPT_PATH") awk_count=$(echo "$function_body" | grep -c "awk" || true) sed_count=$(echo "$function_body" | grep -c "sed -n" || true) # Should have 1 awk and 0 sed -n calls [ "$awk_count" -eq 1 ] [ "$sed_count" -eq 0 ] } # ======================================== # Test: POSIX compliance # ======================================== @test "POSIX compliance: script uses 'command -v' instead of 'type'" { # Verify the script uses POSIX-compliant 'command -v' grep_result=$(grep -n "command -v getargs" "$SCRIPT_PATH" || true) [ -n "$grep_result" ] # Should not use 'type' command type_usage=$(grep -n "type getargs" "$SCRIPT_PATH" || true) [ -z "$type_usage" ] } @test "POSIX compliance: no leading underscores in variable names" { # Variables in the script should not have leading underscores (old convention) # Check for the current clean style # The script should use 'dev' not '_dev', etc. # We verify by checking the get_dev_hcn function function_body=$(sed -n '/^get_dev_hcn()/,/^}/p' "$SCRIPT_PATH") # Should use clean variable names echo "$function_body" | grep -q "local dev=" echo "$function_body" | grep -q "local hcnid" } # ======================================== # Test: Fixtures validation # ======================================== @test "fixture validation: bond333e80f5 has two devices" { # Verify test fixture consistency hcn_id_vnic=$(hexdump -n 4 -ve '/1 "%02x"' "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ibm,hcn-id") hcn_id_pci=$(hexdump -n 4 -ve '/1 "%02x"' "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ibm,hcn-id") [ "$hcn_id_vnic" = "333e80f5" ] [ "$hcn_id_pci" = "333e80f5" ] } @test "fixture validation: vnic device maps to env6" { ofpathname_result=$(cat "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ofpathname") [ "$ofpathname_result" = "env6" ] } @test "fixture validation: PCI device maps to enP32775p1s0" { ofpathname_result=$(cat "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ofpathname") [ "$ofpathname_result" = "enP32775p1s0" ] } @test "fixture validation: PCI device is primary" { mode=$(cat "$MOCK_PROC/device-tree/pci@800000029008007/ethernet@0/ibm,hcn-mode") # Should contain "primary" (with possible null bytes) echo "$mode" | grep -q "primary" } @test "fixture validation: vnic device is backup" { mode=$(cat "$MOCK_PROC/device-tree/vdevice/vnic@30000006/ibm,hcn-mode") # Should contain "backup" (with possible null bytes) echo "$mode" | grep -q "backup" } @test "fixture validation: nm-initrd-generator connections have UUID controllers" { # The nm-initrd-generator creates connections with UUID references grep -q "controller=e2a7b4b8-8297-4185-8421-ed32cf47db1c" \ "$MOCK_NM_INITRD_DIR/enP32775p1s0.nmconnection" grep -q "controller=e2a7b4b8-8297-4185-8421-ed32cf47db1c" \ "$MOCK_NM_INITRD_DIR/env6.nmconnection" } @test "fixture validation: hcn connections have bond name controllers" { # After fixup, connections should reference bond by name grep -q "controller=bond333e80f5" \ "$MOCK_HCN_DIR/bond333e80f5-enP32775p1s0.nmconnection" grep -q "controller=bond333e80f5" \ "$MOCK_HCN_DIR/bond333e80f5-env6.nmconnection" }