From 3e302e7f192778a30bc04a84570d247fd3ddc440 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 8 Sep 2026 12:11:42 -0300 Subject: [PATCH 1/4] test(xcat-core): Introduce BATS & convert shell scripting tests to it The go-xcat shell behavior tests were written as Perl harnesses, which made the shell assertions harder to read and kept shell-specific setup outside a native shell test framework. Add BATS to the GitHub Actions dependency set, run BATS tests from the same preserved source tree as the Perl unit suite, and move the go-xcat repository checks into xCAT-test/autotest/bats. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- .github/workflows/xcat_test.yml | 2 +- github_action_xcat_test.pl | 49 ++++- .../dracut_105/el/module-setup.sh | 13 +- .../dracut_105/ubuntu/module-setup.sh | 13 +- .../share/xcat/install/scripts/post.xcat | 5 +- .../share/xcat/install/scripts/pre.sles | 11 - .../share/xcat/install/scripts/scriptlib | 24 ++ xCAT-test/README.md | 20 ++ xCAT-test/autotest/bats/README.md | 21 ++ .../autotest/bats/genesis_ib_modules.bats | 118 ++++++++++ .../bats/go_xcat_common_repository.bats | 122 +++++++++++ .../autotest/bats/go_xcat_el_repo_check.bats | 206 ++++++++++++++++++ xCAT-test/autotest/bats/helpers/go_xcat.bash | 56 +++++ .../autotest/bats/helpers/shell_source.bash | 132 +++++++++++ .../bats/post_xcat_download_policy.bats | 76 +++++++ .../autotest/bats/remoteshell_restart.bats | 77 +++++++ xCAT-test/autotest/bats/sles_pre_script.bats | 45 ++++ .../autotest/bats/statelite_add_ssh.bats | 40 ++++ xCAT-test/unit/README.md | 5 + xCAT-test/unit/genesis_ib_modules.t | 40 ---- xCAT-test/unit/go_xcat_common_repository.t | 165 -------------- xCAT-test/unit/go_xcat_el_repo_check.t | 161 -------------- xCAT-test/unit/post_xcat_download_policy.t | 48 ---- xCAT-test/unit/remoteshell_kill_signal.t | 26 --- xCAT-test/unit/remoteshell_kill_wait.t | 62 ------ xCAT-test/unit/sles_pre_script.t | 20 -- xCAT-test/unit/statelite_add_ssh.t | 17 -- xCAT/postscripts/remoteshell | 16 +- xCAT/postscripts/xcatdsklspost | 67 +----- xCAT/postscripts/xcatlib.sh | 101 +++++++++ 30 files changed, 1113 insertions(+), 645 deletions(-) create mode 100644 xCAT-test/README.md create mode 100644 xCAT-test/autotest/bats/README.md create mode 100644 xCAT-test/autotest/bats/genesis_ib_modules.bats create mode 100644 xCAT-test/autotest/bats/go_xcat_common_repository.bats create mode 100644 xCAT-test/autotest/bats/go_xcat_el_repo_check.bats create mode 100644 xCAT-test/autotest/bats/helpers/go_xcat.bash create mode 100644 xCAT-test/autotest/bats/helpers/shell_source.bash create mode 100644 xCAT-test/autotest/bats/post_xcat_download_policy.bats create mode 100644 xCAT-test/autotest/bats/remoteshell_restart.bats create mode 100644 xCAT-test/autotest/bats/sles_pre_script.bats create mode 100644 xCAT-test/autotest/bats/statelite_add_ssh.bats delete mode 100644 xCAT-test/unit/genesis_ib_modules.t delete mode 100644 xCAT-test/unit/go_xcat_common_repository.t delete mode 100644 xCAT-test/unit/go_xcat_el_repo_check.t delete mode 100644 xCAT-test/unit/post_xcat_download_policy.t delete mode 100644 xCAT-test/unit/remoteshell_kill_signal.t delete mode 100644 xCAT-test/unit/remoteshell_kill_wait.t delete mode 100644 xCAT-test/unit/sles_pre_script.t delete mode 100644 xCAT-test/unit/statelite_add_ssh.t diff --git a/.github/workflows/xcat_test.yml b/.github/workflows/xcat_test.yml index 0fe2cd911..04d50aff0 100644 --- a/.github/workflows/xcat_test.yml +++ b/.github/workflows/xcat_test.yml @@ -7,7 +7,7 @@ jobs: steps: - uses: actions/checkout@v6 - name: Install dependencies - run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common + run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests bats build-essential fakeroot reprepro devscripts debhelper libcapture-tiny-perl libfile-slurper-perl libjson-perl libparallel-forkmanager-perl libsoap-lite-perl libdbi-perl libcgi-pm-perl quilt openssh-server dpkg looptools genometools software-properties-common - name: Run tests run: perl github_action_xcat_test.pl diff --git a/github_action_xcat_test.pl b/github_action_xcat_test.pl index f4480771b..42d565885 100644 --- a/github_action_xcat_test.pl +++ b/github_action_xcat_test.pl @@ -311,8 +311,11 @@ sub preserve_source_tree{ return 1; } - @output = runcmd("ls $unitsrc/xCAT-test/unit/*.t | wc -l"); - print "[preserve_source_tree] preserved $srcdir in $unitsrc ($output[0] unit tests)\n"; + @output = runcmd("find $unitsrc/xCAT-test/unit -name '*.t' | wc -l"); + my $perl_count = $output[0]; + @output = runcmd("find $unitsrc/xCAT-test/autotest/bats -name '*.bats' 2>/dev/null | wc -l"); + my $bats_count = $output[0]; + print "[preserve_source_tree] preserved $srcdir in $unitsrc ($perl_count Perl unit tests, $bats_count BATS tests)\n"; return 0; } @@ -466,6 +469,39 @@ sub run_unit_tests{ return 0; } +#-------------------------------------------------------- +# Fuction name: run_bats_tests +# Description: Run shell-script unit tests under xCAT-test/autotest/bats. +# Runs against the pre-build copy of the source tree taken by +# preserve_source_tree(), like the Perl unit tests. +# Attributes: +# Return code: 0 all tests passed, 1 otherwise +#-------------------------------------------------------- +sub run_bats_tests{ + my $testdir = "$unitsrc/xCAT-test/autotest/bats"; + my @output = runcmd("find $testdir -name '*.bats' -print -quit 2>/dev/null"); + if (!@output) { + print "[run_bats_tests] no BATS tests found under $testdir\n"; + return 0; + } + + my $cmd = "cd $unitsrc && bats -r xCAT-test/autotest/bats"; + print "[run_bats_tests] running $cmd\n"; + @output = runcmd("$cmd"); + print Dumper \@output; + if($::RUNCMD_RC){ + print RED "[run_bats_tests] $cmd ....[Failed]\n"; + $check_result_str .= "> **BATS TESTS Failed** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n"; + print $check_result_str; + return 1; + } + + print "[run_bats_tests] $cmd ....[Pass]\n"; + $check_result_str .= "> **BATS TESTS Successful**\n"; + print $check_result_str; + return 0; +} + #-------------------------------------------------------- # Fuction name: check_syntax # Description: @@ -689,6 +725,15 @@ if($rst){ } mark_time("run_unit_tests"); +#Run shell-script unit tests. +print GREEN "\n------Running xCAT-test BATS tests ------\n"; +$rst = run_bats_tests(); +if($rst){ + print RED "Run of xCAT-test BATS tests failed\n"; + exit $rst; +} +mark_time("run_bats_tests"); + #Check the syntax of changing code print GREEN "\n------ Checking the syntax of changed code------\n"; $rst = check_syntax(); diff --git a/xCAT-genesis-builder/dracut_105/el/module-setup.sh b/xCAT-genesis-builder/dracut_105/el/module-setup.sh index e2bb98250..302c4758f 100755 --- a/xCAT-genesis-builder/dracut_105/el/module-setup.sh +++ b/xCAT-genesis-builder/dracut_105/el/module-setup.sh @@ -9,14 +9,15 @@ depends() { } installkernel() { - local modules_dep modfile modname + local modules_dep modules_root modfile modname - if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then - modules_dep="/lib/modules/$kernel/modules.dep" - elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then - modules_dep="/lib/modules/$KERNELVERSION/modules.dep" + modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}" + if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then + modules_dep="$modules_root/$kernel/modules.dep" + elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then + modules_dep="$modules_root/$KERNELVERSION/modules.dep" else - modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1) + modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1) fi [[ -r "$modules_dep" ]] || return 0 diff --git a/xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh b/xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh index e4b8b0e3d..ba5325247 100755 --- a/xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh +++ b/xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh @@ -9,14 +9,15 @@ depends() { } installkernel() { - local modules_dep modfile modname + local modules_dep modules_root modfile modname - if [[ -n "${kernel:-}" && -r "/lib/modules/$kernel/modules.dep" ]]; then - modules_dep="/lib/modules/$kernel/modules.dep" - elif [[ -n "${KERNELVERSION:-}" && -r "/lib/modules/$KERNELVERSION/modules.dep" ]]; then - modules_dep="/lib/modules/$KERNELVERSION/modules.dep" + modules_root="${DRACUT_MODULES_ROOT:-/lib/modules}" + if [[ -n "${kernel:-}" && -r "$modules_root/$kernel/modules.dep" ]]; then + modules_dep="$modules_root/$kernel/modules.dep" + elif [[ -n "${KERNELVERSION:-}" && -r "$modules_root/$KERNELVERSION/modules.dep" ]]; then + modules_dep="$modules_root/$KERNELVERSION/modules.dep" else - modules_dep=$(ls -1 /lib/modules/*/modules.dep 2>/dev/null | head -n 1) + modules_dep=$(ls -1 "$modules_root"/*/modules.dep 2>/dev/null | head -n 1) fi [[ -r "$modules_dep" ]] || return 0 diff --git a/xCAT-server/share/xcat/install/scripts/post.xcat b/xCAT-server/share/xcat/install/scripts/post.xcat index d6ac63ff0..bb40d0f22 100755 --- a/xCAT-server/share/xcat/install/scripts/post.xcat +++ b/xCAT-server/share/xcat/install/scripts/post.xcat @@ -100,10 +100,7 @@ if [ ! -x /usr/bin/wget ]; then sleep 36500d fi -# These dispatcher scripts are not needed by the legacy post.xcat path. Newer -# wget parses HTML-looking regex strings inside downloaded scripts and fails the -# whole recursive download on bogus URLs. -wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 http://${MASTER_IP}:${HTTPPORT}${INSTALLDIR}/postscripts/ -P /xcatpost 2> /tmp/wget.log +xcat_download_postscripts "${MASTER_IP}:${HTTPPORT}" "$INSTALLDIR" "/xcatpost" "/tmp/wget.log" if [ "$?" != "0" ]; then msgutil_r "$MASTER_IP" "error" "failed to download postscripts from http://$MASTER_IP$INSTALLDIR/postscripts/,check /tmp/wget.log on the node, halt ..." "/var/log/xcat/xcat.log" "$log_label" /tmp/updateflag $MASTER $XCATIPORT "installstatus failed" diff --git a/xCAT-server/share/xcat/install/scripts/pre.sles b/xCAT-server/share/xcat/install/scripts/pre.sles index 292b4c6b7..1eaa4c7c1 100644 --- a/xCAT-server/share/xcat/install/scripts/pre.sles +++ b/xCAT-server/share/xcat/install/scripts/pre.sles @@ -191,17 +191,6 @@ if [ -e "/tmp/xcat.install_disk" ]; then fi msgutil_r "$MASTER_IP" "info" "Found $instdisk, generate partition file..." "/var/log/xcat/xcat.log" "$log_label" -set_sles11_uefi_bootloader() -{ - if grep -E 'install=.*sles11' /proc/cmdline >/dev/null 2>&1; then - # SLES 11 AutoYaST keeps the template's legacy MBR bootloader - # location unless the UEFI path explicitly selects elilo. - sed -i -e '/mbr!elilo!' \ - /tmp/profile/modified.xml - fi -} if [ -d /sys/firmware/efi ]; then sed -e 's!XCATPARTITIONHOOK!'$instdisk'vfat/boot/efi128mbswapauto/auto!' /tmp/profile/autoinst.xml > /tmp/profile/modified.xml diff --git a/xCAT-server/share/xcat/install/scripts/scriptlib b/xCAT-server/share/xcat/install/scripts/scriptlib index ab74bddb6..de427d3e2 100644 --- a/xCAT-server/share/xcat/install/scripts/scriptlib +++ b/xCAT-server/share/xcat/install/scripts/scriptlib @@ -63,3 +63,27 @@ declare -F xcat_enable_active_nm_autoconnect &>/dev/null || function xcat_enable nmcli con mod "$con_name" connection.autoconnect yes done } + +declare -F xcat_download_postscripts &>/dev/null || function xcat_download_postscripts { + local server="$1" + local install_dir="${2:-/install}" + local postroot="${3:-/xcatpost}" + local log_file="${4:-/tmp/wget.log}" + + export LANG=C + wget -l inf -N -r --waitretry=10 --random-wait --retry-connrefused -e robots=off -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent -t 20 -T 60 "http://${server}${install_dir}/postscripts/" -P "$postroot" 2> "$log_file" +} + +declare -F set_sles11_uefi_bootloader &>/dev/null || function set_sles11_uefi_bootloader { + local cmdline="${1:-/proc/cmdline}" + local profile="${2:-/tmp/profile/modified.xml}" + + if grep -E 'install=.*sles11' "$cmdline" >/dev/null 2>&1; then + # SLES 11 AutoYaST keeps the template's legacy MBR bootloader + # location unless the UEFI path explicitly selects elilo. + sed -i -e '/mbr!elilo!' \ + "$profile" + fi +} diff --git a/xCAT-test/README.md b/xCAT-test/README.md new file mode 100644 index 000000000..7406be0d4 --- /dev/null +++ b/xCAT-test/README.md @@ -0,0 +1,20 @@ +# xCAT-test + +Unit tests that run from the source checkout are split by implementation +language: + +| Test type | Location | Runner | +| --------- | -------- | ------ | +| Perl unit tests | `xCAT-test/unit/*.t` | `prove -r xCAT-test/unit` | +| Shell unit tests | `xCAT-test/autotest/bats/*.bats` | `bats -r xCAT-test/autotest/bats` | + +Use Perl `.t` tests for Perl modules, Perl scripts, templates, and repository +artifacts. Use BATS tests for shell-script behavior that can be exercised from +the checkout by sourcing a shell library or script and shadowing external +commands. + +Shell behavior should not be tested by Perl tests that grep shell source. Put +those tests under `xCAT-test/autotest/bats` instead. + +See `unit/README.md` and `autotest/bats/README.md` for the detailed rules for +each unit-test suite. diff --git a/xCAT-test/autotest/bats/README.md b/xCAT-test/autotest/bats/README.md new file mode 100644 index 000000000..29a1403e8 --- /dev/null +++ b/xCAT-test/autotest/bats/README.md @@ -0,0 +1,21 @@ +# xCAT-test/autotest/bats + +Shell-script unit tests live here and run with: + +```bash +bats -r xCAT-test/autotest/bats +``` + +The GitHub Actions `xcat_test` workflow runs this command after the Perl `.t` +unit tests. Use BATS for shell behavior that can be exercised from the source +tree without an installed xCAT, a live management node, or real services. + +Prefer sourcing an existing shell library or sourceable script and calling the +function under test. Keep reusable install-template helpers in +`xCAT-server/share/xcat/install/scripts/scriptlib`, and reusable postscript +helpers in `xCAT/postscripts/xcatlib.sh`. Use scratch directories and shadowed +commands so tests cannot write to the host. + +Extraction helpers in `helpers/shell_source.bash` are only for legacy code that +cannot safely be sourced yet. Do not add Perl `.t` tests that grep shell source +when the behavior can be tested with BATS. diff --git a/xCAT-test/autotest/bats/genesis_ib_modules.bats b/xCAT-test/autotest/bats/genesis_ib_modules.bats new file mode 100644 index 000000000..ed132043e --- /dev/null +++ b/xCAT-test/autotest/bats/genesis_ib_modules.bats @@ -0,0 +1,118 @@ +#!/usr/bin/env bats + +load 'helpers/shell_source' + +setup() +{ + GENESIS_SPEC="$(repo_path 'xCAT-genesis-builder/xCAT-genesis-base.spec')" + DRACUT_MODULE="$(repo_path 'xCAT-genesis-builder/dracut_105/el/module-setup.sh')" + DOXCAT="$(repo_path 'xCAT-genesis-scripts/usr/bin/doxcat')" + [ -r "$GENESIS_SPEC" ] || skip "$GENESIS_SPEC is required" + [ -r "$DRACUT_MODULE" ] || skip "$DRACUT_MODULE is required" + [ -r "$DOXCAT" ] || skip "$DOXCAT is required" + export GENESIS_SPEC DRACUT_MODULE DOXCAT +} + +run_installkernel() +{ + local modules_root="$1" + local instmods_log="$2" + + kernel=5.14.0-test + DRACUT_MODULES_ROOT="$modules_root" + instmods() + { + printf '%s\n' "$1" >>"$instmods_log" + } + + source "$DRACUT_MODULE" + installkernel +} + +run_doxcat_modprobe_preamble() +{ + local preamble="$1" + local modprobe_log="$2" + + modprobe() + { + printf '%s\n' "$*" >>"$modprobe_log" + } + + eval "$preamble" +} + +run_doxcat_bootif_block() +{ + local block="$1" + + BOOTIF=01-aa-bb-cc-dd-ee-ff + bootnic= + log_label=test + gripeiter=2 + + logger() { :; } + sleep() { :; } + ip() + { + printf '%s\n' "$*" >>"$IP_LOG" + if [ "$*" = "link show" ]; then + cat <<'EOF' +2: eth0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000 + link/ether 00:11:22:33:44:55 brd ff:ff:ff:ff:ff:ff +3: ib0: mtu 65520 qdisc mq state UP mode DEFAULT group default qlen 256 + link/infiniband 00:bb:cc:dd:ee:ff brd 00:ff:ff:ff:ff:ff +EOF + fi + } + + eval "$block" + printf '%s\n' "$bootnic" +} + +@test "genesis build requires kernel module packages" { + grep -Fxq 'BuildRequires: kernel-core' "$GENESIS_SPEC" + grep -Fxq 'BuildRequires: kernel-modules' "$GENESIS_SPEC" + grep -Fxq 'BuildRequires: kernel-modules-extra' "$GENESIS_SPEC" +} + +@test "dracut genesis module installs every module from modules.dep" { + local modules_root="${BATS_TEST_TMPDIR}/modules" + local modules_dep="${modules_root}/5.14.0-test/modules.dep" + local instmods_log="${BATS_TEST_TMPDIR}/instmods.log" + + mkdir -p "${modules_root}/5.14.0-test" + cat >"$modules_dep" <<'EOF' +kernel/drivers/infiniband/ulp/ipoib/ib_ipoib.ko.xz: +kernel/drivers/net/ethernet/intel/e1000e/e1000e.ko.xz: +EOF + + run run_installkernel "$modules_root" "$instmods_log" + [ "$status" -eq 0 ] + grep -Fxq 'ib_ipoib' "$instmods_log" + grep -Fxq 'e1000e' "$instmods_log" +} + +@test "doxcat loads IP over InfiniBand support during startup" { + local preamble + local modprobe_log="${BATS_TEST_TMPDIR}/modprobe.log" + + preamble="$(extract_line_range "$DOXCAT" '^modprobe acpi_cpufreq' '^modprobe ib_ipoib$')" || return 1 + + run run_doxcat_modprobe_preamble "$preamble" "$modprobe_log" + [ "$status" -eq 0 ] + grep -Fxq 'ib_ipoib' "$modprobe_log" +} + +@test "doxcat falls back to InfiniBand BOOTIF lookup after Ethernet lookup misses" { + local block + + IP_LOG="${BATS_TEST_TMPDIR}/ip.log" + export IP_LOG + block="$(extract_shell_if_block "$DOXCAT" 'if [ ! -z "$BOOTIF" ]; then')" || return 1 + + run run_doxcat_bootif_block "$block" + [ "$status" -eq 0 ] + [ "$output" = "ib0" ] + [ "$(grep -c '^link show$' "$IP_LOG")" -eq 2 ] +} diff --git a/xCAT-test/autotest/bats/go_xcat_common_repository.bats b/xCAT-test/autotest/bats/go_xcat_common_repository.bats new file mode 100644 index 000000000..b49f4fce0 --- /dev/null +++ b/xCAT-test/autotest/bats/go_xcat_common_repository.bats @@ -0,0 +1,122 @@ +#!/usr/bin/env bats + +load 'helpers/go_xcat' + +setup() +{ + go_xcat_require_source +} + +run_common_repository_case() +{ + local case_dir="$1" + local common_present="$2" + shift 2 + + mkdir -p "$case_dir" + export ADD_LOG="${case_dir}/add.log" + export COMMON_PRESENT="$common_present" + export DOWNLOAD_LOG="${case_dir}/download.log" + export ID_LOG="${case_dir}/id.log" + export TEST_TMP="$case_dir" + + go_xcat_load_functions \ + add_xcat_dep_common_repo_yum_or_zypper \ + xcat_dep_common_repo_configured \ + refresh_xcat_dep_repository_ids + + TMP_DIR="$TEST_TMP" + GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid + GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep) + + yum() { :; } + + download_file() + { + printf '%s\n' "$1" >>"$DOWNLOAD_LOG" + [[ ${COMMON_PRESENT:-0} == 1 ]] || return 1 + : >"$2" + } + + add_repo_by_url_yum_or_zypper() + { + printf '%s %s\n' "$1" "$2" >>"$ADD_LOG" + } + + xcat_dep_common_repo_configured() + { + [[ -s "$ADD_LOG" ]] + } + + ( add_xcat_dep_common_repo_yum_or_zypper "$@" ) + refresh_xcat_dep_repository_ids + printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG" +} + +run_template_generation() +{ + local tmp_dir="$1" + local repo_log="$2" + + mkdir -p "$tmp_dir" + export REPO_LOG="$repo_log" + export TEST_TMP="$tmp_dir" + + go_xcat_load_functions add_repo_by_url_yum_or_zypper + + TMP_DIR="$TEST_TMP" + GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat + yum() { :; } + add_repo_by_file() { cp "$1" "$REPO_LOG"; } + + add_repo_by_url_yum_or_zypper \ + https://repo.example.invalid/xcat-dep/common xcat-dep-common optional +} + +@test "an available remote common repository is enabled" { + local case_dir="${BATS_TEST_TMPDIR}/remote-present" + + run run_common_repository_case "$case_dir" 1 "" latest + [ "$status" -eq 0 ] + [ "$(read_file_or_empty "${case_dir}/download.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml" ] + [ "$(read_file_or_empty "${case_dir}/add.log")" = "https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common" ] + [ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep xcat-dep-common" ] +} + +@test "a release without the remote common repository remains usable" { + local case_dir="${BATS_TEST_TMPDIR}/remote-missing" + + run run_common_repository_case "$case_dir" 0 "" 2.18 + [ "$status" -eq 0 ] + [ "$(read_file_or_empty "${case_dir}/add.log")" = "" ] + [ "$(read_file_or_empty "${case_dir}/id.log")" = "xcat-dep" ] +} + +@test "a custom repository file does not guess an unrelated common repository" { + local case_dir="${BATS_TEST_TMPDIR}/repo-file" + + run run_common_repository_case "$case_dir" 1 https://repo.example.invalid/custom/xcat-dep.repo latest + [ "$status" -eq 0 ] + [ "$(read_file_or_empty "${case_dir}/download.log")" = "" ] + [ "$(read_file_or_empty "${case_dir}/add.log")" = "" ] +} + +@test "a local common repository is enabled beside the distribution repository" { + local local_root="${BATS_TEST_TMPDIR}/local-repository" + local case_dir="${BATS_TEST_TMPDIR}/local-present" + mkdir -p "${local_root}/common/repodata" + : >"${local_root}/common/repodata/repomd.xml" + + run run_common_repository_case "$case_dir" 0 "$local_root" latest + [ "$status" -eq 0 ] + [ "$(read_file_or_empty "${case_dir}/add.log")" = "${local_root}/common xcat-dep-common" ] +} + +@test "the optional common repository template tolerates outages and verifies metadata" { + local template_log="${BATS_TEST_TMPDIR}/generated-common.repo" + + run run_template_generation "$BATS_TEST_TMPDIR" "$template_log" + [ "$status" -eq 0 ] + grep -Fxq 'skip_if_unavailable=1' "$template_log" + grep -Fxq 'repo_gpgcheck=1' "$template_log" +} diff --git a/xCAT-test/autotest/bats/go_xcat_el_repo_check.bats b/xCAT-test/autotest/bats/go_xcat_el_repo_check.bats new file mode 100644 index 000000000..0064f8673 --- /dev/null +++ b/xCAT-test/autotest/bats/go_xcat_el_repo_check.bats @@ -0,0 +1,206 @@ +#!/usr/bin/env bats + +load 'helpers/go_xcat' + +setup() +{ + go_xcat_require_source + export CALLS="${BATS_TEST_TMPDIR}/calls" + export GO_XCAT_ARCH=x86_64 + export GO_XCAT_LINUX_DISTRO=rocky + export GO_XCAT_LINUX_VERSION=10.2 + export EPEL_HAS=1 + export CRB_HAS=1 + export QUERY_FAIL=0 + export QUERY_WARNS=0 + export SOURCE_ONLY=0 + export ENTRY=check +} + +run_el_repo_check() +{ + rm -f "$CALLS" + + go_xcat_load_functions \ + repo_carries \ + el_epel_and_crb_check \ + install_packages_dnf \ + install_packages_yum + + EL_EPEL_TEST_RPM=perl-Crypt-CBC + EL_CRB_TEST_RPM=perl-IO-Tty + + dnf() + { + echo "$*" >>"$CALLS" + if [[ ${QUERY_FAIL:-0} == 1 ]]; then + echo "Error: Failed to download metadata for repo 'epel'" >&2 + return 1 + fi + [[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2 + + local has=0 + case "$*" in + *perl-Crypt-CBC*) has="$EPEL_HAS" ;; + *perl-IO-Tty*) has="$CRB_HAS" ;; + esac + + [[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1 + case "$1" in + repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;; + list) [[ $has == 1 ]] && return 0; return 1 ;; + esac + return 0 + } + + yum() + { + dnf "$@" + } + + case "${ENTRY:-check}" in + dnf) install_packages_dnf -y xCAT ;; + yum) install_packages_yum -y xCAT ;; + *) el_epel_and_crb_check dnf ;; + esac +} + +@test "EL9 with EPEL and CRB passes after probing binary repositories" { + export GO_XCAT_LINUX_VERSION=9.5 + + run run_el_repo_check + [ "$status" -eq 0 ] + probes="$(joined_file_lines "$CALLS")" + [[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]] + [[ "$probes" =~ ^repoquery\ ]] + [[ "$probes" =~ --arch\ x86_64,noarch ]] +} + +@test "EL9 without EPEL stops and names the EL9 release package" { + export GO_XCAT_LINUX_VERSION=9.5 + export EPEL_HAS=0 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ epel-release-latest-9\.noarch ]] +} + +@test "EL10 with EPEL and CRB passes after probing both repositories" { + run run_el_repo_check + [ "$status" -eq 0 ] + probes="$(joined_file_lines "$CALLS")" + [[ "$probes" =~ perl-Crypt-CBC.*\;.*perl-IO-Tty ]] +} + +@test "EL10 without EPEL stops with the EL10 EPEL release package" { + export EPEL_HAS=0 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ EPEL\ repository ]] + [[ "$output" =~ epel-release-latest-10\.noarch ]] +} + +@test "EL10 without CRB stops with current CRB guidance" { + export GO_XCAT_LINUX_DISTRO=rhel + export CRB_HAS=0 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ CRB\ repository ]] + [[ "$output" =~ "'dnf update epel-release' and then 'crb enable'" ]] + [[ ! "$output" =~ gpgcheck=0|centos-crb|subscription-manager ]] +} + +@test "a source repository does not stand in for the binary one" { + export EPEL_HAS=0 + export SOURCE_ONLY=1 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ EPEL\ repository ]] +} + +@test "Oracle Linux 10 without CRB names its CodeReady Builder command" { + export GO_XCAT_LINUX_DISTRO=ol + export GO_XCAT_LINUX_VERSION=10.1 + export CRB_HAS=0 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ dnf\ config-manager\ --enable\ ol10_codeready_builder ]] +} + +@test "a failed repository query stops with the package manager error" { + export QUERY_FAIL=1 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ Failed\ to\ download\ metadata ]] + [[ ! "$output" =~ requires\ EPEL\ repository ]] +} + +@test "a warning on stderr does not stand in for a package" { + export EPEL_HAS=0 + export QUERY_WARNS=1 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ EPEL\ repository ]] +} + +@test "a warning beside a real match does not fail the check" { + export QUERY_WARNS=1 + + run run_el_repo_check + [ "$status" -eq 0 ] +} + +@test "CentOS Stream 10, which reports the major version alone, is checked" { + export GO_XCAT_LINUX_DISTRO=centos + export GO_XCAT_LINUX_VERSION=10 + export EPEL_HAS=0 + export CRB_HAS=0 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ EPEL\ repository ]] +} + +@test "EL8 and Fedora are not checked" { + export GO_XCAT_LINUX_VERSION=8.10 + export EPEL_HAS=0 + export CRB_HAS=0 + + run run_el_repo_check + [ "$status" -eq 0 ] + [ "$(joined_file_lines "$CALLS")" = "" ] + + export GO_XCAT_LINUX_DISTRO=fedora + export GO_XCAT_LINUX_VERSION=42 + + run run_el_repo_check + [ "$status" -eq 0 ] +} + +@test "dnf and yum installer paths run the check before installing" { + for entry in dnf yum; do + export ENTRY="$entry" + export EPEL_HAS=0 + export CRB_HAS=1 + + run run_el_repo_check + [ "$status" -eq 1 ] + [[ "$output" =~ requires\ EPEL\ repository ]] + probes="$(joined_file_lines "$CALLS")" + [[ ! "$probes" =~ install ]] + + export EPEL_HAS=1 + export CRB_HAS=1 + + run run_el_repo_check + [ "$status" -eq 0 ] + probes="$(joined_file_lines "$CALLS")" + [[ "$probes" =~ perl-IO-Tty.*\;.*install\ initscripts.*\;.*install\ xCAT ]] + done +} diff --git a/xCAT-test/autotest/bats/helpers/go_xcat.bash b/xCAT-test/autotest/bats/helpers/go_xcat.bash new file mode 100644 index 000000000..b863c963f --- /dev/null +++ b/xCAT-test/autotest/bats/helpers/go_xcat.bash @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +go_xcat_default_source() +{ + printf '%s\n' "${BATS_TEST_DIRNAME}/../../../xCAT-server/share/xcat/tools/go-xcat" +} + +go_xcat_require_source() +{ + GO_XCAT_SOURCE="${XCAT_TEST_GO_XCAT:-$(go_xcat_default_source)}" + export GO_XCAT_SOURCE + [ -r "$GO_XCAT_SOURCE" ] || skip "$GO_XCAT_SOURCE is required" +} + +go_xcat_extract_functions() +{ + local function_name + for function_name in "$@"; do + awk -v name="$function_name" ' + $0 == "function " name "()" { copy = 1 } + copy { print } + copy && /^}$/ { exit } + ' "$GO_XCAT_SOURCE" + done +} + +go_xcat_load_functions() +{ + local function_body function_name + function_body="$(go_xcat_extract_functions "$@")" || return 1 + eval "$function_body" + for function_name in "$@"; do + declare -F "$function_name" >/dev/null || { + printf 'missing %s\n' "$function_name" >&2 + return 70 + } + done +} + +read_file_or_empty() +{ + local path="$1" + [ -f "$path" ] || return 0 + cat "$path" +} + +joined_file_lines() +{ + local path="$1" + local line separator="" + [ -f "$path" ] || return 0 + while IFS= read -r line; do + printf '%s%s' "$separator" "$line" + separator=";" + done <"$path" +} diff --git a/xCAT-test/autotest/bats/helpers/shell_source.bash b/xCAT-test/autotest/bats/helpers/shell_source.bash new file mode 100644 index 000000000..f76ed55d3 --- /dev/null +++ b/xCAT-test/autotest/bats/helpers/shell_source.bash @@ -0,0 +1,132 @@ +#!/usr/bin/env bash + +repo_root() +{ + printf '%s\n' "${BATS_TEST_DIRNAME}/../../.." +} + +repo_path() +{ + printf '%s/%s\n' "$(repo_root)" "$1" +} + +require_repo_file() +{ + local path + path="$(repo_path "$1")" + [ -r "$path" ] || skip "$path is required" + printf '%s\n' "$path" +} + +read_file_or_empty() +{ + local path="$1" + [ -f "$path" ] || return 0 + cat "$path" +} + +extract_shell_function() +{ + local file="$1" + local name="$2" + + awk -v name="$name" ' + BEGIN { + signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*$" + inline_signature = "^[[:space:]]*(function[[:space:]]+)?" name "([[:space:]]*\\(\\))?[[:space:]]*\\{" + } + $0 ~ signature || $0 ~ inline_signature { + copy = 1 + } + copy { + print + opened += gsub(/\{/, "{") + closed += gsub(/\}/, "}") + if (opened > 0 && opened == closed) { + found = 1 + exit + } + } + END { + if (!found) { + exit 1 + } + } + ' "$file" +} + +extract_shell_if_block() +{ + local file="$1" + local start="$2" + + awk -v start="$start" ' + index($0, start) { + copy = 1 + } + copy { + print + if ($0 ~ /^[[:space:]]*if[[:space:]\[]/) { + depth++ + } + line = $0 + while (line ~ /(^|[;[:space:]])fi([;[:space:]]|$)/) { + depth-- + sub(/(^|[;[:space:]])fi([;[:space:]]|$)/, " ", line) + } + if (depth == 0) { + found = 1 + exit + } + } + END { + if (!found) { + exit 1 + } + } + ' "$file" +} + +extract_line_range() +{ + local file="$1" + local start="$2" + local end="$3" + + awk -v start="$start" -v end="$end" ' + $0 ~ start { + copy = 1 + } + copy { + print + if ($0 ~ end) { + found = 1 + exit + } + } + END { + if (!found) { + exit 1 + } + } + ' "$file" +} + +extract_first_matching_line() +{ + local file="$1" + local pattern="$2" + + awk -v pattern="$pattern" ' + $0 ~ pattern { + print + found = 1 + exit + } + END { + if (!found) { + exit 1 + } + } + ' "$file" +} diff --git a/xCAT-test/autotest/bats/post_xcat_download_policy.bats b/xCAT-test/autotest/bats/post_xcat_download_policy.bats new file mode 100644 index 000000000..4ec06387a --- /dev/null +++ b/xCAT-test/autotest/bats/post_xcat_download_policy.bats @@ -0,0 +1,76 @@ +#!/usr/bin/env bats + +load 'helpers/shell_source' + +setup() +{ + SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')" + XCATLIB="$(repo_path 'xCAT/postscripts/xcatlib.sh')" + [ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required" + [ -r "$XCATLIB" ] || skip "$XCATLIB is required" + export SCRIPT_LIB XCATLIB +} + +capture_install_scriptlib_wget() +{ + local wget_log="$1" + + wget() + { + printf '%s\n' "$*" >"$wget_log" + return 0 + } + + source "$SCRIPT_LIB" + xcat_download_postscripts "192.0.2.10:80" "/install" "/xcatpost" "$wget_log" +} + +capture_xcatlib_wget() +{ + local wget_log="$1" + + xcatpost="${BATS_TEST_TMPDIR}/xcatpost" + INSTALLDIR=/install + + echolog() { :; } + sleep() { :; } + grep() + { + [ "${*: -1}" = "/tmp/wget.log" ] && return 1 + command grep "$@" + } + wget() + { + printf '%s\n' "$*" >"$wget_log" + return 0 + } + + source "$XCATLIB" + download_postscripts 192.0.2.10:80 +} + +assert_download_policy() +{ + local args="$1" + + [[ "$args" == *'--reject index.html*,post.xcat.ng,post.xcat.rhels10'* ]] + [[ "$args" == *'--no-parent'* ]] + [[ "$args" == *'postscripts/'* ]] + [[ "$args" != *'>"$KILL_LOG" + [ "$1" = "-0" ] && return 1 + return 0 + } + sleep() { :; } + sshd() + { + printf '%s\n' start >>"$SSHD_LOG" + } + + source "$XCATLIB" + xcat_restart_sshd_after_failed_service_restart sshd +} + +run_wait_for_processes() +{ + local rc + + source "$XCATLIB" + xcat_wait_for_processes_to_exit "$*" 3 + rc=$? + printf '%s\n' "$rc" + return 0 +} + +@test "remoteshell restart fallback sends an uncatchable signal before starting sshd" { + KILL_LOG="${BATS_TEST_TMPDIR}/kill.log" + SSHD_LOG="${BATS_TEST_TMPDIR}/sshd.log" + export KILL_LOG SSHD_LOG + + run run_restart_fallback + [ "$status" -eq 0 ] + grep -Fxq -- '-9 4321' "$KILL_LOG" + ! grep -Eq '^9( |$)' "$KILL_LOG" + [ "$(read_file_or_empty "$SSHD_LOG")" = "start" ] +} + +@test "remoteshell wait loop reports a still-running process and gives up" { + local child + + sleep 30 & + child=$! + + run run_wait_for_processes "$child" + kill -KILL "$child" 2>/dev/null || true + wait "$child" 2>/dev/null || true + + [ "$status" -eq 0 ] + [ "$output" = "1" ] +} + +@test "remoteshell wait loop returns as soon as killed processes are gone" { + run run_wait_for_processes 999999 + [ "$status" -eq 0 ] + [ "$output" = "0" ] +} diff --git a/xCAT-test/autotest/bats/sles_pre_script.bats b/xCAT-test/autotest/bats/sles_pre_script.bats new file mode 100644 index 000000000..f35153200 --- /dev/null +++ b/xCAT-test/autotest/bats/sles_pre_script.bats @@ -0,0 +1,45 @@ +#!/usr/bin/env bats + +load 'helpers/shell_source' + +setup() +{ + SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')" + [ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required" + export SCRIPT_LIB +} + +@test "SLES 11 UEFI install changes the AutoYaST bootloader to elilo" { + local cmdline="${BATS_TEST_TMPDIR}/cmdline" + local profile="${BATS_TEST_TMPDIR}/modified.xml" + + printf '%s\n' 'BOOT_IMAGE=/linux install=http://192.0.2.10/install/sles11/ppc64le' >"$cmdline" + cat >"$profile" <<'EOF' + +true +true +mbr + +EOF + + source "$SCRIPT_LIB" + run set_sles11_uefi_bootloader "$cmdline" "$profile" + [ "$status" -eq 0 ] + grep -Fxq 'elilo' "$profile" + ! grep -q 'mbr' "$profile" + ! grep -q '"$cmdline" + printf '%s\n' 'mbr' >"$profile" + + source "$SCRIPT_LIB" + run set_sles11_uefi_bootloader "$cmdline" "$profile" + [ "$status" -eq 0 ] + grep -Fxq 'mbr' "$profile" +} diff --git a/xCAT-test/autotest/bats/statelite_add_ssh.bats b/xCAT-test/autotest/bats/statelite_add_ssh.bats new file mode 100644 index 000000000..b633666f9 --- /dev/null +++ b/xCAT-test/autotest/bats/statelite_add_ssh.bats @@ -0,0 +1,40 @@ +#!/usr/bin/env bats + +load 'helpers/shell_source' + +setup() +{ + ADD_SSH="$(repo_path 'xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh')" + [ -r "$ADD_SSH" ] || skip "$ADD_SSH is required" + export ADD_SSH +} + +run_sshd_config_block() +{ + local root="$1" + local block + + block="$(extract_shell_if_block "$ADD_SSH" 'if [ -r $ROOTDIR/etc/ssh/sshd_config ]')" || return 1 + ROOTDIR="$root" + eval "$block" +} + +@test "statelite add_ssh writes sshd settings below systemd's open-file limit" { + local root="${BATS_TEST_TMPDIR}/rootimg" + local sshd_config="${root}/etc/ssh/sshd_config" + + mkdir -p "${root}/etc/ssh" + cat >"$sshd_config" <<'EOF' +X11Forwarding no +KeyRegenerationInterval 3600 +MaxStartups 1024 +EOF + + run run_sshd_config_block "$root" + [ "$status" -eq 0 ] + grep -Fxq 'X11Forwarding yes' "$sshd_config" + grep -Fxq 'KeyRegenerationInterval 0' "$sshd_config" + grep -Fxq '#MaxStartups 1024' "$sshd_config" + grep -Fxq 'MaxStartups 100:30:200' "$sshd_config" + ! grep -Fxq 'MaxStartups 1024' "$sshd_config" +} diff --git a/xCAT-test/unit/README.md b/xCAT-test/unit/README.md index 79096bcbf..2fd67bcb6 100644 --- a/xCAT-test/unit/README.md +++ b/xCAT-test/unit/README.md @@ -46,6 +46,11 @@ so putting a test in `integration/` does not cost it CI coverage. What differs i each suite is allowed to depend on, and that unit tests also run standalone from a bare checkout with no xCAT at all. +Shell-script unit tests belong in [`../autotest/bats`](../autotest/bats/README.md) +and run with BATS. Do not add Perl `.t` tests that grep shell source when the +behavior can be exercised by sourcing a shell library or script and shadowing the +external commands it calls. + The distinction matters because a test that needs an absent environment does not fail -- it calls `plan skip_all` and reports as skipped. A handful of those in a suite of several hundred assertions is easy to stop reading. Keeping the two kinds in separate diff --git a/xCAT-test/unit/genesis_ib_modules.t b/xCAT-test/unit/genesis_ib_modules.t deleted file mode 100644 index ef2c3f36e..000000000 --- a/xCAT-test/unit/genesis_ib_modules.t +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use File::Spec; -use FindBin; -use Test::More; - -my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..')); - -my $spec = read_file('xCAT-genesis-builder/xCAT-genesis-base.spec'); -like($spec, qr/^BuildRequires:\s+kernel-core$/m, 'genesis build installs the kernel core'); -like($spec, qr/^BuildRequires:\s+kernel-modules$/m, 'genesis build installs the standard kernel modules'); -like($spec, qr/^BuildRequires:\s+kernel-modules-extra$/m, 'genesis build installs the extra kernel modules'); - -my $dracut_module = read_file('xCAT-genesis-builder/dracut_105/el/module-setup.sh'); -like( - $dracut_module, - qr/installkernel\(\) \{.*?modules_dep=.*?while IFS= read -r modfile;.*?instmods "\$modname".*?done < "\$modules_dep"/s, - 'genesis dracut module installs every module available to the build' -); - -my $doxcat = read_file('xCAT-genesis-scripts/usr/bin/doxcat'); -like($doxcat, qr/^modprobe ib_ipoib$/m, 'genesis loads the IP over InfiniBand module'); -like( - $doxcat, - qr/if \[ -z "\$bootnic" \]; then\s+bootnic=`ip link show\|grep -B1 infiniband/s, - 'genesis checks InfiniBand addresses only after the Ethernet lookup misses' -); - -done_testing(); - -sub read_file { - my ($file) = @_; - my $path = File::Spec->catfile($repo_root, split m{/}, $file); - open(my $fh, '<', $path) or die "open $path: $!"; - my $contents = do { local $/; <$fh> }; - close($fh) or die "close $path: $!"; - return $contents; -} diff --git a/xCAT-test/unit/go_xcat_common_repository.t b/xCAT-test/unit/go_xcat_common_repository.t deleted file mode 100644 index 21931d248..000000000 --- a/xCAT-test/unit/go_xcat_common_repository.t +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use File::Path qw(make_path); -use File::Temp qw(tempdir); -use FindBin; -use Test::More; - -my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat"; -my $tmpdir = tempdir(CLEANUP => 1); -my $driver = "$tmpdir/driver.sh"; - -open(my $driver_fh, '>', $driver) or die "open $driver: $!"; -print {$driver_fh} <<'DRIVER'; -#!/bin/bash -set -euo pipefail - -function_body=$( - for function_name in \ - add_xcat_dep_common_repo_yum_or_zypper \ - xcat_dep_common_repo_configured \ - refresh_xcat_dep_repository_ids - do - awk -v name="$function_name" ' - $0 == "function " name "()" { copy = 1 } - copy { print } - copy && /^}$/ { exit } - ' "$GO_XCAT_SOURCE" - done -) -eval "$function_body" - -TMP_DIR=$TEST_TMP -GO_XCAT_DEFAULT_BASE_URL=https://repo.example.invalid -GO_XCAT_DEP_REPOSITORY_IDS=(xcat-dep) - -yum() { :; } - -download_file() { - printf '%s\n' "$1" >>"$DOWNLOAD_LOG" - [[ ${COMMON_PRESENT:-0} == 1 ]] || return 1 - : >"$2" -} - -add_repo_by_url_yum_or_zypper() { - printf '%s %s\n' "$1" "$2" >>"$ADD_LOG" -} - -xcat_dep_common_repo_configured() { [[ -s "$ADD_LOG" ]]; } -( add_xcat_dep_common_repo_yum_or_zypper "$@" ) -refresh_xcat_dep_repository_ids -printf '%s\n' "${GO_XCAT_DEP_REPOSITORY_IDS[*]}" >"$ID_LOG" -DRIVER -close($driver_fh) or die "close $driver: $!"; -chmod(0755, $driver) or die "chmod $driver: $!"; - -sub run_case { - my ($name, $present, @arguments) = @_; - my $case_dir = "$tmpdir/$name"; - make_path($case_dir); - local %ENV = ( - %ENV, - ADD_LOG => "$case_dir/add.log", - COMMON_PRESENT => $present, - DOWNLOAD_LOG => "$case_dir/download.log", - GO_XCAT_SOURCE => $go_xcat, - ID_LOG => "$case_dir/id.log", - TEST_TMP => $case_dir, - ); - my $status = system('bash', $driver, @arguments); - return ($status >> 8, $case_dir); -} - -sub read_file { - my ($path) = @_; - return '' unless -f $path; - open(my $fh, '<', $path) or die "open $path: $!"; - my $content = do { local $/; <$fh> }; - close($fh) or die "close $path: $!"; - return $content; -} - -my ($status, $case_dir) = run_case('remote-present', 1, '', 'latest'); -is($status, 0, 'an available common repository is accepted'); -is( - read_file("$case_dir/download.log"), - "https://repo.example.invalid/yum/latest/xcat-dep/common/repodata/repomd.xml\n", - 'the default repository is probed before it is enabled', -); -is( - read_file("$case_dir/add.log"), - "https://repo.example.invalid/yum/latest/xcat-dep/common xcat-dep-common\n", - 'the common repository uses its own repository ID', -); -is(read_file("$case_dir/id.log"), "xcat-dep xcat-dep-common\n", - 'common packages are included in repository listings'); - -($status, $case_dir) = run_case('remote-missing', 0, '', '2.18'); -is($status, 0, 'a release without the common repository remains usable'); -is(read_file("$case_dir/add.log"), '', 'a missing common repository is not enabled'); -is(read_file("$case_dir/id.log"), "xcat-dep\n", - 'legacy package listings remain unchanged when common is absent'); - -($status, $case_dir) = run_case( - 'repo-file', 1, 'https://repo.example.invalid/custom/xcat-dep.repo', 'latest' -); -is($status, 0, 'a custom repository file remains supported'); -is(read_file("$case_dir/download.log"), '', - 'the common URL is not guessed from a custom repository file'); -is(read_file("$case_dir/add.log"), '', - 'a custom repository file does not enable an unrelated repository'); - -my $local_root = "$tmpdir/local-repository"; -make_path("$local_root/common/repodata"); -open(my $repomd_fh, '>', "$local_root/common/repodata/repomd.xml") or die $!; -close($repomd_fh) or die $!; -($status, $case_dir) = run_case('local-present', 0, $local_root, 'latest'); -is($status, 0, 'a local common repository is accepted'); -is( - read_file("$case_dir/add.log"), - "$local_root/common xcat-dep-common\n", - 'the local common repository is enabled beside the distribution repository', -); - -my $template_driver = "$tmpdir/template-driver.sh"; -open(my $template_fh, '>', $template_driver) or die "open $template_driver: $!"; -print {$template_fh} <<'DRIVER'; -#!/bin/bash -set -euo pipefail - -function_body=$( - awk ' - /^function add_repo_by_url_yum_or_zypper\(\)/ { copy = 1 } - copy { print } - copy && /^}$/ { exit } - ' "$GO_XCAT_SOURCE" -) -eval "$function_body" - -TMP_DIR=$TEST_TMP -GO_XCAT_DEFAULT_INSTALL_PATH=/install/xcat -yum() { :; } -add_repo_by_file() { cp "$1" "$REPO_LOG"; } -add_repo_by_url_yum_or_zypper \ - https://repo.example.invalid/xcat-dep/common xcat-dep-common optional -DRIVER -close($template_fh) or die "close $template_driver: $!"; -chmod(0755, $template_driver) or die "chmod $template_driver: $!"; - -my $template_log = "$tmpdir/generated-common.repo"; -local %ENV = ( - %ENV, - GO_XCAT_SOURCE => $go_xcat, - REPO_LOG => $template_log, - TEST_TMP => $tmpdir, -); -$status = system('bash', $template_driver); -is($status >> 8, 0, 'go-xcat can generate the optional common repository'); -like(read_file($template_log), qr/^skip_if_unavailable=1$/m, - 'the generated common repository stays optional during outages'); -like(read_file($template_log), qr/^repo_gpgcheck=1$/m, - 'the generated common repository verifies signed metadata'); - -done_testing(); diff --git a/xCAT-test/unit/go_xcat_el_repo_check.t b/xCAT-test/unit/go_xcat_el_repo_check.t deleted file mode 100644 index f8413b165..000000000 --- a/xCAT-test/unit/go_xcat_el_repo_check.t +++ /dev/null @@ -1,161 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use File::Temp qw(tempdir); -use FindBin; -use Test::More; - -# go-xcat checked for the EPEL and CRB repositories on EL9 only, and only when the version -# carried a minor number, so CentOS Stream was never checked. On EL10 a management node without -# them failed inside dnf install with a dependency error instead of the message that names the -# missing repository, and the CRB message proposed a CentOS Stream repository file with -# signature checks disabled. The probe used dnf list, which an installed copy of the probe -# package satisfies, and which reports a failed query as a missing repository. The check -# functions are taken from the shipped script and run against a dnf stand-in that answers for -# each probe and records what was asked. - -my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat"; -plan skip_all => 'go-xcat not found' unless -r $go_xcat; - -my $tmpdir = tempdir( CLEANUP => 1 ); -my $driver = "$tmpdir/driver.sh"; -my $calls = "$tmpdir/calls"; -open( my $fh, '>', $driver ) or die "open $driver: $!"; -print {$fh} <<'DRIVER'; -#!/bin/bash -set -uo pipefail -eval "$(awk ' - /^function (repo_carries|el9?_epel_and_crb_check|install_packages_(dnf|yum))\(\)/ { copy = 1 } - copy { print } - copy && /^}$/ { copy = 0 } -' "$GO_XCAT_SOURCE")" -EL9_EPEL_TEST_RPM="perl-Crypt-CBC"; EL_EPEL_TEST_RPM="perl-Crypt-CBC" -EL9_CRB_TEST_RPM="perl-IO-Tty"; EL_CRB_TEST_RPM="perl-IO-Tty" -# repoquery prints the name when an enabled repository carries the package and nothing -# otherwise, and fails with a message when the repositories cannot be read. list -q exits 1 -# when neither a repository nor the installed set has the package. -dnf() { - echo "$*" >> "$CALLS" - if [[ ${QUERY_FAIL:-0} == 1 ]]; then - echo "Error: Failed to download metadata for repo 'epel'" >&2 - return 1 - fi - [[ ${QUERY_WARNS:-0} == 1 ]] && echo "Warning: repository 'extras' metadata is stale" >&2 - local has=0 - case "$*" in - *perl-Crypt-CBC*) has="$EPEL_HAS" ;; - *perl-IO-Tty*) has="$CRB_HAS" ;; - esac - # A source repository answers for the name unless the query is limited to binary architectures. - [[ ${SOURCE_ONLY:-0} == 1 && "$*" != *"--arch"* ]] && has=1 - case "$1" in - repoquery) [[ $has == 1 ]] && { echo "${@: -1}"; echo "${@: -1}"; }; return 0 ;; - list) [[ $has == 1 ]] && return 0; return 1 ;; - esac - return 0 -} -yum() { dnf "$@"; } -# ENTRY=dnf or yum runs the installer function go-xcat dispatches to, which owns the check. -case "${ENTRY:-check}" in - dnf) install_packages_dnf -y xCAT ;; - yum) install_packages_yum -y xCAT ;; - *) if declare -F el_epel_and_crb_check >/dev/null; then el_epel_and_crb_check dnf; else el9_epel_and_crb_check dnf; fi ;; -esac -DRIVER -close($fh); - -# Runs the check as go-xcat would on a host of this distro and version. Returns the exit status, -# the output, and the probes the dnf stand-in saw. -sub check { - my (%host) = @_; - unlink $calls; - local $ENV{GO_XCAT_SOURCE} = $go_xcat; - local $ENV{CALLS} = $calls; - local $ENV{GO_XCAT_LINUX_DISTRO} = $host{distro} || 'rocky'; - local $ENV{GO_XCAT_LINUX_VERSION} = $host{version}; - local $ENV{GO_XCAT_ARCH} = 'x86_64'; - local $ENV{EPEL_HAS} = $host{epel} ? 1 : 0; - local $ENV{CRB_HAS} = $host{crb} ? 1 : 0; - local $ENV{QUERY_FAIL} = $host{query_fail} ? 1 : 0; - local $ENV{QUERY_WARNS} = $host{query_warns} ? 1 : 0; - local $ENV{SOURCE_ONLY} = $host{source_only} ? 1 : 0; - local $ENV{ENTRY} = $host{entry} || 'check'; - my $out = `bash '$driver' 2>&1`; - my $rc = $? >> 8; - my @probes; - if ( open( my $cfh, '<', $calls ) ) { chomp( @probes = <$cfh> ); close($cfh); } - return ( $rc, $out, join( ';', @probes ) ); -} - -my ( $rc, $out, $probes ); - -( $rc, $out, $probes ) = check( version => '9.5', epel => 1, crb => 1 ); -is( $rc, 0, 'EL9 with EPEL and CRB passes' ); -like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' ); -like( $probes, qr/^repoquery /, '... through repoquery, which an installed copy does not satisfy' ); -like( $probes, qr/--arch x86_64,noarch/, '... limited to the binary architectures' ); - -( $rc, $out, $probes ) = check( version => '9.5', epel => 0, crb => 1 ); -is( $rc, 1, 'EL9 without EPEL stops' ); -like( $out, qr/epel-release-latest-9\.noarch/, '... and names the EL9 EPEL release package' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1 ); -is( $rc, 0, 'EL10 with EPEL and CRB passes' ); -like( $probes, qr/perl-Crypt-CBC.*;.*perl-IO-Tty/, '... after probing both repositories' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1 ); -is( $rc, 1, 'EL10 without EPEL stops' ); -like( $out, qr/requires EPEL repository/, '... and names the missing repository' ); -like( $out, qr/epel-release-latest-10\.noarch/, '... and the EL10 EPEL release package' ); - -( $rc, $out, $probes ) = check( distro => 'rhel', version => '10.2', epel => 1, crb => 0 ); -is( $rc, 1, 'EL10 without CRB stops' ); -like( $out, qr/requires CRB repository/, '... and names the missing repository' ); -like( $out, qr/'dnf update epel-release' and then 'crb enable'/, '... with a current crb helper, which covers RHEL under RHSM and RHUI' ); -unlike( $out, qr/gpgcheck=0|centos-crb|subscription-manager/, '... and no repository file or subscription-only command' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, source_only => 1 ); -is( $rc, 1, 'a source repository does not stand in for the binary one' ); -like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' ); - -( $rc, $out, $probes ) = check( distro => 'ol', version => '10.1', epel => 1, crb => 0 ); -is( $rc, 1, 'Oracle Linux 10 without CRB stops' ); -like( $out, qr/dnf config-manager --enable ol10_codeready_builder/, '... with the command that enables its CodeReady Builder' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_fail => 1 ); -is( $rc, 1, 'a failed repository query stops' ); -like( $out, qr/Failed to download metadata/, '... with the package manager error' ); -unlike( $out, qr/requires EPEL repository/, '... and not as a missing repository' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 0, crb => 1, query_warns => 1 ); -is( $rc, 1, 'a warning on stderr does not stand in for a package' ); -like( $out, qr/requires EPEL repository/, '... so the missing repository is still reported' ); - -( $rc, $out, $probes ) = check( version => '10.2', epel => 1, crb => 1, query_warns => 1 ); -is( $rc, 0, 'a warning beside a real match does not fail the check' ); - -( $rc, $out, $probes ) = check( distro => 'centos', version => '10', epel => 0, crb => 0 ); -is( $rc, 1, 'CentOS Stream 10, which reports the major version alone, is checked' ); -like( $out, qr/requires EPEL repository/, '... and told about EPEL' ); - -( $rc, $out, $probes ) = check( version => '8.10', epel => 0, crb => 0 ); -is( $rc, 0, 'EL8 is not checked' ); -is( $probes, '', '... and nothing is probed' ); - -( $rc, $out, $probes ) = check( distro => 'fedora', version => '42', epel => 0, crb => 0 ); -is( $rc, 0, 'Fedora is not checked' ); - -# The installer functions go-xcat dispatches to run the check before they install anything. -foreach my $entry (qw(dnf yum)) { - ( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 0, crb => 1 ); - is( $rc, 1, "install_packages_$entry on EL10 without EPEL stops" ); - like( $out, qr/requires EPEL repository/, '... with the EPEL message' ); - unlike( $probes, qr/install/, '... before anything is installed' ); - - ( $rc, $out, $probes ) = check( entry => $entry, version => '10.2', epel => 1, crb => 1 ); - is( $rc, 0, "install_packages_$entry on EL10 with EPEL and CRB installs" ); - like( $probes, qr/perl-IO-Tty.*;.*install initscripts.*;.*install xCAT/, '... after the check passed' ); -} - -done_testing(); diff --git a/xCAT-test/unit/post_xcat_download_policy.t b/xCAT-test/unit/post_xcat_download_policy.t deleted file mode 100644 index 7cbf70492..000000000 --- a/xCAT-test/unit/post_xcat_download_policy.t +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use FindBin; -use File::Spec; -use Test::More; - -my $repo_root = File::Spec->catdir( $FindBin::Bin, '..', '..' ); -my %scripts = ( - 'legacy install post.xcat' => File::Spec->catfile( - $repo_root, - 'xCAT-server/share/xcat/install/scripts/post.xcat' - ), - 'legacy netboot xcatdsklspost' => File::Spec->catfile( - $repo_root, - 'xCAT/postscripts/xcatdsklspost' - ), -); - -foreach my $path ( values %scripts ) { - plan skip_all => "$path not found" unless -r $path; -} - -foreach my $name ( sort keys %scripts ) { - my $path = $scripts{$name}; - open( my $fh, '<', $path ) or die "Unable to read $path: $!"; - my $script = do { local $/; <$fh> }; - close($fh); - - like( - $script, - qr/--reject\s+"index\.html\*,post\.xcat\.ng,post\.xcat\.rhels10"/, - "$name recursive wget ignores dispatcher scripts that contain literal HTML-link regexes" - ); - like( - $script, - qr/Newer\s+(?:#\s+)?wget\s+parses\s+HTML-looking\s+regex\s+strings/s, - "$name download policy documents why dispatcher scripts are excluded" - ); - unlike( - $script, - qr/catfile( $FindBin::Bin, '..', '..', - 'xCAT', 'postscripts', 'remoteshell' ); -plan skip_all => 'remoteshell not found' unless -r $script; - -open( my $fh, '<', $script ) or die "Unable to read $script: $!"; -my $source = do { local $/; <$fh> }; -close($fh); - -# A kill without the hyphen reads the signal number as one more process id, so -# the target gets the default signal, which a process can catch, and the -# process with that id is signalled as well. -my @bare = ( $source =~ /^[^#\n]*\bkill\s+\d/gm ); -is( scalar(@bare), 0, 'no kill gives the signal number without a hyphen' ); - -like( $source, qr/kill -9 \$PIDLIST/, - 'the ssh daemon gets the signal that a process cannot catch' ); - -done_testing(); diff --git a/xCAT-test/unit/remoteshell_kill_wait.t b/xCAT-test/unit/remoteshell_kill_wait.t deleted file mode 100644 index 505fd2418..000000000 --- a/xCAT-test/unit/remoteshell_kill_wait.t +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; - -use File::Spec; -use FindBin; -use Test::More; - -my $script = File::Spec->catfile( $FindBin::Bin, '..', '..', - 'xCAT', 'postscripts', 'remoteshell' ); -plan skip_all => 'remoteshell not found' unless -r $script; -plan skip_all => 'no bash' unless -x '/bin/bash'; - -open( my $fh, '<', $script ) or die "Unable to read $script: $!"; -my $source = do { local $/; <$fh> }; -close($fh); - -my ($loop) = $source =~ /(waited=0\n.*?\n done)/s; -ok( defined($loop), 'the wait loop was found in the postscript' ); - -# Run the loop of the postscript against real processes, with a shorter bound -# so that the test does not spend the whole timeout of the postscript. -sub waits_for { - my (@pids) = @_; - ( my $body = $loop ) =~ s/\$waited -lt 10/\$waited -lt 3/; - my $list = join( ' ', @pids ); - my $out = `/bin/bash -c 'PIDLIST="$list"\n$body\necho \$alive' 2>/dev/null`; - chomp $out; - return $out; -} - -# A process that is still running must be reported as alive, and the loop must -# give up rather than run for ever. -my $child = fork(); -if ( !defined $child ) { plan skip_all => 'cannot fork' } -if ( $child == 0 ) { exec( 'sleep', '30' ); exit 1 } -my $start = time; -is( waits_for($child), '1', 'a process that is still running is reported alive' ); -cmp_ok( time - $start, '<', 10, 'the loop gives up instead of waiting for ever' ); -kill 'KILL', $child; -waitpid( $child, 0 ); - -# A process that has ended must be reported as gone, without waiting. -my $gone = fork(); -if ( $gone == 0 ) { exit 0 } -waitpid( $gone, 0 ); -$start = time; -is( waits_for($gone), '0', 'a process that has ended is reported gone' ); -cmp_ok( time - $start, '<', 3, 'no time is spent once the process is gone' ); - -# The bound of the postscript itself, and the shape of the test. -like( $source, qr/while \[ \$waited -lt 10 \]/, 'the postscript waits at most ten seconds' ); -like( $source, qr/kill -0 \$pid/, 'the check for a running process sends no signal' ); -like( $source, qr/kill -9 \$PIDLIST/, 'the daemon still gets the signal it cannot catch' ); - -# The wait has to happen before the new daemon starts. -my ($block) = $source =~ /(PIDLIST=.*?\/usr\/sbin\/sshd)/s; -ok( defined($block), 'the fallback block was found' ); -cmp_ok( index( $block, 'waited=0' ), '<', index( $block, '/usr/sbin/sshd' ), - 'the wait comes before the new daemon starts' ); - -done_testing(); diff --git a/xCAT-test/unit/sles_pre_script.t b/xCAT-test/unit/sles_pre_script.t deleted file mode 100644 index 1ffa01eea..000000000 --- a/xCAT-test/unit/sles_pre_script.t +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; -use Test::More; - -my $pre_sles_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/install/scripts/pre.sles" : ''; -$pre_sles_path = "xCAT-server/share/xcat/install/scripts/pre.sles" - unless -f $pre_sles_path; - -plan skip_all => "pre.sles not found" unless -f $pre_sles_path; - -my $src = do { local $/; open my $fh, '<', $pre_sles_path or die $!; <$fh> }; - -like($src, qr/sub set_sles11_uefi_bootloader\b|set_sles11_uefi_bootloader\(\)/, 'SLES UEFI bootloader helper exists'); -like($src, qr/install=\.\*sles11/, 'SLES 11 UEFI bootloader change is scoped to SLES 11 install media'); -like($src, qr/elilo<\/loader_type>/, 'SLES 11 UEFI install selects elilo'); -like($src, qr/mbr<\/location>/, 'legacy MBR template value is replaced at install time'); -like($src, qr/if \[ -d \/sys\/firmware\/efi \]; then\s+sed .*?set_sles11_uefi_bootloader/s, 'UEFI default partitioning applies bootloader helper'); - -done_testing(); diff --git a/xCAT-test/unit/statelite_add_ssh.t b/xCAT-test/unit/statelite_add_ssh.t deleted file mode 100644 index b3dc6ddbe..000000000 --- a/xCAT-test/unit/statelite_add_ssh.t +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env perl -use strict; -use warnings; -use Test::More; - -my $script_path = defined $ENV{XCATROOT} ? "$ENV{XCATROOT}/share/xcat/netboot/add-on/statelite/add_ssh" : ''; -$script_path = "xCAT-server/share/xcat/netboot/add-on/statelite/add_ssh" - unless -f $script_path; - -plan skip_all => "add_ssh not found" unless -f $script_path; - -my $script = do { local $/; open my $fh, '<', $script_path or die $!; <$fh> }; - -like($script, qr/MaxStartups 100:30:200/, 'add_ssh caps MaxStartups below systemd soft nofile limit'); -unlike($script, qr/echo\s+"MaxStartups 1024"/, 'add_ssh does not force MaxStartups 1024'); - -done_testing(); diff --git a/xCAT/postscripts/remoteshell b/xCAT/postscripts/remoteshell index 3850585f9..04fc145b4 100755 --- a/xCAT/postscripts/remoteshell +++ b/xCAT/postscripts/remoteshell @@ -631,20 +631,6 @@ fi #if the service restart with "service/systemctl" failed #try to kill the process and start if [ "$?" != "0" ];then - PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs` - if [ -n "$PIDLIST" ]; then - kill -9 $PIDLIST - waited=0 - while [ $waited -lt 10 ]; do - alive=0 - for pid in $PIDLIST; do - if kill -0 $pid 2>/dev/null; then alive=1; fi - done - if [ $alive -eq 0 ]; then break; fi - sleep 1 - waited=`expr $waited + 1` - done - fi - /usr/sbin/sshd + xcat_restart_sshd_after_failed_service_restart fi kill -9 $CREDPID diff --git a/xCAT/postscripts/xcatdsklspost b/xCAT/postscripts/xcatdsklspost index 13110575d..6e2ad718e 100755 --- a/xCAT/postscripts/xcatdsklspost +++ b/xCAT/postscripts/xcatdsklspost @@ -19,7 +19,12 @@ # ##################################################### -[ -f "/xcatpost/xcatlib.sh" ] && . /xcatpost/xcatlib.sh +if [ -f "/xcatpost/xcatlib.sh" ]; then + . /xcatpost/xcatlib.sh +else + str_dir_name=`dirname $0` + [ -f "$str_dir_name/xcatlib.sh" ] && . "$str_dir_name/xcatlib.sh" +fi if [ -f /xcatpost/mypostscript.post ]; then XCATDEBUGMODE=`grep 'XCATDEBUGMODE=' /xcatpost/mypostscript.post | cut -d= -f2 | tr -d \'\" | tr A-Z a-z` @@ -83,66 +88,6 @@ echolog() } -download_postscripts() -{ - server=$1 - if [ -z $server ]; then - return 1; - fi - - # Do not override the parameter --installdir - if [ -z "$INSTALLDIR" ]; then - if [ -f /opt/xcat/xcatinfo ]; then - INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2` - fi - if [ -z "$INSTALLDIR" ]; then - INSTALLDIR="/install" - fi - fi - echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/" - max_retries=5 - retry=0 - rc=1 # this is a fail return - while [ 0 -eq 0 ]; do - if [ -e "$xcatpost" ]; then - rm -rf "$xcatpost" - fi - - # These dispatcher scripts are not needed by the legacy netboot post - # path. Newer wget parses HTML-looking regex strings inside downloaded - # scripts and fails the whole recursive download on bogus URLs. - export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> /tmp/wget.log - rc=$? - if [ $rc -eq 0 ]; then - # return from wget was 0 but some OS do not return errors, so we - # have additional checks for - # failed: Connection httpd not running - # 404: Not Found - if directory does not exist - grep -i -E "... failed: Connection refused.$" /tmp/wget.log - rc1=$? - grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log - rc2=$? - # check to see no errors at all, grep returns 1 - if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then - echolog "debug" "postscripts are downloaded from $server successfully." - return 0 - fi - fi - - retry=$(($retry+1)) - echolog "debug" "download_postscripts retry $retry" - if [ $retry -eq $max_retries ]; then - echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries." - break - fi - - SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}') - sleep $SLI - done - return $rc -} - - download_mypostscript() { server=$1 diff --git a/xCAT/postscripts/xcatlib.sh b/xCAT/postscripts/xcatlib.sh index b06838589..8e2dd1431 100755 --- a/xCAT/postscripts/xcatlib.sh +++ b/xCAT/postscripts/xcatlib.sh @@ -833,6 +833,107 @@ function msgutil { msgutil_r "" "$@" } +function xcat_download_postscripts { + local server="$1" + local install_dir="${2:-${INSTALLDIR:-/install}}" + local postroot="${3:-/$xcatpost}" + local log_file="${4:-/tmp/wget.log}" + + export LANG=C + wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent "http://$server$install_dir/postscripts/" -P "$postroot" 2> "$log_file" +} + +function download_postscripts { + server=$1 + if [ -z $server ]; then + return 1; + fi + + # Do not override the parameter --installdir + if [ -z "$INSTALLDIR" ]; then + if [ -f /opt/xcat/xcatinfo ]; then + INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2` + fi + if [ -z "$INSTALLDIR" ]; then + INSTALLDIR="/install" + fi + fi + echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/" + max_retries=5 + retry=0 + rc=1 # this is a fail return + while [ 0 -eq 0 ]; do + if [ -e "$xcatpost" ]; then + rm -rf "$xcatpost" + fi + + xcat_download_postscripts "$server" "$INSTALLDIR" "/$xcatpost" "/tmp/wget.log" + rc=$? + if [ $rc -eq 0 ]; then + # return from wget was 0 but some OS do not return errors, so we + # have additional checks for + # failed: Connection httpd not running + # 404: Not Found - if directory does not exist + grep -i -E "... failed: Connection refused.$" /tmp/wget.log + rc1=$? + grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log + rc2=$? + # check to see no errors at all, grep returns 1 + if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then + echolog "debug" "postscripts are downloaded from $server successfully." + return 0 + fi + fi + + retry=$(($retry+1)) + echolog "debug" "download_postscripts retry $retry" + if [ $retry -eq $max_retries ]; then + echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries." + break + fi + + SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}') + sleep $SLI + done + return $rc +} + +function xcat_wait_for_processes_to_exit { + local pidlist="$1" + local max_wait="${2:-10}" + local waited=0 + local alive + local pid + + while [ $waited -lt $max_wait ]; do + alive=0 + for pid in $pidlist; do + if kill -0 $pid 2>/dev/null; then + alive=1 + fi + done + if [ $alive -eq 0 ]; then + return 0 + fi + sleep 1 + waited=`expr $waited + 1` + done + + return 1 +} + +function xcat_restart_sshd_after_failed_service_restart { + local sshd_cmd="${1:-/usr/sbin/sshd}" + local PIDLIST + + PIDLIST=`ps aux | grep -v grep | grep "/usr/sbin/sshd"|awk -F" " '{print $2}'|xargs` + if [ -n "$PIDLIST" ]; then + kill -9 $PIDLIST + xcat_wait_for_processes_to_exit "$PIDLIST" + fi + $sshd_cmd +} + function fetch_mypostscript { local postroot postroot=$1 From 58c030a8f38c178fc9bbda7bdbae0e7d83069fb0 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:03:34 -0300 Subject: [PATCH 2/4] test(xcat-core): Move BATS tests beside unit tests Shell unit tests were introduced under xCAT-test/autotest/bats, but the existing source-tree unit suite already lives directly under xCAT-test/unit. Keeping the BATS suite under xCAT-test/bats makes the unit-test layout consistent and keeps autotest reserved for xcattest-driven functional cases. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- github_action_xcat_test.pl | 8 ++++---- xCAT-test/README.md | 7 ++++--- xCAT-test/{autotest => }/bats/README.md | 4 ++-- xCAT-test/{autotest => }/bats/genesis_ib_modules.bats | 0 .../{autotest => }/bats/go_xcat_common_repository.bats | 0 xCAT-test/{autotest => }/bats/go_xcat_el_repo_check.bats | 0 xCAT-test/{autotest => }/bats/helpers/go_xcat.bash | 2 +- xCAT-test/{autotest => }/bats/helpers/shell_source.bash | 2 +- .../{autotest => }/bats/post_xcat_download_policy.bats | 0 xCAT-test/{autotest => }/bats/remoteshell_restart.bats | 0 xCAT-test/{autotest => }/bats/sles_pre_script.bats | 0 xCAT-test/{autotest => }/bats/statelite_add_ssh.bats | 0 xCAT-test/unit/README.md | 2 +- 13 files changed, 13 insertions(+), 12 deletions(-) rename xCAT-test/{autotest => }/bats/README.md (93%) rename xCAT-test/{autotest => }/bats/genesis_ib_modules.bats (100%) rename xCAT-test/{autotest => }/bats/go_xcat_common_repository.bats (100%) rename xCAT-test/{autotest => }/bats/go_xcat_el_repo_check.bats (100%) rename xCAT-test/{autotest => }/bats/helpers/go_xcat.bash (93%) rename xCAT-test/{autotest => }/bats/helpers/shell_source.bash (98%) rename xCAT-test/{autotest => }/bats/post_xcat_download_policy.bats (100%) rename xCAT-test/{autotest => }/bats/remoteshell_restart.bats (100%) rename xCAT-test/{autotest => }/bats/sles_pre_script.bats (100%) rename xCAT-test/{autotest => }/bats/statelite_add_ssh.bats (100%) diff --git a/github_action_xcat_test.pl b/github_action_xcat_test.pl index 42d565885..4a47d4c7d 100644 --- a/github_action_xcat_test.pl +++ b/github_action_xcat_test.pl @@ -313,7 +313,7 @@ sub preserve_source_tree{ @output = runcmd("find $unitsrc/xCAT-test/unit -name '*.t' | wc -l"); my $perl_count = $output[0]; - @output = runcmd("find $unitsrc/xCAT-test/autotest/bats -name '*.bats' 2>/dev/null | wc -l"); + @output = runcmd("find $unitsrc/xCAT-test/bats -name '*.bats' 2>/dev/null | wc -l"); my $bats_count = $output[0]; print "[preserve_source_tree] preserved $srcdir in $unitsrc ($perl_count Perl unit tests, $bats_count BATS tests)\n"; return 0; @@ -471,21 +471,21 @@ sub run_unit_tests{ #-------------------------------------------------------- # Fuction name: run_bats_tests -# Description: Run shell-script unit tests under xCAT-test/autotest/bats. +# Description: Run shell-script unit tests under xCAT-test/bats. # Runs against the pre-build copy of the source tree taken by # preserve_source_tree(), like the Perl unit tests. # Attributes: # Return code: 0 all tests passed, 1 otherwise #-------------------------------------------------------- sub run_bats_tests{ - my $testdir = "$unitsrc/xCAT-test/autotest/bats"; + my $testdir = "$unitsrc/xCAT-test/bats"; my @output = runcmd("find $testdir -name '*.bats' -print -quit 2>/dev/null"); if (!@output) { print "[run_bats_tests] no BATS tests found under $testdir\n"; return 0; } - my $cmd = "cd $unitsrc && bats -r xCAT-test/autotest/bats"; + my $cmd = "cd $unitsrc && bats -r xCAT-test/bats"; print "[run_bats_tests] running $cmd\n"; @output = runcmd("$cmd"); print Dumper \@output; diff --git a/xCAT-test/README.md b/xCAT-test/README.md index 7406be0d4..01fbe54d2 100644 --- a/xCAT-test/README.md +++ b/xCAT-test/README.md @@ -6,7 +6,8 @@ language: | Test type | Location | Runner | | --------- | -------- | ------ | | Perl unit tests | `xCAT-test/unit/*.t` | `prove -r xCAT-test/unit` | -| Shell unit tests | `xCAT-test/autotest/bats/*.bats` | `bats -r xCAT-test/autotest/bats` | +| Shell unit tests | `xCAT-test/bats/*.bats` | `bats -r xCAT-test/bats` | +| CLI functional tests | `xCAT-test/autotest/testcase/` and `xCAT-test/autotest/bundle/` | `xcattest -f -t ` or `xcattest -f -b ` | Use Perl `.t` tests for Perl modules, Perl scripts, templates, and repository artifacts. Use BATS tests for shell-script behavior that can be exercised from @@ -14,7 +15,7 @@ the checkout by sourcing a shell library or script and shadowing external commands. Shell behavior should not be tested by Perl tests that grep shell source. Put -those tests under `xCAT-test/autotest/bats` instead. +those tests under `xCAT-test/bats` instead. -See `unit/README.md` and `autotest/bats/README.md` for the detailed rules for +See `unit/README.md` and `bats/README.md` for the detailed rules for each unit-test suite. diff --git a/xCAT-test/autotest/bats/README.md b/xCAT-test/bats/README.md similarity index 93% rename from xCAT-test/autotest/bats/README.md rename to xCAT-test/bats/README.md index 29a1403e8..ac65379ab 100644 --- a/xCAT-test/autotest/bats/README.md +++ b/xCAT-test/bats/README.md @@ -1,9 +1,9 @@ -# xCAT-test/autotest/bats +# xCAT-test/bats Shell-script unit tests live here and run with: ```bash -bats -r xCAT-test/autotest/bats +bats -r xCAT-test/bats ``` The GitHub Actions `xcat_test` workflow runs this command after the Perl `.t` diff --git a/xCAT-test/autotest/bats/genesis_ib_modules.bats b/xCAT-test/bats/genesis_ib_modules.bats similarity index 100% rename from xCAT-test/autotest/bats/genesis_ib_modules.bats rename to xCAT-test/bats/genesis_ib_modules.bats diff --git a/xCAT-test/autotest/bats/go_xcat_common_repository.bats b/xCAT-test/bats/go_xcat_common_repository.bats similarity index 100% rename from xCAT-test/autotest/bats/go_xcat_common_repository.bats rename to xCAT-test/bats/go_xcat_common_repository.bats diff --git a/xCAT-test/autotest/bats/go_xcat_el_repo_check.bats b/xCAT-test/bats/go_xcat_el_repo_check.bats similarity index 100% rename from xCAT-test/autotest/bats/go_xcat_el_repo_check.bats rename to xCAT-test/bats/go_xcat_el_repo_check.bats diff --git a/xCAT-test/autotest/bats/helpers/go_xcat.bash b/xCAT-test/bats/helpers/go_xcat.bash similarity index 93% rename from xCAT-test/autotest/bats/helpers/go_xcat.bash rename to xCAT-test/bats/helpers/go_xcat.bash index b863c963f..ee4ec3c44 100644 --- a/xCAT-test/autotest/bats/helpers/go_xcat.bash +++ b/xCAT-test/bats/helpers/go_xcat.bash @@ -2,7 +2,7 @@ go_xcat_default_source() { - printf '%s\n' "${BATS_TEST_DIRNAME}/../../../xCAT-server/share/xcat/tools/go-xcat" + printf '%s\n' "${BATS_TEST_DIRNAME}/../../xCAT-server/share/xcat/tools/go-xcat" } go_xcat_require_source() diff --git a/xCAT-test/autotest/bats/helpers/shell_source.bash b/xCAT-test/bats/helpers/shell_source.bash similarity index 98% rename from xCAT-test/autotest/bats/helpers/shell_source.bash rename to xCAT-test/bats/helpers/shell_source.bash index f76ed55d3..61a0bea8d 100644 --- a/xCAT-test/autotest/bats/helpers/shell_source.bash +++ b/xCAT-test/bats/helpers/shell_source.bash @@ -2,7 +2,7 @@ repo_root() { - printf '%s\n' "${BATS_TEST_DIRNAME}/../../.." + printf '%s\n' "${BATS_TEST_DIRNAME}/../.." } repo_path() diff --git a/xCAT-test/autotest/bats/post_xcat_download_policy.bats b/xCAT-test/bats/post_xcat_download_policy.bats similarity index 100% rename from xCAT-test/autotest/bats/post_xcat_download_policy.bats rename to xCAT-test/bats/post_xcat_download_policy.bats diff --git a/xCAT-test/autotest/bats/remoteshell_restart.bats b/xCAT-test/bats/remoteshell_restart.bats similarity index 100% rename from xCAT-test/autotest/bats/remoteshell_restart.bats rename to xCAT-test/bats/remoteshell_restart.bats diff --git a/xCAT-test/autotest/bats/sles_pre_script.bats b/xCAT-test/bats/sles_pre_script.bats similarity index 100% rename from xCAT-test/autotest/bats/sles_pre_script.bats rename to xCAT-test/bats/sles_pre_script.bats diff --git a/xCAT-test/autotest/bats/statelite_add_ssh.bats b/xCAT-test/bats/statelite_add_ssh.bats similarity index 100% rename from xCAT-test/autotest/bats/statelite_add_ssh.bats rename to xCAT-test/bats/statelite_add_ssh.bats diff --git a/xCAT-test/unit/README.md b/xCAT-test/unit/README.md index 2fd67bcb6..daff2691c 100644 --- a/xCAT-test/unit/README.md +++ b/xCAT-test/unit/README.md @@ -46,7 +46,7 @@ so putting a test in `integration/` does not cost it CI coverage. What differs i each suite is allowed to depend on, and that unit tests also run standalone from a bare checkout with no xCAT at all. -Shell-script unit tests belong in [`../autotest/bats`](../autotest/bats/README.md) +Shell-script unit tests belong in [`../bats`](../bats/README.md) and run with BATS. Do not add Perl `.t` tests that grep shell source when the behavior can be exercised by sourcing a shell library or script and shadowing the external commands it calls. From cc36d25c354dc444e0ecbae131c4a5cf2111a8b8 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 8 Sep 2026 13:10:13 -0300 Subject: [PATCH 3/4] test(xcat-core): Keep xcatdsklspost download local xcatdsklspost can run before xcatlib.sh is available beside it in stateless and statelite image contexts. Moving download_postscripts into xcatlib.sh could leave the legacy postscript without its callee when it is copied by itself into the image. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/bats/post_xcat_download_policy.bats | 14 ++-- xCAT/postscripts/xcatdsklspost | 64 ++++++++++++++++++ xCAT/postscripts/xcatlib.sh | 65 ------------------- 3 files changed, 71 insertions(+), 72 deletions(-) diff --git a/xCAT-test/bats/post_xcat_download_policy.bats b/xCAT-test/bats/post_xcat_download_policy.bats index 4ec06387a..ec643410c 100644 --- a/xCAT-test/bats/post_xcat_download_policy.bats +++ b/xCAT-test/bats/post_xcat_download_policy.bats @@ -5,10 +5,10 @@ load 'helpers/shell_source' setup() { SCRIPT_LIB="$(repo_path 'xCAT-server/share/xcat/install/scripts/scriptlib')" - XCATLIB="$(repo_path 'xCAT/postscripts/xcatlib.sh')" + XCATDSKLSPOST="$(repo_path 'xCAT/postscripts/xcatdsklspost')" [ -r "$SCRIPT_LIB" ] || skip "$SCRIPT_LIB is required" - [ -r "$XCATLIB" ] || skip "$XCATLIB is required" - export SCRIPT_LIB XCATLIB + [ -r "$XCATDSKLSPOST" ] || skip "$XCATDSKLSPOST is required" + export SCRIPT_LIB XCATDSKLSPOST } capture_install_scriptlib_wget() @@ -25,7 +25,7 @@ capture_install_scriptlib_wget() xcat_download_postscripts "192.0.2.10:80" "/install" "/xcatpost" "$wget_log" } -capture_xcatlib_wget() +capture_xcatdsklspost_wget() { local wget_log="$1" @@ -45,7 +45,7 @@ capture_xcatlib_wget() return 0 } - source "$XCATLIB" + XCATDSKLSPOST_SOURCE_ONLY=1 source "$XCATDSKLSPOST" download_postscripts 192.0.2.10:80 } @@ -67,10 +67,10 @@ assert_download_policy() assert_download_policy "$(read_file_or_empty "$wget_log")" } -@test "postscript xcatlib recursive download rejects dispatcher scripts" { +@test "xcatdsklspost recursive download rejects dispatcher scripts" { local wget_log="${BATS_TEST_TMPDIR}/xcatdsklspost-wget.log" - run capture_xcatlib_wget "$wget_log" + run capture_xcatdsklspost_wget "$wget_log" [ "$status" -eq 0 ] assert_download_policy "$(read_file_or_empty "$wget_log")" } diff --git a/xCAT/postscripts/xcatdsklspost b/xCAT/postscripts/xcatdsklspost index 6e2ad718e..40d1dde3d 100755 --- a/xCAT/postscripts/xcatdsklspost +++ b/xCAT/postscripts/xcatdsklspost @@ -127,6 +127,66 @@ download_mypostscript() } +download_postscripts() +{ + server=$1 + if [ -z $server ]; then + return 1; + fi + + # Do not override the parameter --installdir + if [ -z "$INSTALLDIR" ]; then + if [ -f /opt/xcat/xcatinfo ]; then + INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2` + fi + if [ -z "$INSTALLDIR" ]; then + INSTALLDIR="/install" + fi + fi + echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/" + max_retries=5 + retry=0 + rc=1 # this is a fail return + while [ 0 -eq 0 ]; do + if [ -e "$xcatpost" ]; then + rm -rf "$xcatpost" + fi + + # These dispatcher scripts are not needed by the legacy netboot post + # path. Newer wget parses HTML-looking regex strings inside downloaded + # scripts and fails the whole recursive download on bogus URLs. + export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> /tmp/wget.log + rc=$? + if [ $rc -eq 0 ]; then + # return from wget was 0 but some OS do not return errors, so we + # have additional checks for + # failed: Connection httpd not running + # 404: Not Found - if directory does not exist + grep -i -E "... failed: Connection refused.$" /tmp/wget.log + rc1=$? + grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log + rc2=$? + # check to see no errors at all, grep returns 1 + if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then + echolog "debug" "postscripts are downloaded from $server successfully." + return 0 + fi + fi + + retry=$(($retry+1)) + echolog "debug" "download_postscripts retry $retry" + if [ $retry -eq $max_retries ]; then + echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries." + break + fi + + SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}') + sleep $SLI + done + return $rc +} + + # pmatch determines if 1st argument string is matched by 2nd argument pattern pmatch () @@ -161,6 +221,10 @@ parsehttpserver () fi } +if [ "$XCATDSKLSPOST_SOURCE_ONLY" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + # Main # parse the arguments log_label="xcat.updatenode" diff --git a/xCAT/postscripts/xcatlib.sh b/xCAT/postscripts/xcatlib.sh index 8e2dd1431..6dd27ded5 100755 --- a/xCAT/postscripts/xcatlib.sh +++ b/xCAT/postscripts/xcatlib.sh @@ -833,71 +833,6 @@ function msgutil { msgutil_r "" "$@" } -function xcat_download_postscripts { - local server="$1" - local install_dir="${2:-${INSTALLDIR:-/install}}" - local postroot="${3:-/$xcatpost}" - local log_file="${4:-/tmp/wget.log}" - - export LANG=C - wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent "http://$server$install_dir/postscripts/" -P "$postroot" 2> "$log_file" -} - -function download_postscripts { - server=$1 - if [ -z $server ]; then - return 1; - fi - - # Do not override the parameter --installdir - if [ -z "$INSTALLDIR" ]; then - if [ -f /opt/xcat/xcatinfo ]; then - INSTALLDIR=`grep 'INSTALLDIR' /opt/xcat/xcatinfo |cut -d= -f2` - fi - if [ -z "$INSTALLDIR" ]; then - INSTALLDIR="/install" - fi - fi - echolog "debug" "trying to download postscripts from http://$server$INSTALLDIR/postscripts/" - max_retries=5 - retry=0 - rc=1 # this is a fail return - while [ 0 -eq 0 ]; do - if [ -e "$xcatpost" ]; then - rm -rf "$xcatpost" - fi - - xcat_download_postscripts "$server" "$INSTALLDIR" "/$xcatpost" "/tmp/wget.log" - rc=$? - if [ $rc -eq 0 ]; then - # return from wget was 0 but some OS do not return errors, so we - # have additional checks for - # failed: Connection httpd not running - # 404: Not Found - if directory does not exist - grep -i -E "... failed: Connection refused.$" /tmp/wget.log - rc1=$? - grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log - rc2=$? - # check to see no errors at all, grep returns 1 - if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then - echolog "debug" "postscripts are downloaded from $server successfully." - return 0 - fi - fi - - retry=$(($retry+1)) - echolog "debug" "download_postscripts retry $retry" - if [ $retry -eq $max_retries ]; then - echolog "debug" "failed to download postscripts from http://$server$INSTALLDIR/postscripts/ after several retries." - break - fi - - SLI=$(awk 'BEGIN{srand(); printf("%d\n",rand()*20)}') - sleep $SLI - done - return $rc -} - function xcat_wait_for_processes_to_exit { local pidlist="$1" local max_wait="${2:-10}" From a8e770c42ebd340cf5f35e7ee2497720f45eab38 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:28:04 -0300 Subject: [PATCH 4/4] fix(xcat-core): Prevent BATS checks from missing failures Negative checks could pass when a later command succeeded. The diskless test also read host state and wrote to the host wget log. The SSH fallback test did not prove that the restart waited for the killed process. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/bats/post_xcat_download_policy.bats | 26 +++++++--- xCAT-test/bats/remoteshell_restart.bats | 33 ++++++++---- xCAT-test/bats/sles_pre_script.bats | 8 +-- xCAT/postscripts/xcatdsklspost | 51 ++++++++++--------- 4 files changed, 73 insertions(+), 45 deletions(-) diff --git a/xCAT-test/bats/post_xcat_download_policy.bats b/xCAT-test/bats/post_xcat_download_policy.bats index ec643410c..41810bb97 100644 --- a/xCAT-test/bats/post_xcat_download_policy.bats +++ b/xCAT-test/bats/post_xcat_download_policy.bats @@ -28,25 +28,37 @@ capture_install_scriptlib_wget() capture_xcatdsklspost_wget() { local wget_log="$1" + local host_init_log="${BATS_TEST_TMPDIR}/host-init.log" + local download_log="${BATS_TEST_TMPDIR}/xcatdsklspost-wget-errors.log" + + cat() { printf 'cat %s\n' "$*" >>"$host_init_log"; return 1; } + grep() + { + printf 'grep %s\n' "$*" >>"$host_init_log" + command grep "$@" + } + dirname() { printf 'dirname %s\n' "$*" >>"$host_init_log"; return 1; } + + XCATDSKLSPOST_SOURCE_ONLY=1 + XCAT_WGET_LOG="$download_log" + source "$XCATDSKLSPOST" + [ ! -e "$host_init_log" ] || return 1 + [ "$XCAT_WGET_LOG" = "$download_log" ] || return 1 + unset -f cat grep dirname xcatpost="${BATS_TEST_TMPDIR}/xcatpost" INSTALLDIR=/install - echolog() { :; } sleep() { :; } - grep() - { - [ "${*: -1}" = "/tmp/wget.log" ] && return 1 - command grep "$@" - } wget() { printf '%s\n' "$*" >"$wget_log" + printf '%s\n' 'mock wget stderr' >&2 return 0 } - XCATDSKLSPOST_SOURCE_ONLY=1 source "$XCATDSKLSPOST" download_postscripts 192.0.2.10:80 + [ "$(read_file_or_empty "$download_log")" = "mock wget stderr" ] } assert_download_policy() diff --git a/xCAT-test/bats/remoteshell_restart.bats b/xCAT-test/bats/remoteshell_restart.bats index 1ddce6cfc..b4727d7fc 100644 --- a/xCAT-test/bats/remoteshell_restart.bats +++ b/xCAT-test/bats/remoteshell_restart.bats @@ -1,5 +1,7 @@ #!/usr/bin/env bats +bats_require_minimum_version 1.5.0 + load 'helpers/shell_source' setup() @@ -11,6 +13,8 @@ setup() run_restart_fallback() { + local poll_count=0 + ps() { cat <<'EOF' @@ -19,14 +23,23 @@ EOF } kill() { - printf '%s\n' "$*" >>"$KILL_LOG" - [ "$1" = "-0" ] && return 1 - return 0 + if [ "$1" = "-9" ]; then + printf 'kill %s\n' "$*" >>"$EVENT_LOG" + return 0 + fi + + poll_count=$((poll_count + 1)) + if [ "$poll_count" -eq 1 ]; then + printf 'poll %s alive\n' "$2" >>"$EVENT_LOG" + return 0 + fi + printf 'poll %s gone\n' "$2" >>"$EVENT_LOG" + return 1 } sleep() { :; } sshd() { - printf '%s\n' start >>"$SSHD_LOG" + printf '%s\n' start >>"$EVENT_LOG" } source "$XCATLIB" @@ -44,16 +57,14 @@ run_wait_for_processes() return 0 } -@test "remoteshell restart fallback sends an uncatchable signal before starting sshd" { - KILL_LOG="${BATS_TEST_TMPDIR}/kill.log" - SSHD_LOG="${BATS_TEST_TMPDIR}/sshd.log" - export KILL_LOG SSHD_LOG +@test "remoteshell restart fallback kills, waits, then starts sshd" { + EVENT_LOG="${BATS_TEST_TMPDIR}/events.log" + export EVENT_LOG run run_restart_fallback [ "$status" -eq 0 ] - grep -Fxq -- '-9 4321' "$KILL_LOG" - ! grep -Eq '^9( |$)' "$KILL_LOG" - [ "$(read_file_or_empty "$SSHD_LOG")" = "start" ] + [ "$(read_file_or_empty "$EVENT_LOG")" = $'kill -9 4321\npoll 4321 alive\npoll 4321 gone\nstart' ] + run -1 grep -Eq '^kill 9( |$)' "$EVENT_LOG" } @test "remoteshell wait loop reports a still-running process and gives up" { diff --git a/xCAT-test/bats/sles_pre_script.bats b/xCAT-test/bats/sles_pre_script.bats index f35153200..bd8387ab0 100644 --- a/xCAT-test/bats/sles_pre_script.bats +++ b/xCAT-test/bats/sles_pre_script.bats @@ -1,5 +1,7 @@ #!/usr/bin/env bats +bats_require_minimum_version 1.5.0 + load 'helpers/shell_source' setup() @@ -26,9 +28,9 @@ EOF run set_sles11_uefi_bootloader "$cmdline" "$profile" [ "$status" -eq 0 ] grep -Fxq 'elilo' "$profile" - ! grep -q 'mbr' "$profile" - ! grep -q 'mbr' "$profile" + run -1 grep -q '> /tmp/wget.log + wget -N --waitretry=10 --random-wait -T 60 http://$server$TFTPDIR/mypostscripts/mypostscript.$node -P /$xcatpost 2>> "$XCAT_WGET_LOG" rc=$? # if no error and the file was downloaded if [ $rc -eq 0 ] && [ -f /$xcatpost/mypostscript.$node ]; then @@ -155,16 +158,16 @@ download_postscripts() # These dispatcher scripts are not needed by the legacy netboot post # path. Newer wget parses HTML-looking regex strings inside downloaded # scripts and fails the whole recursive download on bogus URLs. - export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> /tmp/wget.log + export LANG=C; wget -l inf -nH -N -r --waitretry=10 --random-wait -e robots=off -T 60 -nH --cut-dirs=2 --reject "index.html*,post.xcat.ng,post.xcat.rhels10" --no-parent http://$server$INSTALLDIR/postscripts/ -P /$xcatpost 2> "$XCAT_WGET_LOG" rc=$? if [ $rc -eq 0 ]; then # return from wget was 0 but some OS do not return errors, so we # have additional checks for # failed: Connection httpd not running # 404: Not Found - if directory does not exist - grep -i -E "... failed: Connection refused.$" /tmp/wget.log + grep -i -E "... failed: Connection refused.$" "$XCAT_WGET_LOG" rc1=$? - grep -i -E "ERROR 404: Not Found.$" /tmp/wget.log + grep -i -E "ERROR 404: Not Found.$" "$XCAT_WGET_LOG" rc2=$? # check to see no errors at all, grep returns 1 if [ $rc1 -eq 1 ] && [ $rc2 -eq 1 ]; then