From 76d6967ed70e5a00ef64069a6b08c91a16d00007 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Fri, 4 Sep 2026 18:33:56 -0300 Subject: [PATCH] test(xcat-core): capture the Debian genesis dependency ignoring the architecture The ppc64el and riscv64 xcat debs depend on xcat-genesis-scripts-amd64, and xcat-genesis-scripts-ppc64 depends on xcat-genesis-base-ppc64, a package no repository publishes. Nothing reports either one: the amd64 scripts package is Architecture: all, so it installs on any architecture, and the broken ppc64 dependency is never reached because nothing pulls that package. Extend debian_control_arch_coverage.t. It now reads the Depends field of xCAT/debian/control and xCATsn/debian/control, applies each architecture restriction the way dpkg-gencontrol does, and asserts that the genesis scripts a given architecture receives are that architecture's own. It also asserts that xCAT-genesis-scripts/debian/control- builds xcat-genesis-scripts- and depends on xcat-genesis-base-. Eight of the eighteen assertions fail on this tree. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/unit/debian_control_arch_coverage.t | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/xCAT-test/unit/debian_control_arch_coverage.t b/xCAT-test/unit/debian_control_arch_coverage.t index 73bf2c7a1..dc545f0bc 100644 --- a/xCAT-test/unit/debian_control_arch_coverage.t +++ b/xCAT-test/unit/debian_control_arch_coverage.t @@ -37,4 +37,102 @@ for my $ctl (@controls) { } } +# The genesis dependency must follow the architecture. xCAT and xCATsn are built once per +# architecture from one control file, so an unrestricted "Depends: xcat-genesis-scripts-amd64" +# reaches the ppc64el and riscv64 debs too. That package is Architecture: all, so it installs and +# apt reports no error -- it lays down the x86_64 Genesis tree and pulls the 128 MB amd64 +# genesis-base, and the management node gets no Genesis for its own architecture. The rpm side +# already selects per architecture through %{?genesistarch:Requires: xCAT-genesis-scripts-...}. +# +# xCAT-genesis-scripts keeps one control file per Debian architecture, and the file name is the +# Debian architecture. Its package name and its genesis-base dependency must carry that same +# architecture: xcat-genesis-base-ppc64 is a name no repository publishes, while the base deb +# that builddeb-genesis-base builds for ppc64el is xcat-genesis-base-ppc64el. + +# Return the folded value of a control field, or undef. +sub control_field { + my ($text, $name) = @_; + return $1 if $text =~ /^\Q$name\E:[ \t]*(.*(?:\n[ \t]+.*)*)/m; + return; +} + +# Split a dependency field into [package name, architecture restriction] pairs. Alternatives +# separated by "|" are returned one by one, because a restriction binds to one alternative. +sub dependency_terms { + my ($field) = @_; + my @terms; + return @terms unless defined $field; + $field =~ s/\n/ /g; + for my $dep (split /,/, $field) { + for my $alt (split /\|/, $dep) { + next unless $alt =~ /^\s*([A-Za-z0-9][A-Za-z0-9+.-]*)\s*(?:\([^)]*\))?\s*(?:\[([^\]]*)\])?/; + push @terms, [ $1, $2 ]; + } + } + return @terms; +} + +# dpkg-gencontrol drops a dependency whose architecture restriction excludes the build +# architecture. No restriction means the dependency reaches every architecture. +sub term_applies { + my ($restriction, $arch) = @_; + return 1 unless defined $restriction; + my @tokens = grep { length } split /\s+/, $restriction; + return 1 unless @tokens; + my $negated = ($tokens[0] =~ /^!/) ? 1 : 0; + my %named = map { my $t = $_; $t =~ s/^!//; $t =~ s/^any-//; ($t => 1) } @tokens; + return $negated ? (exists $named{$arch} ? 0 : 1) : (exists $named{$arch} ? 1 : 0); +} + +# The architectures xCAT-genesis-scripts is packaged for, taken from its per-architecture control +# files. riscv64 has none on purpose: its Genesis is the OpenEmbedded image. +my $scripts_debian = "$root/xCAT-genesis-scripts/debian"; +my @scripts_arches = sort map { m{/control-(.+)$} ? $1 : () } glob("$scripts_debian/control-*"); + +SKIP: { + skip 'xCAT-genesis-scripts has no per-architecture control files', 1 unless @scripts_arches; + + for my $arch (@scripts_arches) { + my $ctl = "$scripts_debian/control-$arch"; + open my $fh, '<', $ctl or die "read $ctl: $!"; + local $/; my $text = <$fh>; close $fh; + + my ($package) = ($text =~ /^Package:\s*(\S+)/m); + is($package, "xcat-genesis-scripts-$arch", + "control-$arch builds xcat-genesis-scripts-$arch"); + + my @bases = grep { /^xcat-genesis-base-/ } + map { $_->[0] } dependency_terms(control_field($text, 'Depends')); + is_deeply(\@bases, ["xcat-genesis-base-$arch"], + "xcat-genesis-scripts-$arch depends on xcat-genesis-base-$arch"); + } + + for my $ctl (@controls) { + open my $fh, '<', $ctl or die "read $ctl: $!"; + local $/; my $text = <$fh>; close $fh; + (my $short = $ctl) =~ s{^\Q$root\E/}{}; + + my ($arch_line) = ($text =~ /^Architecture:\s*(.+)$/m); + next unless defined $arch_line; + my @built = grep { !/^(?:any|all)$/ } split /\s+/, $arch_line; + + my @genesis = grep { $_->[0] =~ /^xcat-genesis-scripts-/ } + dependency_terms(control_field($text, 'Depends')); + + for my $arch (@built) { + my @reaching = map { $_->[0] } + grep { term_applies($_->[1], $arch) } @genesis; + my @foreign = grep { $_ ne "xcat-genesis-scripts-$arch" } @reaching; + is_deeply(\@foreign, [], + "$short on $arch depends on no other architecture's genesis scripts"); + + my %packaged = map { $_ => 1 } @scripts_arches; + next unless $packaged{$arch}; + ok(scalar(grep { $_ eq "xcat-genesis-scripts-$arch" } @reaching), + "$short on $arch depends on xcat-genesis-scripts-$arch"); + } + } +} + + done_testing();