From 3582c6ab195c538796d1f869a5f81b196e0fe2b9 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 30 Sep 2026 19:59:41 -0300 Subject: [PATCH 1/3] fix(xcat-core): xcattest runs no test case on an openEuler management node xcattest reports "Test case has an invalid OS option - Linux" and then "There is no valid case to run", so every case is skipped on openEuler. Recorded on an openEuler 24.03 LTS SP4 management node with xCAT 2.20.0: both flat provisioning cases were rejected and the run measured nothing. Two causes, both in the os: comparison in xCAT-test/xcattest. get_current_os takes the /etc/os-release branch, finds no sles, and falls off the end of that branch, so it returns the exit status of the failed grep, 256, which names no distribution. And a case marked os:Linux expands to exactly rhels, sles and ubuntu, so openEuler would be rejected even by a correct get_current_os. Both halves move into xCAT::Test::OS. xcattest finds it beside itself and the package installs it at /lib/perl. current_os takes a path prefix, so a test describes a system by writing release files into a scratch tree. openEuler is named in both halves and nothing else changes. xCAT-test/unit/xcattest_os_selection.t covers it. Removing openeuler from linux_aliases fails two checks, and removing the openEuler branch from current_os fails three. A further case gives a root both /etc/redhat-release and an openEuler /etc/os-release and asserts rhels10, so the precedence is named: openEuler ships neither that file nor /etc/lsb-release. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/lib/xCAT/Test/OS.pm | 87 +++++++++++++++++++ .../autotest_check_lines_are_understood.t | 4 +- xCAT-test/unit/xcattest_os_selection.t | 76 ++++++++++++++++ xCAT-test/unit/xcattest_report_every_check.t | 4 +- xCAT-test/xCAT-test.spec | 4 + xCAT-test/xcattest | 28 ++---- 6 files changed, 178 insertions(+), 25 deletions(-) create mode 100644 xCAT-test/lib/xCAT/Test/OS.pm create mode 100644 xCAT-test/unit/xcattest_os_selection.t diff --git a/xCAT-test/lib/xCAT/Test/OS.pm b/xCAT-test/lib/xCAT/Test/OS.pm new file mode 100644 index 000000000..7bb05c053 --- /dev/null +++ b/xCAT-test/lib/xCAT/Test/OS.pm @@ -0,0 +1,87 @@ +package xCAT::Test::OS; + +use strict; +use warnings; + +#----------------------------------------------------------------------------- + +=head1 NAME + +xCAT::Test::OS - the operating system names xcattest matches a case against + +=head1 DESCRIPTION + +A test case declares the distributions it runs on in its C attribute, and xcattest compares +that list against the distribution it is running on. Both halves of the comparison live here, so a +distribution is named in one place and a unit test can call them. + +=cut + +#----------------------------------------------------------------------------- + +#--- +# =head3 linux_aliases +# Descriptions: the distributions a case's `os:Linux` stands for. +# Arguments: none +# Returns: the list of os names +#--- +sub linux_aliases { + return qw(rhels sles ubuntu openeuler); +} + +#--- +# =head3 current_os +# Descriptions: the os name of the running system, in the spelling a case's `os:` attribute uses. +# Arguments: $root - a path prefix, for tests +# Returns: an os name, or undef +#--- +sub current_os { + my ($root) = @_; + $root = '' unless defined $root; + + if (-f "$root/etc/redhat-release") { + my $text = _slurp("$root/etc/redhat-release"); + my ($major) = (defined($text) && $text =~ /(\d+)\.(\d*)/) ? ($1) : (''); + return "rhels$major"; + } + return 'ubuntu' if -f "$root/etc/lsb-release"; + + if (-f "$root/etc/os-release") { + my $text = _slurp("$root/etc/os-release") // ''; + return 'sles' if $text =~ /sles/; + if ($text =~ /^ID\s*=\s*"?openeuler"?\s*$/mi) { + my ($version) = $text =~ /^VERSION\s*=\s*"?([^"\n]+?)"?\s*$/mi; + my $release = _openeuler_release($version); + return defined($release) ? "openeuler$release" : 'openeuler'; + } + return undef; + } + return 'sles' if -f "$root/etc/SuSE-release"; + return 'aix'; +} + +#--- +# =head3 _openeuler_release +# Descriptions: the release token openEuler's osimage names carry, from an os-release VERSION. +# Arguments: $version - the VERSION value of /etc/os-release +# Returns: a release token, or undef +#--- +sub _openeuler_release { + my ($version) = @_; + return undef unless defined $version; + $version =~ s/^\s+|\s+$//g; + my ($release, $sp) = $version =~ /\A((?:20|22|24)\.03)(?:\s*\(LTS(?:-SP([1-9][0-9]*))?\))?\z/i + or return undef; + return $release . (defined($sp) ? "sp$sp" : ''); +} + +sub _slurp { + my ($path) = @_; + open my $fh, '<', $path or return undef; + local $/; + my $text = <$fh>; + close $fh; + return $text; +} + +1; diff --git a/xCAT-test/unit/autotest_check_lines_are_understood.t b/xCAT-test/unit/autotest_check_lines_are_understood.t index 1f4e54861..40b6ea317 100644 --- a/xCAT-test/unit/autotest_check_lines_are_understood.t +++ b/xCAT-test/unit/autotest_check_lines_are_understood.t @@ -54,9 +54,11 @@ for my $file (sort keys %checks) { # xcattest derives its result directory from the location of the program, so the copy under the # scratch tree keeps every file the run writes inside that tree. my $root = tempdir(CLEANUP => 1); -make_path("$root/bin", "$root/cases"); +make_path("$root/bin", "$root/cases", "$root/lib/perl/xCAT/Test"); copy($program, "$root/bin/xcattest") or die("copy xcattest: $!"); chmod 0755, "$root/bin/xcattest"; +copy("$FindBin::Bin/../lib/xCAT/Test/OS.pm", "$root/lib/perl/xCAT/Test/OS.pm") + or die("copy xCAT/Test/OS.pm: $!"); open(my $fixture_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!"); print $fixture_fh $fixture; close($fixture_fh) or die("close the fixture case: $!"); diff --git a/xCAT-test/unit/xcattest_os_selection.t b/xCAT-test/unit/xcattest_os_selection.t new file mode 100644 index 000000000..eed16fc12 --- /dev/null +++ b/xCAT-test/unit/xcattest_os_selection.t @@ -0,0 +1,76 @@ +#!/usr/bin/env perl +use strict; +use warnings; +use File::Path qw(make_path); +use File::Temp qw(tempdir); +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; +use xCAT::Test::OS; + +sub a_system { + my (%files) = @_; + my $root = tempdir(CLEANUP => 1); + make_path("$root/etc"); + for my $name (sort keys %files) { + open my $fh, '>', "$root/etc/$name" or die "write $root/etc/$name: $!"; + print {$fh} $files{$name}; + close $fh or die "close $root/etc/$name: $!"; + } + return $root; +} + +my $OPENEULER_OS_RELEASE = <<'REL'; +NAME="openEuler" +VERSION="24.03 (LTS-SP4)" +ID="openEuler" +VERSION_ID="24.03" +PRETTY_NAME="openEuler 24.03 (LTS-SP4)" +REL + + +is(xCAT::Test::OS::current_os(a_system('os-release' => $OPENEULER_OS_RELEASE, + 'openEuler-release' => "openEuler release 24.03 (LTS-SP4)\n", + 'system-release' => "openEuler release 24.03 (LTS-SP4)\n")), + 'openeuler24.03sp4', + 'openEuler 24.03 SP4 is named, with the release copycds puts in its osimage names'); + +is(xCAT::Test::OS::current_os(a_system('redhat-release' => "AlmaLinux release 10.2 (Lavender Lion)\n", + 'os-release' => $OPENEULER_OS_RELEASE)), + 'rhels10', + '/etc/redhat-release decides before /etc/os-release, so openEuler must not ship one'); + +my $sp3 = $OPENEULER_OS_RELEASE; +$sp3 =~ s/LTS-SP4/LTS-SP3/; +is(xCAT::Test::OS::current_os(a_system('os-release' => $sp3)), 'openeuler24.03sp3', + 'a different service pack is a different name'); + +my $lts = $OPENEULER_OS_RELEASE; +$lts =~ s/VERSION="24\.03 \(LTS-SP4\)"/VERSION="22.03 (LTS)"/; +is(xCAT::Test::OS::current_os(a_system('os-release' => $lts)), 'openeuler22.03', + 'a release with no service pack carries no sp token'); + +is(xCAT::Test::OS::current_os(a_system('os-release' => qq{NAME="Fedora Linux"\nID=fedora\n})), + undef, 'a distribution whose ID is not openEuler is not openEuler'); + +is(xCAT::Test::OS::current_os(a_system('redhat-release' => "AlmaLinux release 9.4 (Seafoam Ocelot)\n", + 'os-release' => qq{ID="almalinux"\n})), + 'rhels9', 'a Red Hat family system is rhels plus its major version'); +is(xCAT::Test::OS::current_os(a_system('lsb-release' => "DISTRIB_ID=Ubuntu\n")), 'ubuntu', + 'Ubuntu is named from lsb-release'); +is(xCAT::Test::OS::current_os(a_system('os-release' => qq{ID="sles"\nVERSION="15-SP6"\n})), 'sles', + 'SLES is named from os-release'); +is(xCAT::Test::OS::current_os(a_system()), 'aix', + 'a system with none of the release files is aix, as before'); + + +my @aliases = xCAT::Test::OS::linux_aliases(); +for my $family (qw(rhels sles ubuntu openeuler)) { + ok(scalar(grep { $_ eq $family } @aliases), "os:Linux includes $family"); +} + +my $current = 'openeuler24.03sp4'; +ok(scalar(grep { $current =~ /$_/i } @aliases), + 'an openEuler management node matches an os:Linux case'); + +done_testing(); diff --git a/xCAT-test/unit/xcattest_report_every_check.t b/xCAT-test/unit/xcattest_report_every_check.t index 2dd3e9824..44372e9f4 100644 --- a/xCAT-test/unit/xcattest_report_every_check.t +++ b/xCAT-test/unit/xcattest_report_every_check.t @@ -28,9 +28,11 @@ sub run_harness { # xcattest derives its result directory from the location of the program, so the copy # under the scratch tree keeps every file the run writes inside that tree. my $root = tempdir(CLEANUP => 1); - make_path("$root/bin", "$root/cases"); + make_path("$root/bin", "$root/cases", "$root/lib/perl/xCAT/Test"); copy($program, "$root/bin/xcattest") or die("copy xcattest: $!"); chmod 0755, "$root/bin/xcattest"; + copy("$FindBin::Bin/../lib/xCAT/Test/OS.pm", "$root/lib/perl/xCAT/Test/OS.pm") + or die("copy xCAT/Test/OS.pm: $!"); open(my $case_fh, '>', "$root/cases/fixture") or die("write the fixture case: $!"); print $case_fh $case_text; diff --git a/xCAT-test/xCAT-test.spec b/xCAT-test/xCAT-test.spec index aafb56b6d..f432fa195 100644 --- a/xCAT-test/xCAT-test.spec +++ b/xCAT-test/xCAT-test.spec @@ -44,6 +44,7 @@ rm -rf $RPM_BUILD_ROOT mkdir -p $RPM_BUILD_ROOT/%{prefix}/bin mkdir -p $RPM_BUILD_ROOT/%{prefix}/sbin +mkdir -p $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT/Test mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/xcat/tools/autotest mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/man/man1 mkdir -p $RPM_BUILD_ROOT/%{prefix}/share/doc/man1 @@ -52,6 +53,9 @@ cp bin/* $RPM_BUILD_ROOT/%{prefix}/bin cp xcattest $RPM_BUILD_ROOT/%{prefix}/bin chmod 755 $RPM_BUILD_ROOT/%{prefix}/bin/* +cp lib/xCAT/Test/OS.pm $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT/Test +chmod 644 $RPM_BUILD_ROOT/%{prefix}/lib/perl/xCAT/Test/OS.pm + cp restapitest $RPM_BUILD_ROOT/%{prefix}/sbin chmod 755 $RPM_BUILD_ROOT/%{prefix}/sbin/* diff --git a/xCAT-test/xcattest b/xCAT-test/xcattest index 25eaf9d6c..a424ec6f1 100755 --- a/xCAT-test/xcattest +++ b/xCAT-test/xcattest @@ -12,8 +12,11 @@ use File::Path; BEGIN { $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr'; + my $here = File::Basename::dirname(__FILE__); + unshift @INC, "$here/../lib/perl", "$here/lib"; } use lib "$::XCATROOT/lib/perl"; +use xCAT::Test::OS; #--------------global attributes---------------- my $program_path = dirname(File::Spec->rel2abs(__FILE__)); @@ -1040,7 +1043,7 @@ sub load_case { my @newvalidoslist = (); foreach my $validos (@validoslist) { if ($validos =~ /linux/i) { - push(@newvalidoslist, ("rhels", "sles", "ubuntu")); + push(@newvalidoslist, xCAT::Test::OS::linux_aliases()); } else { push(@newvalidoslist, $validos); } @@ -1831,28 +1834,7 @@ sub is_valid_case_name { # Return code: #-------------------------------------------------------- sub get_current_os { - if (-f "/etc/redhat-release") { - # Try to return "rhels" + major version, if can not get it, just return "rhels" - my $major_version = ""; - my @output = &runcmd("cat /etc/redhat-release"); - my $out_line = $output[0]; - if ($out_line =~ (/(\d+)\.(\d*)/)) { - $major_version = $1; - } - return "rhels".$major_version; - } elsif (-f "/etc/lsb-release") { - return "ubuntu"; - } elsif (-f "/etc/os-release") { - my $file="/etc/os-release"; - &runcmd("grep -q sles $file"); - if ($::RUNCMD_RC == 0) { - return "sles"; - } - } elsif (-f "/etc/SuSE-release") { - return "sles"; - } else { - return "aix"; - } + return xCAT::Test::OS::current_os(); } From 90813ba7d463ce856069f677a89455a0720f5513 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:26:49 -0300 Subject: [PATCH 2/3] test(xcat-core): the Debian xCAT-test package misses xCAT::Test::OS xcattest loads xCAT::Test::OS from /opt/xcat/lib/perl. xCAT-test.spec installs it, but xCAT-test/debian/install does not, so xcattest on an Ubuntu management node dies at startup with "Can't locate xCAT/Test/OS.pm in @INC". Check that the RPM spec and the Debian install list both ship every module under xCAT-test/lib/xCAT. The Debian check fails. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/unit/xcattest_package_modules.t | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 xCAT-test/unit/xcattest_package_modules.t diff --git a/xCAT-test/unit/xcattest_package_modules.t b/xCAT-test/unit/xcattest_package_modules.t new file mode 100644 index 000000000..8e69073be --- /dev/null +++ b/xCAT-test/unit/xcattest_package_modules.t @@ -0,0 +1,28 @@ +#!/usr/bin/env perl +# The installed xcattest loads the modules under xCAT-test/lib/xCAT from /opt/xcat/lib/perl, so +# both packages must ship each one. +use strict; +use warnings; +use File::Find; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::More; +use XCAT::Test::File qw(repo_path slurp_repo_file); + +my $lib = repo_path('xCAT-test/lib'); +my @modules; +find(sub { push @modules, $File::Find::name =~ s{\A\Q$lib\E/}{}r if /\.pm\z/ }, "$lib/xCAT"); +@modules = sort @modules; +ok(@modules, 'xCAT-test ships at least one Perl module to the installed tree'); + +my $rpm_spec = slurp_repo_file('xCAT-test/xCAT-test.spec'); +my $debian_install = slurp_repo_file('xCAT-test/debian/install'); +for my $module (@modules) { + (my $dir = $module) =~ s{/[^/]+\z}{}; + like($rpm_spec, qr{^cp\s+lib/\Q$module\E\s+\$RPM_BUILD_ROOT/%\{prefix\}/lib/perl/\Q$dir\E/?\s*$}m, + "the RPM installs $module under lib/perl/$dir"); + like($debian_install, qr{^lib/\Q$module\E\s+opt/xcat/lib/perl/\Q$dir\E/?\s*$}m, + "the Debian package installs $module under opt/xcat/lib/perl/$dir"); +} + +done_testing(); From ad511ff0c4dbf3f618345d3fcc12db6909a1678f Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 30 Sep 2026 20:26:58 -0300 Subject: [PATCH 3/3] fix(xcat-core): xcattest dies on Ubuntu without xCAT::Test::OS xCAT-test/debian/install did not list lib/xCAT/Test/OS.pm, so the Debian package shipped xcattest without the module it loads, and xcattest stopped at startup with "Can't locate xCAT/Test/OS.pm in @INC". Install the module to opt/xcat/lib/perl/xCAT/Test, as the RPM spec does. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/debian/install | 1 + 1 file changed, 1 insertion(+) diff --git a/xCAT-test/debian/install b/xCAT-test/debian/install index 8601c376f..8bb5e001f 100644 --- a/xCAT-test/debian/install +++ b/xCAT-test/debian/install @@ -5,3 +5,4 @@ share/doc/man1/* opt/xcat/share/doc/man1 autotest opt/xcat/share/xcat/tools unit opt/xcat/share/xcat/tools/autotest integration opt/xcat/share/xcat/tools/autotest +lib/xCAT/Test/OS.pm opt/xcat/lib/perl/xCAT/Test