2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-08-03 16:06:59 +00:00

Merge pull request #7686 from VersatusHPC/backport-7650-to-2.18

[Backport 2.18] fix(xcat-core): run every *.t test in the GitHub CI
This commit is contained in:
Vinícius Ferrão
2026-07-25 01:19:20 -03:00
committed by GitHub
13 changed files with 249 additions and 28 deletions
+96 -2
View File
@@ -27,6 +27,26 @@ my $check_result_str="``CI CHECK RESULT`` : ";
my $last_func_start = timelocal(localtime());
my $GITHUB_API = "https://api.github.com";
# The workflow starts us in the checked out source tree. The unit tests under
# xCAT-test/unit resolve xCAT modules and fixture files relative to that tree
# through FindBin, so they can only be run from a source tree -- but the tree
# does not survive the build. build-ubunturepo sets
# local_core_repo_path="$curdir/../../xcat-core"
# which, under the work/<repo>/<repo> layout GitHub checks out into, resolves
# to the checkout's own parent, and it then rm -rf's that path to make room for
# the apt repository. So take a copy of the tree before building and run the
# unit tests out of the copy.
my $srcdir = getcwd();
my $unitsrc = ($ENV{'RUNNER_TEMP'} ? $ENV{'RUNNER_TEMP'} : "/tmp") . "/xcat-core-unitsrc";
# Cases whose output is printed even when they pass. A passing case is normally
# silent, which is the right default for 250 of them but makes it impossible to
# tell from the log whether a case did real work or skipped everything -- a
# distinction that matters for cases wrapping prove, since prove exits 0 either
# way. Name a case here to see its output; empty the list for the quiet
# behaviour.
my @verbose_cases = qw(integration_tests);
#--------------------------------------------------------
# Fuction name: runcmd
# Description: run a command after 'cmd' label in one case
@@ -270,6 +290,29 @@ sub send_back_comment{
}
}
#--------------------------------------------------------
# Fuction name: preserve_source_tree
# Description: Copy the checkout aside before the build destroys it, so the
# unit tests still have a source tree to run against afterwards.
# Must be called before build_xcat_core().
# Attributes:
# Return code: 0 Success 1 Failed
#--------------------------------------------------------
sub preserve_source_tree{
my $cmd = "rm -rf $unitsrc && cp -a $srcdir $unitsrc";
print "[preserve_source_tree] running $cmd\n";
my @output = runcmd("$cmd");
if($::RUNCMD_RC){
print RED "[preserve_source_tree] $cmd ....[Failed]\n";
print Dumper \@output;
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";
return 0;
}
#--------------------------------------------------------
# Fuction name: build_xcat_core
# Description:
@@ -384,6 +427,34 @@ sub install_xcat{
}
#--------------------------------------------------------
# Fuction name: run_unit_tests
# Description: Run every Perl unit test under xCAT-test/unit with prove.
# Runs against the pre-build copy of the source tree taken by
# preserve_source_tree(): the tests reach for xCAT modules and
# fixture files through FindBin, so the installed copy under
# /opt/xcat/share/xcat/tools/autotest is not enough for them.
# Attributes:
# Return code: 0 all tests passed, 1 otherwise
#--------------------------------------------------------
sub run_unit_tests{
my $cmd = "cd $unitsrc && prove -r xCAT-test/unit";
print "[run_unit_tests] running $cmd\n";
my @output = runcmd("$cmd");
print Dumper \@output;
if($::RUNCMD_RC){
print RED "[run_unit_tests] $cmd ....[Failed]\n";
$check_result_str .= "> **UNIT TESTS Failed** : Please click ``Details`` label in ``Merge pull request`` box for detailed information\n";
print $check_result_str;
return 1;
}
print "[run_unit_tests] $cmd ....[Pass]\n";
$check_result_str .= "> **UNIT TESTS Successful**\n";
print $check_result_str;
return 0;
}
#--------------------------------------------------------
# Fuction name: check_syntax
# Description:
@@ -497,12 +568,16 @@ sub run_fast_regression_test{
$cmd = "sudo bash -c '. /etc/profile.d/xcat.sh && xcattest -f $conf_file -t $case'";
print "[run_fast_regression_test] run $x: $cmd\n";
@output = runcmd("$cmd");
#print Dumper \@output;
my $verbose = grep { $_ eq $case } @verbose_cases;
if($verbose){
print "[run_fast_regression_test] output of $case (listed in \@verbose_cases):\n";
print Dumper \@output;
}
for(my $i = $#output; $i>-1; --$i){
if($output[$i] =~ /------END::(.+)::Failed/){
push @failcase, $1;
++$failnum;
print Dumper \@output;
print Dumper \@output unless($verbose);
last;
}elsif ($output[$i] =~ /------END::(.+)::Passed/){
++$passnum;
@@ -566,6 +641,15 @@ print Dumper \@ipinfo;
#Start to build xcat core
#Save the source tree before the build deletes it, the unit tests need it later
print GREEN "\n------ Preserving the source tree for the unit tests ------\n";
$rst = preserve_source_tree();
if($rst){
print RED "Preserving the source tree failed\n";
exit $rst;
}
mark_time("preserve_source_tree");
print GREEN "\n------ Building xCAT core package ------\n";
$rst = build_xcat_core();
if($rst){
@@ -583,6 +667,16 @@ if($rst){
}
mark_time("install_xcat");
#Run the xCAT-test unit tests. They need the perl dependencies xCAT pulls in
#(Net::DNS, XML::Simple) and a usable xCAT database, so they run after install.
print GREEN "\n------Running xCAT-test unit tests ------\n";
$rst = run_unit_tests();
if($rst){
print RED "Run of xCAT-test unit tests failed\n";
exit $rst;
}
mark_time("run_unit_tests");
#Check the syntax of changing code
print GREEN "\n------ Checking the syntax of changed code------\n";
$rst = check_syntax();
@@ -1,8 +0,0 @@
start:dhcp_backend_unit_tests
description:Run the DHCP backend Perl unit tests through xcattest
os:Linux
label:mn_only,ci_test,dhcp,unit,dhcp_unit
cmd:prove -I/opt/xcat/lib/perl -I/opt/xcat/lib/perl/xCAT /opt/xcat/share/xcat/tools/autotest/unit/dhcp_*.t
check:rc==0
check:output=~All tests successful
end
@@ -0,0 +1,8 @@
start:integration_tests
description:Run the xCAT-test integration tests (xCAT-test/integration) against the installed management node
os:Linux
label:mn_only,ci_test,integration
cmd:prove -I/opt/xcat/lib/perl -I/opt/xcat/lib/perl/xCAT -r /opt/xcat/share/xcat/tools/autotest/integration
check:rc==0
check:output=~Files=3
end
@@ -1,8 +0,0 @@
start:ipmi_rakp2_unit_tests
description:Run the IPMI RAKP2 Perl unit tests through xcattest
os:Linux
label:mn_only,ci_test,ipmi,unit,ipmi_unit
cmd:prove -I/opt/xcat/lib/perl -I/opt/xcat/lib/perl/xCAT /opt/xcat/share/xcat/tools/autotest/unit/ipmi_*.t
check:rc==0
check:output=~All tests successful
end
@@ -1,8 +0,0 @@
start:xcatprobe_tcp_listener_unit_tests
description:Run the xCAT probe TCP listener Perl unit tests through xcattest
os:Linux
label:mn_only,ci_test,xcatprobe,unit,xcatprobe_unit
cmd:prove -I/opt/xcat/probe/lib/perl /opt/xcat/share/xcat/tools/autotest/unit/probe_utils_tcp_listener.t
check:rc==0
check:output=~All tests successful
end
+1
View File
@@ -4,3 +4,4 @@ share/man/man1/* opt/xcat/share/man/man1
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
+80
View File
@@ -0,0 +1,80 @@
# xCAT-test/integration
Integration tests. These run against an **installed management node** -- they need a
real xCAT installation, and depending on the test a populated `/install`, a service
binary they can execute, or a live daemon.
They are driven by `xcattest` through the testcase in
`../autotest/testcase/integration/`, which proves the copy installed by the
`xcat-test` package:
```
prove -I/opt/xcat/lib/perl -I/opt/xcat/lib/perl/xCAT \
-r /opt/xcat/share/xcat/tools/autotest/integration
```
Run the case by hand on an MN with:
```
xcattest -f <cluster.conf> -t integration_tests
```
The case carries the `ci_test` label, so it also runs on every pull request: the
`xcat_test` GitHub Actions workflow installs and configures xCAT on the runner, which
makes that runner a (single node) management node, and then runs every `ci_test` case
against it.
Note the `-I` flags: unlike the unit tests these run from the installed location, so
they pick up xCAT modules from `/opt/xcat/lib/perl` rather than from a source tree.
The case checks `rc==0` and `output=~Files=3`. The second assertion is there because
`prove` exits 0 both when tests pass and when they all skip, so `rc==0` alone would let
the case report green having run nothing. Matching `Files=3` proves `prove` actually
found all three files, which catches a packaging regression or a file being renamed
without the count being updated here. **Add to that number when you add a test.** A
missing directory is already caught by `rc==0` -- `prove -r` on a path that does not
exist exits 2.
Note also that `github_action_xcat_test.pl` invokes each case through `sudo`, so in CI
these tests run as **root** while the unit tests run unprivileged. That is the right
way round -- integration tests legitimately need to write to places like `/etc/kea`,
whereas running the unit tests as root would let permission-related assertions pass
for the wrong reason.
## What belongs here
A test belongs in `integration/` when it needs something the checkout cannot provide:
| Test | Requires |
| --- | --- |
| `copycds_packages_integrity.t` | `/install` populated by a real `copycds` |
| `dhcp_kea_config_validation.t` | a `kea-dhcp4` binary that can read the generated config |
| `dhcp_kea_control_agent_smoke.t` | live `kea-dhcp4` and `kea-ctrl-agent`, root, and the Kea host-commands hook |
## Environment guards
Tests here still guard with `plan skip_all` so the case does not fail on a node that
legitimately lacks the dependency -- an MN with no Kea installed should skip the Kea
tests, not go red. A skip in this directory is therefore expected and normal, which is
precisely why these tests do not belong alongside the unit tests.
Which tests actually run consequently varies by node. On a GitHub runner, for example,
`/install` is empty so `copycds_packages_integrity.t` skips, while
`dhcp_kea_config_validation.t` does run because the case executes as root and can
therefore validate from `/etc/kea`.
`dhcp_kea_control_agent_smoke.t` is opt-in on top of that:
```perl
plan skip_all => 'set XCAT_KEA_LIVE_SMOKE=1 to run live Kea daemon smoke test'
unless $ENV{XCAT_KEA_LIVE_SMOKE};
```
It starts real Kea daemons, so it stays off unless asked for. Do not enable it on a
node whose DHCP service is in use.
## What does not belong here
Anything that only needs the checkout. Those go in [`../unit`](../unit/README.md) and
run on every pull request, which is much faster feedback than waiting for a cluster
test.
+60
View File
@@ -0,0 +1,60 @@
# xCAT-test/unit
Unit tests. These run against the **source tree only** -- no xCAT installation, no
running daemons, no management node.
They are executed on every pull request by the `xcat_test` GitHub Actions workflow,
which calls `run_unit_tests()` in `github_action_xcat_test.pl`:
```
prove -r xCAT-test/unit
```
You can run exactly the same thing from a clean checkout:
```
cd <xcat-core checkout>
prove -r xCAT-test/unit
```
## What belongs here
A test belongs in `unit/` when everything it needs is in the checkout: plugin and
library sources, kickstart/preseed/subiquity templates, postscripts, packaging
metadata. Such a test asserts on rendered output or module logic and reaches the
repository root through `FindBin`:
```perl
use FindBin;
use lib "$FindBin::Bin/../../perl-xCAT";
use lib "$FindBin::Bin/../../xCAT-server/lib/perl";
```
Because of those `FindBin` paths the tests only work from a source tree. The copy
installed under `/opt/xcat/share/xcat/tools/autotest/unit` is not a substitute --
`../..` resolves to `/opt/xcat/share/xcat/tools` there and the tests die or silently
skip. The CI takes a copy of the checkout before the build for this reason; see
`preserve_source_tree()`.
## What does not belong here
Anything that needs an installed xCAT, a populated `/install`, a real service binary
or a live daemon. Those go in [`../integration`](../integration/README.md) and run on
a management node through `xcattest`. Both suites run on every pull request -- the
workflow installs xCAT on the runner and then runs the `ci_test` cases against it --
so putting a test in `integration/` does not cost it CI coverage. What differs is what
each suite is allowed to depend on, and that unit tests also run standalone from a
bare checkout with no xCAT at all.
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
directories means a skip in `unit/` is a real signal rather than routine noise.
Guarding on a *source* file, on the other hand, is fine and common here:
```perl
plan skip_all => "compute.subiquity.tmpl not found" unless -f $tmpl_path;
```
That guard never fires when the tree is intact.
+3 -2
View File
@@ -15,7 +15,7 @@ like($tmpl, qr/^#cloud-config/, 'template starts with #cloud-config');
like($tmpl, qr/autoinstall:/, 'template has autoinstall: key');
like($tmpl, qr/version:\s*1/, 'template has version: 1');
unlike($tmpl, qr/^\s*identity:/m, 'template must not have identity section (use user-data instead)');
like($tmpl, qr/^\s*identity:/m, 'template has an identity section so subiquity does not prompt');
like($tmpl, qr/kernel:/, 'template has kernel section');
like($tmpl, qr/package:\s*linux-generic/, 'template specifies linux-generic kernel');
like($tmpl, qr/#UBUNTU_SUBIQUITY_APT_CONFIG#/, 'template renders apt section from osimage context');
@@ -38,7 +38,8 @@ like($tmpl, qr/printf ''%s\\n'' ''GRUB_CMDLINE_LINUX="#TABLEBLANKOKAY:bootparams
like($tmpl, qr/\/target\/etc\/netplan\/00-xcat-install\.yaml/, 'template writes an xCAT-owned target netplan file');
like($tmpl, qr/installnic="#TABLE:noderes:\$NODE:installnic#"/, 'target netplan uses node installnic');
like($tmpl, qr/installmac="#TABLE:mac:\$NODE:mac#"/, 'target netplan uses node MAC');
like($tmpl, qr/installmac="\$\(printf ''%s'' "\$\{installmac\}" \| tr ''A-F'' ''a-f''\)"/, 'target netplan normalizes MAC case');
like($tmpl, qr/installmac="\$\(printf ''%s'' "\$\{installmac\}".*\| tr ''A-F'' ''a-f''\)"/, 'target netplan normalizes MAC case');
like($tmpl, qr/installmac="\$\(printf ''%s'' "\$\{installmac\}" \| cut -d''\|'' -f1 \| cut -d''!'' -f1/, 'target netplan strips mac table suffixes before matching');
like($tmpl, qr/printf ''%s\\n'' "network:" " version: 2" " ethernets:" " xcat-install:" " match:" " macaddress: \\"\$\{installmac\}\\"" " set-name: \$\{installnic\}" " dhcp4: true" >\/target\/etc\/netplan\/00-xcat-install\.yaml;/, 'target netplan printf stays on one shell line');
like($tmpl, qr/" macaddress: \\"\$\{installmac\}\\""/, 'target netplan matches by MAC address');
like($tmpl, qr/" set-name: \$\{installnic\}"/, 'target netplan sets the expected installnic name');
+1
View File
@@ -63,6 +63,7 @@ chmod 644 $RPM_BUILD_ROOT/%{prefix}/share/doc/man1/*
cp -r autotest $RPM_BUILD_ROOT/%{prefix}/share/xcat/tools
cp -r unit $RPM_BUILD_ROOT/%{prefix}/share/xcat/tools/autotest
cp -r integration $RPM_BUILD_ROOT/%{prefix}/share/xcat/tools/autotest
%clean