From 87974d91c40ceb7ae2a595de4a02708c08bb8f2d Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Wed, 16 Sep 2026 18:24:31 -0300 Subject: [PATCH] test(xcat-core): go-xcat asks to remove packages that are not installed uninstall_xcat hands the whole GO_XCAT_UNINSTALL_LIST to the package manager. Most of that list is not installed on any one node -- it names packages xCAT stopped producing, so that `go-xcat uninstall` still removes them where they are present. apt answers "Unable to locate package" for a name it does not know and go-xcat prints a warning for each; dnf answers "No match for argument" and fails the whole call when nothing in the list matches. The test runs uninstall_xcat with rpm and dpkg-query answering from a fake package database and remove_package shadowed, and reads the argument list the package manager would have been given. A fourth case hands over a database where everything is installed, so a filter that dropped everything could not pass the other three. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- .../unit/go_xcat_uninstall_installed_only.t | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100755 xCAT-test/unit/go_xcat_uninstall_installed_only.t diff --git a/xCAT-test/unit/go_xcat_uninstall_installed_only.t b/xCAT-test/unit/go_xcat_uninstall_installed_only.t new file mode 100755 index 000000000..31464393c --- /dev/null +++ b/xCAT-test/unit/go_xcat_uninstall_installed_only.t @@ -0,0 +1,109 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Temp qw(tempdir); +use FindBin; +use Test::More; + +# `go-xcat uninstall` hands GO_XCAT_UNINSTALL_LIST to the package manager. That list names +# packages xCAT stopped producing -- xCAT-genesis-builder, yaboot-xcat, conserver-xcat -- so +# most of it is not installed on any one node. apt answers "Unable to locate package" for a +# name it does not know, and go-xcat prints a warning for each; dnf answers "No match for +# argument" and fails the whole call when nothing in the list matches. +# +# So what is not installed does not reach the package manager. That is also what makes keeping +# a retired name in the list free, which is why the list outlives the build. +# +# uninstall_xcat is taken from the shipped script and run with the package database faked and +# remove_package shadowed, so the assertions read the argument list the package manager would +# have been given. + +my $go_xcat = "$FindBin::Bin/../../xCAT-server/share/xcat/tools/go-xcat"; +plan skip_all => 'go-xcat not found' unless -r $go_xcat; +plan tests => 4; + +{ + my @handed = uninstall_args($go_xcat, ['xCAT-server', 'xCAT-client']); + is_deeply([sort @handed], ['xCAT-client', 'xCAT-server'], + 'only the installed packages reach the package manager'); +} + +{ + # The point of keeping a retired name: where it IS installed, it is still removed. + my @handed = uninstall_args($go_xcat, ['xCAT-genesis-builder']); + is_deeply(\@handed, ['xCAT-genesis-builder'], + 'a retired package that is installed is still removed'); +} + +{ + my @handed = uninstall_args($go_xcat, []); + is_deeply(\@handed, [], + 'a node with none of them installed hands the package manager nothing'); +} + +{ + # Guards the fake: a database that says everything is installed must hand over the list, + # or the three assertions above would pass against a filter that drops everything. + my @handed = uninstall_args($go_xcat, ['*']); + ok(scalar(@handed) > 10, 'with everything installed the whole list is handed over'); +} + +# Extract `function NAME()` blocks by name, in the order asked for. +sub extract { + my ($text, @names) = @_; + my @blocks; + for my $name (@names) { + my ($block) = $text =~ /^(function \Q$name\E\(\)\s*\n\{.*?^\})/ms; + push @blocks, $block if $block; + } + return @blocks; +} + +# Run the shipped uninstall path with rpm and dpkg-query answering from a fake database and the +# package manager shadowed, and collect what it was asked to remove. +sub uninstall_args { + my ($path, $installed) = @_; + my $text = do { open my $fh, '<', $path or die "$path: $!"; local $/; <$fh> }; + + my ($lists) = $text =~ /^(GO_XCAT_INSTALL_LIST=\(.*?\n)\n/ms; + BAIL_OUT("no package lists in $path") unless $lists; + my @functions = extract($text, qw(function_dispatch installed_packages + installed_packages_rpm installed_packages_deb + uninstall_xcat)); + BAIL_OUT("no uninstall_xcat() in $path") + unless grep { /^function uninstall_xcat\(\)/ } @functions; + + my $dir = tempdir(CLEANUP => 1); + my $driver = "$dir/driver.sh"; + my $present = join ' ', @{$installed}; + open my $fh, '>', $driver or die "$driver: $!"; + print {$fh} "set -u\n"; + print {$fh} "type() { return 1; }\n"; # the rpm branch of the lists + print {$fh} $lists, "\n"; + print {$fh} <<"BASH"; +INSTALLED="$present" +present() { + case "\$INSTALLED" in *"*"*) return 0 ;; esac + case " \$INSTALLED " in *" \$1 "*) return 0 ;; *) return 1 ;; esac +} +rpm() { for a in "\$\@"; do case "\$a" in -*) continue ;; esac; present "\$a" || return 1; done; return 0; } +dpkg-query() { + for a in "\$\@"; do + case "\$a" in -*) continue ;; esac + present "\$a" && echo "install ok installed" + done + return 0 +} +exit_if_bad() { :; } +warn_if_bad() { :; } +remove_package() { for a in "\$\@"; do case "\$a" in -y) continue ;; esac; echo "\$a"; done; } +BASH + print {$fh} join("\n", @functions), "\n"; + print {$fh} "uninstall_xcat -y\n"; + close $fh; + + my @out = qx{bash '$driver' 2>/dev/null}; + chomp @out; + return grep { length } @out; +}