2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 23:28:19 +00:00

Merge pull request #7917 from VersatusHPC/test/r26-package-artifacts

test(packaging): exercise package and repository behavior
This commit is contained in:
Daniel Hilst
2026-10-14 11:44:03 -03:00
committed by GitHub
13 changed files with 517 additions and 329 deletions
+13
View File
@@ -39,6 +39,19 @@ jobs:
- name: Run tests
if: ${{ !cancelled() }}
run: perl github_action_xcat_test.pl
- name: Install package test dependencies
id: package-deps
if: ${{ !cancelled() }}
run: sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends --no-install-suggests cpio createrepo-c zstd rsync gnupg libxml-libxml-perl python3-rpm
- name: Exercise probe package payloads
if: ${{ !cancelled() && steps.package-deps.outcome == 'success' }}
run: prove xCAT-test/native/probe_package_payload.t
- name: Exercise release repositories
if: ${{ !cancelled() && steps.package-deps.outcome == 'success' }}
run: prove xCAT-test/native/release_repository.t
- name: Check parsed package relations
if: ${{ !cancelled() && steps.package-deps.outcome == 'success' }}
run: prove xCAT-test/native/package_relations.t
rpm_lifecycle_test:
runs-on: ubuntu-24.04
timeout-minutes: 30
+1 -1
View File
@@ -32,7 +32,7 @@ Missing prerequisites fail these tests without stopping unrelated test files.
Non-Linux hosts report a skip. Set `TMPDIR` to a writable, executable filesystem
if the default temporary directory is mounted with `noexec`.
`go_xcat_os_release.bats` and the full-script cases in
`go_xcat_os_release.bats`, `go_xcat_activation.bats` and the full-script cases in
`postscripts_apt_get.bats` use the same namespace isolation. They require
bats-core 1.4 or newer. OS detection also requires GNU awk for the legacy
Red Hat release parser. Postscript cases require GNU diffutils.
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env bats
load helpers/script_sandbox
setup() {
setup_script_sandbox awk cat comm cp cut dirname grep head id ln mkdir mktemp mv rm sed sleep sort tr wc xargs
mkdir -p "$fixture/etc/yum.repos.d" "$fixture/tools"
sandbox+=(--uid 0 --gid 0
--ro-bind "$(repo_path xCAT-server/share/xcat/tools/go-xcat)" /run/go-xcat)
# An inherited library-only setting must not disable the command.
sandbox+=(--setenv GO_XCAT_LIBRARY_ONLY 1)
cat >"$fixture/tools/manager" <<'SH'
#!/bin/sh
operation=
for arg do
case "$arg" in install|remove) operation=$arg; continue ;; esac
[ -n "$operation" ] || continue
case "$arg" in -*) continue ;; esac
printf '%s %s\n' "$operation" "$arg" >> /run/fixture/transactions
done
[ "$operation" != install ] || exit 17
case "$*" in *repoquery*) printf '%s\n' fixture-package ;; esac
exit 0
SH
cat >"$fixture/tools/rpm" <<'SH'
#!/bin/sh
case "$*" in
'-q --quiet '*) grep -Fxq "$3" /run/fixture/installed ;;
*) exit 0 ;;
esac
SH
cat >"$fixture/tools/dpkg-query" <<'SH'
#!/bin/sh
if grep -Fxq "$3" /run/fixture/installed; then printf 'install ok installed'; else exit 1; fi
SH
printf '#!/bin/sh\nexit 0\n' >"$fixture/tools/apt-cache"
printf '#!/bin/sh\nexit 22\n' >"$fixture/bin/curl"
chmod +x "$fixture/bin/curl" "$fixture/tools/"*
: >"$fixture/installed"
: >"$fixture/transactions"
}
invoke() {
local format=$1 architecture=$2 action=$3
local debian_arch=$architecture
if [ "$format" = rpm ]; then
printf 'ID=almalinux\nVERSION_ID=8.10\n' >"$fixture/etc/os-release"
cp "$fixture/tools/rpm" "$fixture/bin/rpm"
cp "$fixture/tools/manager" "$fixture/bin/dnf"
else
[ "$architecture" != x86_64 ] || debian_arch=amd64
printf '#!/bin/sh\nprintf "%%s\\n" "%s"\n' "$debian_arch" >"$fixture/bin/dpkg"
chmod +x "$fixture/bin/dpkg"
mkdir -p "$fixture/etc/apt/sources.list.d" "$fixture/var/lib/apt/lists"
printf 'ID=ubuntu\nVERSION_ID=24.04\n' >"$fixture/etc/os-release"
printf 'DISTRIB_CODENAME=noble\n' >"$fixture/etc/lsb-release"
printf 'Package: fixture-package\nVersion: 1\n' >"$fixture/var/lib/apt/lists/test_xcat-core_dists_noble_main_binary-${debian_arch}_Packages"
cp "$fixture/var/lib/apt/lists/test_xcat-core_dists_noble_main_binary-${debian_arch}_Packages" "$fixture/var/lib/apt/lists/test_xcat-dep_dists_noble_main_binary-${debian_arch}_Packages"
cp "$fixture/tools/dpkg-query" "$fixture/bin/dpkg-query"
cp "$fixture/tools/apt-cache" "$fixture/bin/apt-cache"
cp "$fixture/tools/manager" "$fixture/bin/apt-get"
fi
run "${sandbox[@]}" --setenv HOSTTYPE "$architecture" \
timeout 30 bash /run/go-xcat -y \
--xcat-core=https://packages.example.test/core \
--xcat-dep=https://packages.example.test/dep "$action"
}
check_uninstall() {
local format=$1 selection=$2 name prefix=xCAT
[ "$format" != deb ] || prefix=xcat
local architectures=(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x)
[ "$selection" != subset ] || architectures=(ppc64le riscv64)
for name in "${architectures[@]}"; do
[ "$format" != deb ] || name=${name//_/-}
printf '%s-genesis-openembedded-%s\n' "$prefix" "$name"
done >"$fixture/installed"
printf 'unrelated-package\n' >>"$fixture/installed"
invoke "$format" x86_64 uninstall
[ "$status" -eq 0 ] || { echo "$output" >&2; return 1; }
grep -v '^unrelated-package$' "$fixture/installed" | sed 's/^/remove /' | sort >"$fixture/expected"
sort "$fixture/transactions" >"$fixture/actual"
diff -u "$fixture/expected" "$fixture/actual"
}
check_install() {
local format=$1 architecture=$2 prefix=xCAT legacy=ppc64 x86=x86_64
if [ "$format" = deb ]; then prefix=xcat; legacy=ppc64el; x86=amd64; fi
invoke "$format" "$architecture" install
[ "$status" -eq 17 ] || { echo "$output" >&2; return 1; }
local packages=("perl-$prefix" "$prefix-client" "$prefix" "$prefix-buildkit" "$prefix-server"
elilo-xcat grub2-xcat ipmitool-xcat syslinux-xcat ipxe-xcat xnba-undi)
if [ "$architecture" = riscv64 ]; then
packages+=("$prefix-genesis-openembedded-riscv64")
else
packages+=("$prefix-genesis-scripts-$legacy" "$prefix-genesis-scripts-$x86"
"$prefix-genesis-base-$legacy" "$prefix-genesis-base-$x86")
fi
[ "$format" != rpm ] || packages+=(initscripts)
printf 'install %s\n' "${packages[@]}" | sort >"$fixture/expected"
sort "$fixture/transactions" >"$fixture/actual"
diff -u "$fixture/expected" "$fixture/actual" || {
echo "$output" >&2
[ ! -f "$fixture/tmp/go-xcat.log" ] || cat "$fixture/tmp/go-xcat.log" >&2
return 1
}
}
@test "RPM uninstall removes all installed OpenEmbedded architectures" { check_uninstall rpm all; }
@test "DEB uninstall removes all installed OpenEmbedded architectures" { check_uninstall deb all; }
@test "RPM uninstall excludes absent and unrelated packages" { check_uninstall rpm subset; }
@test "DEB uninstall excludes absent and unrelated packages" { check_uninstall deb subset; }
@test "RPM RISC-V installation selects OpenEmbedded and keeps other packages" { check_install rpm riscv64; }
@test "DEB RISC-V installation selects OpenEmbedded and keeps other packages" { check_install deb riscv64; }
@test "RPM x86 installation retains its legacy Genesis packages" { check_install rpm x86_64; }
@test "DEB x86 installation retains its legacy Genesis packages" { check_install deb x86_64; }
+26
View File
@@ -0,0 +1,26 @@
package XCAT::Test::Package;
use strict;
use warnings;
use Cwd qw(getcwd);
use Exporter qw(import);
use XCAT::Test::Lifecycle ();
our @EXPORT_OK = qw(run_in);
sub run_in {
my ($directory, @command) = @_;
my $previous = getcwd();
chdir($directory) or die "chdir $directory: $!";
my ($stdout, $stderr, $status);
my $ok = eval {
($status, $stdout, $stderr) = XCAT::Test::Lifecycle::command(@command);
1;
};
my $error = $@;
chdir($previous) or die "chdir $previous: $!";
die $error unless $ok;
return ($status, $stdout, $stderr);
}
1;
+17
View File
@@ -44,6 +44,23 @@ packages, execute target-architecture binaries or prove that the generated image
## Other native tests
The pull-request workflow runs these packaging cases explicitly as an ordinary
user:
```
prove xCAT-test/native/probe_package_payload.t \
xCAT-test/native/release_repository.t \
xCAT-test/native/package_relations.t
```
They need RPM build/query tools, cpio, createrepo_c, zstd, rsync, GnuPG, Python 3
with RPM bindings, user/mount namespaces, and the Perl modules Capture::Tiny,
File::Slurper, Parallel::ForkManager and XML::LibXML. The probe command also
needs `which`. The release test uses an ephemeral signing key and removes its
agent. These checks inspect real packages and repository output; they do not
qualify package installation on every target architecture. On EL8, install both
`python36` and `python3-rpm` to provide the interpreter and its RPM bindings.
Run the package and unprivileged namespace cases as an ordinary user:
```
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(repo_path);
use XCAT::Test::Package qw(run_in);
plan skip_all => 'requires Linux RPM tooling' unless $^O eq 'linux';
local %ENV = (%ENV, HOME => tempdir(CLEANUP => 1));
delete @ENV{qw(pcm fsm s390x)};
my @platforms = (
['EL7', 'rhel 7', 0], ['EL8', 'rhel 8', 1], ['EL9', 'rhel 9', 1],
['EL10', 'rhel 10', 1], ['Fedora', 'fedora 44', 1],
['SLES12', 'suse_version 1315', 0], ['SLES15', 'suse_version 1500', 1],
['openEuler', 'openEuler 2', 0],
);
my @images = map { "xCAT-genesis-openembedded-$_" } qw(x86_64 ppc64le riscv64 s390x);
my %legacy = (x86_64 => 'x86_64', ppc64le => 'ppc64', aarch64 => 'aarch64');
for my $platform (@platforms) {
my ($name, $macro, $weak) = @$platform;
for my $arch (qw(x86_64 ppc64le aarch64 riscv64 s390x)) {
for my $pkg (qw(xCAT xCATsn)) {
my $requires = query($pkg, $arch, $macro, '--requires');
my $recommends = query($pkg, $arch, $macro, '--recommends');
unlike($requires, qr/xCAT-genesis-openembedded-/, "$name $arch $pkg never requires optional images");
my @actual = sort grep { /^xCAT-genesis-openembedded-/ } split /\n/, $recommends;
is_deeply(\@actual, $weak ? [sort @images] : [], "$name $arch $pkg optional image recommendations");
my @scripts = grep { /^xCAT-genesis-scripts-/ } split /\n/, $requires;
is_deeply(\@scripts, $legacy{$arch}
? ["xCAT-genesis-scripts-$legacy{$arch} = 1:9.9.9-1"] : [],
"$name $arch $pkg required legacy Genesis scripts");
if ($arch eq 'riscv64') {
like($requires, $pkg eq 'xCAT' ? qr/^ipmitool-xcat >= 1\.8\.18-4$/m : qr/^ipmitool-xcat >= 1\.8\.17-1$/m,
"$name $pkg RISC-V IPMI dependency");
unlike($requires, qr/xCAT-genesis-scripts-|xnba-undi|syslinux-xcat|elilo-xcat|%\{/,
"$name $pkg RISC-V has no unavailable legacy payload or unresolved macro");
}
}
}
for my $s390 (0, 1) {
local $ENV{s390x} = $s390;
my $requires = query('xCAT-server', 'x86_64', $macro, '--requires');
like($requires, qr/^perl\(Digest::SHA\)$/m, "$name server s390x=$s390 requires Digest::SHA");
next if $s390;
my $recommends = query('xCAT-server', 'x86_64', $macro, '--recommends');
is(scalar(grep { $_ eq 'perl-DB_File' } split /\n/, $requires), $name eq 'EL10' ? 0 : 1,
"$name server hard DB_File dependency");
is(scalar(grep { $_ eq 'perl-DB_File' } split /\n/, $recommends), $name eq 'EL10' ? 1 : 0,
"$name server weak DB_File dependency");
like($requires, qr/^\Q$_\E$/m, "$name server keeps $_")
for qw(perl-Net-Telnet perl-Net-DNS perl-Crypt-CBC perl-Crypt-Rijndael);
}
}
for my $platform (@platforms) {
my ($name, $macro) = @$platform;
my ($rc, $filter, $err) = run_in(repo_path('.'), 'python3', '-c', <<'PY', repo_path('xCAT-server/xCAT-server.spec'), $macro);
import rpm, sys
for name in ('rhel', 'fedora', 'suse_version', 'openEuler'):
rpm.delMacro(name)
name, value = sys.argv[2].split()
rpm.addMacro(name, value)
rpm.addMacro('version', '9.9.9')
rpm.addMacro('release', '1')
rpm.addMacro('__requires_exclude', '^retained$')
rpm.spec(sys.argv[1])
print(rpm.expandMacro('%{?__requires_exclude}'))
PY
is($rc, 0, "$name parses server dependency generator policy") or die($err);
chomp $filter;
ok('retained' =~ /$filter/, "$name keeps an inherited dependency filter");
is('perl(DB_File)' =~ /$filter/ ? 1 : 0, $name eq 'EL10' ? 1 : 0,
"$name automatic DB_File dependency exclusion");
unlike('perl(Digest::SHA)', qr/$filter/, "$name keeps automatic Digest::SHA dependencies");
}
for my $case (['Linux', 'rhel 8', 'iproute'], ['SUSE', 'suse_version 1500', 'iproute2']) {
my ($name, $macro, $provider) = @$case;
my $requires = query('xCAT-probe', 'x86_64', $macro, '--requires');
my @providers = grep { /^iproute2?$/ } split /\n/, $requires;
is_deeply(\@providers, [$provider], "$name probe socket tool provider");
}
done_testing();
sub query {
my ($package, $arch, $macro, $relation) = @_;
my $format = $relation eq '--requires'
? '[%{REQUIRENAME} %{REQUIREFLAGS:depflags} %{REQUIREVERSION}\n]'
: '[%{RECOMMENDNAME}\n]';
my ($rc, $out, $err) = run_in(repo_path('.'), 'rpmspec', '-q', '--target', $arch,
(map { ('--undefine', $_) } qw(rhel fedora suse_version openEuler)),
'--define', $macro, '--define', 'version 9.9.9', '--define', 'release 1',
'--qf', $format, repo_path("$package/$package.spec"));
die("$package $arch $macro: $err") if $rc;
$out =~ s/[^\S\n]+$//mg;
return $out;
}
+70
View File
@@ -0,0 +1,70 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Path qw(make_path);
use File::Slurper qw(read_binary write_text);
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib", "$FindBin::Bin/../../build-utils/lib";
use Test::More;
use XCAT::BuildUtils qw(stage_xcat_probe_sources);
use XCAT::Test::File qw(repo_path slurp_repo_file);
use XCAT::Test::Package qw(run_in);
plan skip_all => 'requires Linux package tools' unless $^O eq 'linux';
die('run package builds as an unprivileged user') unless $>;
my $root = tempdir(CLEANUP => 1);
make_path(map { "$root/$_" } qw(SOURCES BUILD BUILDROOT RPMS SRPMS SPECS home));
local %ENV = (%ENV, HOME => "$root/home", LC_ALL => 'C');
delete @ENV{qw(PERL5LIB PERL5OPT PERLLIB XCATROOT XCATCFG)};
stage_xcat_probe_sources(repo_path('.'), "$root/SOURCES", '9.9.9', 1600000000);
my @helpers = qw(CommandUtils.pm GlobalDef.pm NetworkUtils.pm PacketCapture.pm ServiceNodeUtils.pm);
my @commands = qw(code_template detect_dhcpd discovery osdeploy xcatmn);
sub command {
my ($directory, @args) = @_;
my ($rc, $out, $err) = run_in($directory, @args);
unless (is($rc, 0, "$args[0] completes")) {
diag($out, $err);
die("@args failed");
}
return $out;
}
command($root, 'rpmbuild', '-bb', '--define', "_topdir $root",
'--define', 'version 9.9.9', '--define', 'release 1', repo_path('xCAT-probe/xCAT-probe.spec'));
my @rpms = glob("$root/RPMS/noarch/xCAT-probe-*.rpm");
is(scalar(@rpms), 1, 'build produces one probe RPM') or die('missing probe RPM');
my $requires = command($root, 'rpm', '-qp', '--requires', $rpms[0]);
like($requires, qr/^iproute$/m, 'RPM requires the Linux socket tool provider');
make_path("$root/rpm");
command($root, 'bash', '-o', 'pipefail', '-c',
'cd "$1" && rpm2cpio "$2" | cpio -idm --quiet', 'bash', "$root/rpm", $rpms[0]);
check_payload("$root/rpm", 'RPM');
sub check_payload {
my ($destination, $label) = @_;
my $prefix = "$destination/opt/xcat";
my $helper_dir = "$prefix/probe/lib/perl/xCAT";
ok(-x $helper_dir, "$label helper directory is searchable")
or die(sprintf('%s has mode %04o', $helper_dir, (stat($helper_dir))[2] & 0777));
for my $helper (@helpers) {
my $path = "$prefix/probe/lib/perl/xCAT/$helper";
ok(-f $path, "$label carries $helper") or die("missing $path");
is(read_binary($path), slurp_repo_file("perl-xCAT/xCAT/$helper"),
"$label carries the canonical $helper bytes");
is((stat($path))[2] & 0777, 0644, "$label helper is readable without execute permission");
}
write_text("$prefix/bin/xcatclient", "#!/bin/sh\nprintf '[ok]:fixture client\\n'\n");
chmod 0755, "$prefix/bin/xcatclient" or die $!;
local $ENV{XCATROOT} = $prefix;
local $ENV{PATH} = "$prefix/bin:$ENV{PATH}";
for my $name (@commands) {
my $out = command($root, "$prefix/probe/subcmds/$name", '-T');
like($out, qr/^\[ok\]\s*:/m, "$label $name self-test is ready");
}
my $list = command($root, "$prefix/bin/xcatprobe", '-l');
like($list, qr/^\Q$_\E\s/m, "$label lists $_") for @commands;
}
done_testing();
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Slurper qw(read_binary write_text);
use File::Temp qw(tempdir);
use FindBin;
use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
use XML::LibXML;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(repo_path);
use XCAT::Test::Package qw(run_in);
plan skip_all => 'requires Linux package tools' unless $^O eq 'linux';
die('run package builds as an unprivileged user') unless $>;
my $root = tempdir(CLEANUP => 1);
make_path(map { "$root/$_" } qw(home home/keys SOURCES BUILD BUILDROOT RPMS SRPMS SPECS work));
chmod 0700, "$root/home/keys" or die $!;
local %ENV = (%ENV, HOME => "$root/home", GNUPGHOME => "$root/home/keys", LC_ALL => 'C');
delete @ENV{qw(PERL5LIB PERL5OPT PERLLIB)};
write_text("$root/home/.rpmmacros", "%__gpg /usr/bin/gpg\n");
command($root, 'gpg', '--batch', '--pinentry-mode', 'loopback', '--passphrase', '',
'--quick-generate-key', 'Package Test <package@example.test>', 'rsa2048', 'sign', '0');
END {
local $?;
system('gpgconf', '--homedir', "$root/home/keys", '--kill', 'gpg-agent') if defined $root;
}
command($root, 'tar', '-czf', "$root/SOURCES/xCAT-release-9.9.9.tar.gz",
'-C', repo_path('.'), 'xCAT-release');
command($root, 'rpmbuild', '-ba', '--define', "_topdir $root",
'--define', 'version 9.9.9', '--define', 'release 1', repo_path('xCAT-release/xCAT-release.spec'));
my $rpm = 'xCAT-release-9.9.9-1.noarch.rpm';
my $srpm = 'xCAT-release-9.9.9-1.src.rpm';
ok(-f "$root/RPMS/noarch/$rpm", 'real release RPM was built') or die('missing release RPM');
my $requirements = command($root, 'rpm', '-qp', '--requires', "$root/RPMS/noarch/$rpm");
like($requirements, qr/^dnf$/m, 'release package requires DNF');
my $files = command($root, 'rpm', '-qp', '--qf', '[%{FILENAMES}\t%{FILEFLAGS}\n]', "$root/RPMS/noarch/$rpm");
for my $repo (qw(xcat-core xcat-dep xcat-dep-common)) {
like($files, qr{^/etc/yum\.repos\.d/\Q$repo\E\.repo\t17$}m, "$repo remains config(noreplace)");
}
make_path("$root/work/build-utils/lib/XCAT");
copy(repo_path('buildrpms.pl'), "$root/work/buildrpms.pl") or die $!;
copy(repo_path('build-utils/lib/XCAT/BuildUtils.pm'), "$root/work/build-utils/lib/XCAT/BuildUtils.pm") or die $!;
write_text("$root/work/Version", "9.9.9\n");
write_text("$root/work/Gitepoch", "1600000000\n");
write_text("$root/work/Gitinfo", ('a' x 40) . "\n");
make_path("$root/input/SRPMS");
copy("$root/RPMS/noarch/$rpm", "$root/input/$rpm") or die $!;
copy("$root/SRPMS/$srpm", "$root/input/SRPMS/$srpm") or die $!;
copy("$root/RPMS/noarch/$rpm", "$root/input/xCAT-release-latest.noarch.rpm") or die $!;
for my $signed (0, 1) {
for my $attempt (1, 2) {
command("$root/work", $^X, 'buildrpms.pl', '--merge-core-repos',
'--target', 'alma+epel-10-x86_64', '--release', '1',
'--output-dir', "$root/output", '--input-core-repos', "$root/input",
$signed ? ('--gpg-sign', '--gpg-key-name', 'package@example.test') : ());
check_repository("$root/output", $signed, "merge signed=$signed run=$attempt");
}
}
my $target = 'alma+epel-10-x86_64';
my $cached = "$root/work/dist/$target/rpms";
make_path("$cached/SRPMS", "$root/mock", "$root/locks");
copy("$root/RPMS/noarch/$rpm", "$cached/$rpm") or die $!;
copy("$root/SRPMS/$srpm", "$cached/SRPMS/$srpm") or die $!;
command($root, 'cp', '-a', repo_path('xCAT-release'), "$root/work/xCAT-release");
write_text("$root/mock/xCAT-release-$target.cfg", "# cached build\n");
write_text("$root/os-release", "ID=almalinux\nVERSION_ID=10\n");
local $ENV{BUILD_FIXTURE} = $root;
command("$root/work", 'unshare', '--user', '--map-root-user', '--mount',
'bash', repo_path('xCAT-test/native/fixtures/buildrpms-namespace.sh'),
'sh', '-c', 'trap \'gpgconf --homedir "$GNUPGHOME" --kill gpg-agent\' EXIT; "$@"', 'sign-test',
$^X, 'buildrpms.pl', '--package', 'xCAT-release', '--target', $target,
'--release', '1', '--nproc', '1', '--gpg-sign', '--gpg-key-name', 'package@example.test');
check_repository($cached, 1, 'cached build finalization');
write_text("$root/work/schedule.pl", <<'PERL');
BEGIN {
require Parallel::ForkManager;
no warnings 'redefine';
*Parallel::ForkManager::start = sub {
print "scheduled $_[1]\n" if defined $_[1];
return 1;
};
*Parallel::ForkManager::wait_all_children = sub { };
}
do './buildrpms.pl';
die $@ if $@;
PERL
my $schedule = command("$root/work", 'unshare', '--user', '--map-root-user', '--mount',
'bash', repo_path('xCAT-test/native/fixtures/buildrpms-namespace.sh'),
$^X, 'schedule.pl', '--target', $target, '--release', '1', '--nproc', '1');
is(scalar(grep { $_ eq "scheduled xCAT-release-$target" } split /\n/, $schedule), 1,
'default build schedules the release package once');
unlink "$root/input/$rpm" or die $!;
command("$root/work", $^X, 'buildrpms.pl', '--merge-core-repos',
'--target', $target, '--release', '1', '--output-dir', "$root/output",
'--input-core-repos', "$root/input");
ok(!-e "$root/output/xCAT-release-latest.noarch.rpm", 'missing release package leaves no stale alias');
is_deeply([primary_packages("$root/output")], [], 'missing release package leaves an empty binary index');
done_testing();
sub command {
my ($directory, @args) = @_;
my ($rc, $out, $err) = run_in($directory, @args);
is($rc, 0, "$args[0] completes") or do { diag($out, $err); die("@args failed"); };
return $out;
}
sub primary_packages {
my ($directory) = @_;
my $repomd = XML::LibXML->load_xml(location => "$directory/repodata/repomd.xml");
my ($location) = $repomd->findnodes('//*[local-name()="data"][@type="primary"]/*[local-name()="location"]');
die 'missing primary metadata' unless $location;
my $path = "$directory/" . $location->getAttribute('href');
my $xml;
if ($path =~ /\.gz$/) {
gunzip $path => \$xml, Transparent => 0 or die $GunzipError;
} elsif ($path =~ /\.zst$/) {
my ($rc, $out, $err) = run_in($directory, 'zstd', '-dc', $path);
die "Cannot decode primary metadata: $err" if $rc;
$xml = $out;
} else {
die "Unsupported primary metadata format: $path";
}
return XML::LibXML->load_xml(string => $xml)->findnodes('//*[local-name()="package"]');
}
sub check_repository {
my ($directory, $signed, $label) = @_;
my $alias = "$directory/xCAT-release-latest.noarch.rpm";
ok(-f $alias, "$label publishes the alias") or die('missing bootstrap alias');
is(read_binary($alias), read_binary("$directory/$rpm"), "$label alias has final RPM bytes");
is((stat($alias))[2] & 0777, 0644, "$label alias is readable");
my @packages = primary_packages($directory);
is(scalar(@packages), 1, "$label indexes the release package once");
for my $package (@packages) {
my ($location) = $package->findnodes('./*[local-name()="location"]');
is($location->getAttribute('href'), $rpm, "$label indexes the versioned filename only");
}
if ($signed) {
for my $repo ($directory, "$directory/SRPMS") {
command($root, 'gpg', '--verify', "$repo/repodata/repomd.xml.asc", "$repo/repodata/repomd.xml");
}
my $signature = command($root, 'rpm', '-qp', '--qf', '%{RSAHEADER:pgpsig}', $alias);
like($signature, qr/RSA.*Key ID/i, "$label alias carries the package signature");
isnt(read_binary($alias), read_binary("$root/RPMS/noarch/$rpm"), "$label signing changed the unsigned RPM");
}
}
@@ -8,28 +8,6 @@ use Test::More;
use XCAT::Test::File qw(slurp_repo_file);
my $rpm_weak_dependencies = join(
"\n",
'%if 0%{?fedora} || 0%{?rhel} >= 8 || 0%{?suse_version} >= 1500',
'Recommends: xCAT-genesis-openembedded-x86_64',
'Recommends: xCAT-genesis-openembedded-ppc64le',
'Recommends: xCAT-genesis-openembedded-riscv64',
'Recommends: xCAT-genesis-openembedded-s390x',
'%endif',
);
my $rpm_spec = slurp_repo_file('xCAT/xCAT.spec');
like(
$rpm_spec,
qr/^\Q$rpm_weak_dependencies\E$/m,
'RPM weak dependencies stay inside their compatibility guard',
);
unlike(
$rpm_spec,
qr/^Requires:\s+xCAT-genesis-openembedded-/m,
'missing OpenEmbedded images do not block an RPM upgrade',
);
my $deb_control = slurp_repo_file('xCAT/debian/control');
like(
$deb_control,
@@ -57,13 +35,6 @@ unlike(
'missing OpenEmbedded images do not block a DEB upgrade',
);
my $sn_rpm_spec = slurp_repo_file('xCATsn/xCATsn.spec');
like(
$sn_rpm_spec,
qr/^\Q$rpm_weak_dependencies\E$/m,
'service-node weak dependencies stay inside their compatibility guard',
);
my $sn_deb_control = slurp_repo_file('xCATsn/debian/control');
like(
$sn_deb_control,
@@ -86,26 +57,6 @@ like(
'DEB service nodes recommend the s390x image',
);
my $go_xcat = slurp_repo_file('xCAT-server/share/xcat/tools/go-xcat');
unlike(
$go_xcat,
qr/GO_XCAT_LIBRARY_ONLY/,
'go-xcat cannot be disabled by an inherited test environment variable',
);
for my $architecture (qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x)) {
like(
$go_xcat,
qr/\bxCAT-genesis-openembedded-\Q$architecture\E\b/,
"go-xcat removes the $architecture RPM image",
);
(my $deb_architecture = $architecture) =~ tr/_/-/;
like(
$go_xcat,
qr/\bxcat-genesis-openembedded-\Q$deb_architecture\E\b/,
"go-xcat removes the $architecture DEB image",
);
}
my $mknb_pod = slurp_repo_file('xCAT-client/pods/man8/mknb.8.pod');
like(
$mknb_pod,
+3 -60
View File
@@ -7,66 +7,9 @@ use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../../build-utils/lib";
use Test::More;
use XCAT::Test::File qw(repo_path slurp_repo_file);
use XCAT::Test::File qw(repo_path);
use XCAT::BuildUtils qw(targetarch_from_target);
# riscv64 packaging: the arch-named packages (xCAT, xCATsn) must resolve their
# architecture token and dependencies for riscv64, and the build scripts must
# know the riscv64 package names. The Genesis image comes from the OpenEmbedded
# packages, so there is no arch-named Genesis package to resolve here.
my $xcat = slurp_repo_file('xCAT/xCAT.spec');
my ($xcat_rv) = $xcat =~ /^%ifarch riscv64\n((?:#[^\n]*\n|Requires:[^\n]*\n)*)%endif$/m;
like( $xcat_rv || '', qr/^Requires: ipmitool-xcat >= 1\.8\.18-4$/m, 'xCAT.spec requires ipmitool-xcat on riscv64' );
unlike( $xcat_rv || '', qr/xnba-undi|syslinux-xcat|elilo-xcat/, 'xCAT.spec does not require the x86 PXE loaders on riscv64' );
# riscv64 has no legacy Genesis package: the requirement must disappear rather than
# resolve to an unsatisfiable name, and the OpenEmbedded image is recommended instead.
like( $xcat, qr/^%\{\?genesistarch:Requires: xCAT-genesis-scripts-%\{genesistarch\} = 1:%\{version\}-%\{release\}\}$/m, 'xCAT.spec asks for the legacy Genesis package only where the architecture has one' );
like( $xcat, qr/^Recommends: xCAT-genesis-openembedded-riscv64$/m, 'xCAT.spec recommends the RISC-V OpenEmbedded Genesis image' );
SKIP: {
my $rpmspec = qx(command -v rpmspec 2>/dev/null);
chomp($rpmspec);
skip 'rpmspec is not installed', 2 unless $rpmspec && -x $rpmspec;
my $spec = repo_path('xCAT/xCAT.spec');
open( my $requires_fh, '-|',
$rpmspec, '-q', '--target', 'riscv64', '--requires', $spec )
or BAIL_OUT("unable to run $rpmspec: $!");
my $requires = do { local $/; <$requires_fh> };
close($requires_fh)
or BAIL_OUT("rpmspec failed for $spec with status " . ($? >> 8));
unlike( $requires, qr/genesis-scripts/, 'a riscv64 build requires no legacy Genesis package' );
unlike( $requires, qr/\Q%{genesistarch}\E/, 'a riscv64 build leaves no unexpanded architecture macro' );
}
my $xcatsn = slurp_repo_file('xCATsn/xCATsn.spec');
my ($xcatsn_rv) = $xcatsn =~ /^%ifarch [^\n]*\briscv64\b[^\n]*\n((?:#[^\n]*\n|Requires:[^\n]*\n)*)%endif$/m;
like( $xcatsn_rv || '', qr/^Requires: ipmitool-xcat >= 1\.8\.17-1$/m, 'xCATsn.spec requires ipmitool-xcat on riscv64' );
like( $xcatsn, qr/^Recommends: xCAT-genesis-openembedded-riscv64$/m, 'xCATsn.spec recommends the RISC-V OpenEmbedded Genesis image' );
my $server = slurp_repo_file('xCAT-server/xCAT-server.spec');
like( $server, qr/^Recommends: perl-DB_File$/m, 'xCAT-server.spec recommends perl-DB_File on EL10 (riscv64 has no EPEL to provide it)' );
like(
$server,
qr/^%if 0%\{\?rhel\} >= 10\nRecommends: perl-DB_File\n/m,
'the weak perl-DB_File dependency is limited to EL10, where riscv64 has no EPEL',
);
like(
$server,
qr/^%else\nRequires: perl-DB_File\n%endif$/m,
'build hosts without weak dependencies keep the hard perl-DB_File requirement',
);
like(
$server,
qr/^%global __requires_exclude %\{\?__requires_exclude:%\{__requires_exclude\}\|\}\^perl\\\\\(DB_File\\\\\)\$$/m,
'xCAT-server.spec appends the generated perl(DB_File) requirement to the build root filter',
);
like( $server, qr/^Requires: perl-Net-Telnet perl-Net-DNS perl-Crypt-CBC perl-Crypt-Rijndael$/m, 'xCAT-server.spec keeps the other EL perl requires' );
my $architectures = repo_path('build-utils/rpm-architectures.sh');
is(system('sh', '-n', $architectures), 0, 'the shared build architecture data parses as POSIX shell');
open(
@@ -75,9 +18,9 @@ open(
'sh', '-c',
'. "$1"; printf "%s\n%s\n%s\n" "$XCAT_CORE_RPM_ARCHES" "$XCAT_LOCAL_RPM_ARCHES" "$XCAT_LOCAL_COLLECT_ARCHES"',
'sh', $architectures,
) or BAIL_OUT("unable to load $architectures: $!");
) or die("unable to load $architectures: $!");
my @architecture_sets = <$arch_fh>;
close($arch_fh) or BAIL_OUT("unable to read architecture data from $architectures");
close($arch_fh) or die("unable to read architecture data from $architectures");
chomp @architecture_sets;
is(
+5 -134
View File
@@ -1,143 +1,14 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Copy qw(copy);
use File::Path qw(make_path);
use File::Slurper qw(write_text);
use File::Spec;
use File::Temp qw(tempdir);
use FindBin;
use lib "$FindBin::Bin/../lib";
use lib "$FindBin::Bin/../../build-utils/lib";
use Test::More;
use XCAT::Test::File qw(slurp_repo_file);
use XCAT::BuildUtils qw(XCAT_PROBE_HELPERS stage_probe_helpers);
use XCAT::Test::File qw(repo_path slurp_repo_file);
my @helpers = qw(
CommandUtils.pm
GlobalDef.pm
NetworkUtils.pm
PacketCapture.pm
ServiceNodeUtils.pm
);
my @affected_subcommands = qw(
code_template
detect_dhcpd
discovery
osdeploy
xcatmn
);
# How buildrpms.pl stages the xCAT-probe sources is asserted by
# xcat_probe_sources_staged.t, which runs the staging and reads the archive. Seven assertions
# here matched the text of buildrpms.pl instead, and four of them could not fail: three matched
# the xCAT-release routine, which stages the same way, and one spanned the rest of the file. All
# seven stayed green while the staging directory was unset and every RPM target died.
my $installed_probe_test =
slurp_repo_file('xCAT-test/autotest/testcase/probe/xcatproble_list');
my $rpm_spec = slurp_repo_file('xCAT-probe/xCAT-probe.spec');
my $debian_control = slurp_repo_file('xCAT-probe/debian/control');
like(
$rpm_spec,
qr/%if 0%\{\?suse_version\}\s+Requires: iproute2\s+%else\s+Requires: iproute\s+%endif/s,
'RPM package requires the distro-specific provider of ss'
);
like(
$debian_control,
qr/^Depends:.*\biproute2\s*\|\s*net-tools\b/m,
'Debian package requires ss or the legacy netstat provider'
);
# The Debian builder stages the helpers by calling stage_probe_helpers, so run it and
# look at what it produced. The predecessor matched a `cp -f` line in build-ubunturepo,
# which passed whenever that text was reformatted and failed whenever it moved.
my $staged_probe_dir = File::Spec->catdir(tempdir(CLEANUP => 1), 'lib', 'perl', 'xCAT');
stage_probe_helpers(repo_path(File::Spec->catdir('perl-xCAT', 'xCAT')), $staged_probe_dir);
for my $helper (@helpers) {
my $source = repo_path(File::Spec->catfile('perl-xCAT', 'xCAT', $helper));
ok(-f $source, "$helper source exists");
ok(
scalar(grep { $_ eq $helper } XCAT_PROBE_HELPERS),
"the shared builder helper list carries $helper"
);
ok(
-f File::Spec->catfile($staged_probe_dir, $helper),
"Debian builder stages $helper"
);
like(
$installed_probe_test,
qr/cmd:for module in [^;]*\b\Q$helper\E\b[^;]*; do test -r/,
"installed probe payload checks $helper"
);
}
my $tmpdir = tempdir(CLEANUP => 1);
my $xcatroot = File::Spec->catdir($tmpdir, 'opt', 'xcat');
my $probe_root = File::Spec->catdir($xcatroot, 'probe');
my $bin_dir = File::Spec->catdir($xcatroot, 'bin');
my $subcmd_dir = File::Spec->catdir($probe_root, 'subcmds');
my $helper_dir = File::Spec->catdir($probe_root, 'lib', 'perl', 'xCAT');
make_path($probe_root, $bin_dir);
copy_tree(repo_path(File::Spec->catdir('xCAT-probe', 'lib')), File::Spec->catdir($probe_root, 'lib'));
copy_tree(repo_path(File::Spec->catdir('xCAT-probe', 'subcmds')), $subcmd_dir);
my $xcatprobe_source = repo_path(File::Spec->catfile('xCAT-probe', 'xcatprobe'));
my $xcatprobe = File::Spec->catfile($bin_dir, 'xcatprobe');
copy($xcatprobe_source, $xcatprobe) or die "copy $xcatprobe_source: $!";
chmod 0755, $xcatprobe or die "chmod $xcatprobe: $!";
make_path($helper_dir, File::Spec->catdir($subcmd_dir, 'bin'));
for my $helper (@helpers) {
my $source = repo_path(File::Spec->catfile('perl-xCAT', 'xCAT', $helper));
my $destination = File::Spec->catfile($helper_dir, $helper);
copy($source, $destination) or die "copy $source: $!";
chmod 0644, $destination or die "chmod $destination: $!";
}
my $xcatclient = File::Spec->catfile($bin_dir, 'xcatclient');
write_text($xcatclient, "#!/bin/sh\nprintf '[ok]:dummy xcatclient\\n'\n");
chmod 0755, $xcatclient or die "chmod $xcatclient: $!";
local $ENV{XCATROOT} = $xcatroot;
local $ENV{PATH} = "$bin_dir:$ENV{PATH}";
local $ENV{PERL5LIB};
local $ENV{PERL5OPT};
local $ENV{PERLLIB};
delete $ENV{PERL5LIB};
delete $ENV{PERL5OPT};
delete $ENV{PERLLIB};
for my $subcommand (@affected_subcommands) {
my $command = File::Spec->catfile($subcmd_dir, $subcommand);
my ($rc, $output) = run_command($command, '-T');
is($rc, 0, "$subcommand self-test exits successfully") or diag($output);
like($output, qr/^\[ok\]\s*:/m, "$subcommand self-test reports ready");
}
my ($list_rc, $list_output) = run_command($xcatprobe, '-l');
is($list_rc, 0, 'xcatprobe list exits successfully') or diag($list_output);
my %listed = map { /^([^\s].*?)\s/ ? ($1 => 1) : () } split /\n/, $list_output;
for my $subcommand (@affected_subcommands) {
ok($listed{$subcommand}, "xcatprobe lists $subcommand") or diag($list_output);
}
my $control = slurp_repo_file('xCAT-probe/debian/control');
my ($probe) = $control =~ /^Package: xcat-probe\n(.*?)(?:\n\n|\z)/ms;
like($probe // '', qr/^Depends:.*\biproute2\s*\|\s*net-tools\b/m,
'Debian probe keeps the socket-tool alternatives');
done_testing();
sub copy_tree {
my ($source, $destination) = @_;
my $rc = system('cp', '-R', $source, $destination);
is($rc, 0, "copied $source into the package fixture")
or die("unable to create package fixture from $source");
}
sub run_command {
my (@command) = @_;
open(my $fh, '-|', @command) or die "run @command: $!";
my $output = do { local $/; <$fh> };
close($fh);
return ($? >> 8, $output // '');
}
+7 -51
View File
@@ -3,13 +3,12 @@ use strict;
use warnings;
use Digest::SHA qw(sha256_hex);
use File::Spec;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(slurp_repo_file);
my $repo_root = File::Spec->rel2abs(File::Spec->catdir($FindBin::Bin, '..', '..'));
my $spec = read_file('xCAT-release/xCAT-release.spec');
my $spec = slurp_repo_file('xCAT-release/xCAT-release.spec');
like($spec, qr/^Name:\s+xCAT-release$/m, 'package has the expected name');
like($spec, qr/^Source0:\s+xCAT-release-%\{version\}\.tar\.gz$/m, 'source archive follows the package name');
like($spec, qr/^BuildArch:\s+noarch$/m, 'package is architecture independent');
@@ -19,7 +18,7 @@ like($spec, qr/^%config\(noreplace\) .*xcat-dep\.repo$/m, 'dependency repo prese
like($spec, qr/^%config\(noreplace\) .*xcat-dep-common\.repo$/m, 'common dependency repo preserves local changes');
like($spec, qr{RPM-GPG-KEY-xCAT}, 'package installs the signing key');
my $core = read_file('xCAT-release/xcat-core.repo');
my $core = slurp_repo_file('xCAT-release/xcat-core.repo');
assert_repo_security($core, 'core');
like(
$core,
@@ -27,7 +26,7 @@ like(
'core repo uses the stable HTTPS endpoint'
);
my $dep = read_file('xCAT-release/xcat-dep.repo');
my $dep = slurp_repo_file('xCAT-release/xcat-dep.repo');
assert_repo_security($dep, 'dependency');
like(
$dep,
@@ -35,7 +34,7 @@ like(
'dependency repo follows the DNF release and architecture variables'
);
my $common_dep = read_file('xCAT-release/xcat-dep-common.repo');
my $common_dep = slurp_repo_file('xCAT-release/xcat-dep-common.repo');
assert_repo_security($common_dep, 'common dependency');
like(
$common_dep,
@@ -48,7 +47,7 @@ like(
'common dependency repo is independent of the management-node distribution'
);
my $key = read_file('xCAT-release/RPM-GPG-KEY-xCAT');
my $key = slurp_repo_file('xCAT-release/RPM-GPG-KEY-xCAT');
like($key, qr/^-----BEGIN PGP PUBLIC KEY BLOCK-----$/m, 'signing key is ASCII armored');
is(
sha256_hex($key),
@@ -56,40 +55,6 @@ is(
'packaged key matches the published xCAT signing key'
);
my $builder = read_file('buildrpms.pl');
like($builder, qr/^\s+xCAT-release\s*$/m, 'default RPM build includes xCAT-release');
like(
$builder,
qr{\$repodir/xCAT-release-latest\.noarch\.rpm},
'stable bootstrap alias follows the package name'
);
like(
$builder,
qr{\$repodir/xCAT-release-\$VERSION-\$RELEASE\.noarch\.rpm},
'stable bootstrap alias selects the xCAT-release RPM'
);
like(
$builder,
qr/unlink \$alias.*?createrepo_dir\(\$repodir/s,
'stable bootstrap alias is excluded from repository metadata'
);
like(
$builder,
qr/cp \$release_rpms\[0\], \$alias/,
'repository export creates the stable bootstrap filename'
);
my $sign_call = rindex($builder, 'sign_rpms($target)');
my $alias_call = rindex($builder, 'write_release_alias("dist/$target/rpms")');
ok(
$sign_call >= 0 && $alias_call > $sign_call,
'stable bootstrap alias is created after signed metadata is finalized'
);
like(
$builder,
qr/sub merge_core_repos \{.*?write_repo_metadata_dir\(\$out\);.*?write_release_alias\(\$out\);/s,
'assembled core repository creates the stable alias after final metadata'
);
done_testing();
sub assert_repo_security {
@@ -103,12 +68,3 @@ sub assert_repo_security {
"$label repo uses the packaged signing key"
);
}
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;
}
@@ -1,42 +1,14 @@
#!/usr/bin/env perl
use strict;
use warnings;
use File::Spec;
use FindBin;
use lib "$FindBin::Bin/../lib";
use Test::More;
use XCAT::Test::File qw(slurp_repo_file);
my $repo_root = File::Spec->rel2abs(
File::Spec->catdir( $FindBin::Bin, '..', '..' )
);
sub read_file {
my ($filename) = @_;
open( my $fh, '<', $filename )
or die "Unable to read $filename: $!";
my $content = do { local $/; <$fh> };
close($fh);
return $content;
}
my $rpm_spec = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'xCAT-server.spec' )
);
my @rpm_sha_requirements =
$rpm_spec =~ /^Requires:.*\bperl\(Digest::SHA\)(?:\s|$)/mg;
is(
scalar(@rpm_sha_requirements),
2,
'both RPM architecture branches require Digest::SHA',
);
my $debian_control = read_file(
File::Spec->catfile( $repo_root, 'xCAT-server', 'debian', 'control' )
);
like(
$debian_control,
qr/^Depends:.*\blibdigest-sha-perl\b/m,
'the Debian package requires Digest::SHA',
);
my $control = slurp_repo_file('xCAT-server/debian/control');
my ($server) = $control =~ /^Package: xcat-server\n(.*?)(?:\n\n|\z)/ms;
like($server // '', qr/^Depends:.*\blibdigest-sha-perl\b/m,
'the Debian server package requires Digest::SHA');
done_testing();