From ea2b86adef168a82dfc3ba01921871dbfe7d0912 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:02:47 -0300 Subject: [PATCH] test(xcat-core): naming util-linux-extra stops the Genesis build on jammy builddeb-genesis-base names util-linux-extra in REQUIRED_PACKAGES for every release. Measured on the four Ubuntu management nodes: focal and jammy report "Candidate: (none)" for that package and carry hwclock in util-linux, which is essential and already in the build root; noble and resolute carry it in util-linux-extra. apt-get install with a package it cannot locate exits non-zero, and the script runs under set -euo pipefail, so the build stops on two supported targets before dracut runs. util-linux only Suggests util-linux-extra, and the install passes --no-install-recommends, so a release that split the package has to name it. The test asserts the unconditional list does not name it, and drives the selector that decides, with apt-cache shadowed for a release that has the package and one that does not. It also drops the assertion that matched "verify-genesis-payload" against the text of the build script. That proved the string was present, not that the verifier ran, ran before packaging, or stopped the build -- and the script it names does not exist on this branch. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/unit/genesis_ubuntu_build_root.t | 43 ++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/xCAT-test/unit/genesis_ubuntu_build_root.t b/xCAT-test/unit/genesis_ubuntu_build_root.t index cf8363b30..3486094d8 100755 --- a/xCAT-test/unit/genesis_ubuntu_build_root.t +++ b/xCAT-test/unit/genesis_ubuntu_build_root.t @@ -20,16 +20,22 @@ my $builder = repo_path('xCAT-genesis-builder/builddeb-genesis-base'); my $module = repo_path('xCAT-genesis-builder/dracut_105/ubuntu/module-setup.sh'); plan skip_all => 'builddeb-genesis-base not found' unless -f $builder; plan skip_all => 'ubuntu module-setup.sh not found' unless -f $module; -plan tests => 8; +plan tests => 10; # Mandatory commands a minimal Ubuntu server root does NOT already provide, and the package -# that supplies each one. +# that supplies each one on every release xCAT builds for. my %PACKAGE_FOR = ( dhclient => 'isc-dhcp-client', ifenslave => 'ifenslave', - hwclock => 'util-linux-extra', ); +# hwclock is not in that list because the package that carries it moved. Measured on the +# four Ubuntu management nodes: focal and jammy have it in util-linux, which is essential +# and always in the build root, and no util-linux-extra exists to install; noble and +# resolute have it in util-linux-extra. util-linux only Suggests that package, and this +# build passes --no-install-recommends, so the releases that split it must name it and the +# releases that did not must not. + my %mandatory = map { $_ => 1 } mandatory_commands($module); my @packages = required_packages($builder); @@ -43,10 +49,22 @@ for my $command (sort keys %PACKAGE_FOR) { ok($mandatory{dhclient} && scalar(grep { $_ eq 'isc-dhcp-client' } @packages), 'the Genesis image can obtain a DHCP lease'); -# dracut_install is silent about a missing command, so the payload needs its own gate. -# xCAT-genesis-base.spec runs the same verifier on the EL path. -my $text = do { open my $fh, '<', $builder or die "$builder: $!"; local $/; <$fh> }; -like($text, qr{verify-genesis-payload}, 'builddeb-genesis-base verifies the payload it packages'); +ok($mandatory{hwclock}, "the Ubuntu dracut module installs 'hwclock' unconditionally"); + +# Naming a package apt cannot locate fails the whole install, and the script runs under +# set -e, so an unconditional util-linux-extra stops the build on focal and jammy. +ok(!scalar(grep { $_ eq 'util-linux-extra' } @packages), + 'the unconditional list does not name util-linux-extra'); + +# What the script does instead: keep a package only where apt has a candidate for it. +{ + my @present = optional_packages($builder, 'util-linux-extra', 0); + my @absent = optional_packages($builder, 'util-linux-extra', 1); + is_deeply(\@present, ['util-linux-extra'], + 'a release that carries util-linux-extra installs it'); + is_deeply(\@absent, [], + 'a release without it installs nothing in its place'); +} # An absolute path in the install() output is a data file, not a command. sub mandatory_commands { @@ -75,6 +93,17 @@ BASH return @names; } +# Run the script's own selector with apt-cache shadowed, so the decision is exercised +# rather than read. $rc is what the shadow returns: 0 for a release that has the package. +sub optional_packages { + my ($path, $package, $rc) = @_; + my $text = do { open my $fh, '<', $path or die "$path: $!"; local $/; <$fh> }; + my ($block) = $text =~ /^(optional_packages\(\)\s*\{.*?^\})/ms; + BAIL_OUT("no optional_packages() in $path") unless $block; + my $out = qx{bash -c 'set -u; apt-cache() { return $rc; }; $block; optional_packages $package' 2>/dev/null}; + return [ grep { length } split /\s+/, ($out // '') ]; +} + # Evaluate the assignment rather than parse it, so the list is the value the script uses. sub required_packages { my ($path) = @_;