From be52c514d0d4e435580b69c7a9eeb22ecc0f9319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vin=C3=ADcius=20Ferr=C3=A3o?= <2031761+viniciusferrao@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:16:23 -0300 Subject: [PATCH] test(genesis): cover release manifest guards --- t/common-repo-gate.t | 41 +++++++++++++--- t/genesis_openembedded_consumer.t | 78 ++++++++++++++++++++++++++----- t/genesis_openembedded_release.t | 34 ++++++++++++-- 3 files changed, 132 insertions(+), 21 deletions(-) diff --git a/t/common-repo-gate.t b/t/common-repo-gate.t index e05e015..62c8217 100644 --- a/t/common-repo-gate.t +++ b/t/common-repo-gate.t @@ -49,7 +49,6 @@ if (@missing_requirements) { my $tmp = tempdir(CLEANUP => 1); my $target = 'alma+epel-10-' . do { my $m = `uname -m`; chomp $m; $m }; my @architectures = architectures(); -my $architecture_count = scalar(@architectures); my $xcat_version = '2.19.0'; my $xcat_release = 'snap202609040000'; my $xcat_revision = 'c' x 40; @@ -85,8 +84,9 @@ write_checksums($VERSION_1_RELEASE); { my %m = read_manifest("$RealBin/../packages-manifest.conf"); ok($m{common} && %{ $m{common} }, 'the shipped manifest has a [common] section'); - my @missing = grep { !exists $m{common}{ rpm_package_name($_) } } @architectures; - is_deeply(\@missing, [], + my @expected = sort map { rpm_package_name($_) } + qw(x86 x86_64 ppc64 ppc64le armv7hf aarch64 riscv64 s390x); + is_deeply([ sort keys %{ $m{common} } ], \@expected, 'the shared RPM manifest lists every Genesis architecture'); } @@ -166,19 +166,48 @@ sub run_publish { { my ($rc, $out, $common) = run_publish($RELEASE, 'full'); is($rc, 0, 'a complete release publishes') or diag($out); - is(scalar(grep { !/\.src\.rpm$/ } glob("$common/*.rpm")), $architecture_count, + is(scalar(grep { !/\.src\.rpm$/ } glob("$common/*.rpm")), 8, 'the published shared repo carries every architecture'); - like($out, qr/\[verify-repo\] common complete/, 'the shared repo is gated against [common]'); + like($out, qr/\[verify-repo\] common complete: 8 packages present/, + 'the shared repo is gated against [common]'); } { my ($rc, $out, $common) = run_publish($VERSION_1_RELEASE, 'version-1'); is($rc, 0, 'a complete version 1 release publishes') or diag($out); is(scalar(grep { !/\.src\.rpm$/ } glob("$common/*.rpm")), - scalar(@version_1_architectures), + 7, 'the version 1 repository keeps its seven architectures'); ok(!glob("$common/" . rpm_package_name('s390x') . '-*.rpm'), 'the version 1 repository does not require s390x'); + like($out, qr/\[verify-repo\] common complete: 7 packages present/, + 'the version 1 repository is gated against seven packages'); +} + +{ + my ($rc, $out, $common) = run_publish( + $VERSION_1_RELEASE, + 'missing-current-package', + sub { delete $_[0]->{ rpm_package_name('s390x') } }, + ); + isnt($rc, 0, 'a version 1 release does not hide an incomplete current manifest'); + like($out, qr/\[common\] is missing supported packages: .*s390x/, + 'the manifest failure identifies the missing current package'); + ok(!-d $common || !glob("$common/*.rpm"), + 'an incomplete current manifest publishes nothing'); +} + +{ + my ($rc, $out, $common) = run_publish( + $RELEASE, + 'unknown-current-package', + sub { $_[0]->{'xCAT-genesis-openembedded-unknown'} = '>= 2.18.0' }, + ); + isnt($rc, 0, 'an unknown shared manifest package is refused'); + like($out, qr/\[common\] has unsupported packages: xCAT-genesis-openembedded-unknown/, + 'the manifest failure identifies the unknown package'); + ok(!-d $common || !glob("$common/*.rpm"), + 'an unknown shared manifest package publishes nothing'); } # ---- an incomplete release is refused, and publishes nothing -------------------------------------- diff --git a/t/genesis_openembedded_consumer.t b/t/genesis_openembedded_consumer.t index aeec525..72559b1 100644 --- a/t/genesis_openembedded_consumer.t +++ b/t/genesis_openembedded_consumer.t @@ -75,7 +75,7 @@ SKIP: { } SKIP: { - skip 'APT repository tools are not installed', 62 + skip 'APT repository tools are not installed', 69 unless $^O eq 'linux' && command_exists('dpkg-deb') && command_exists('apt-ftparchive'); @@ -301,14 +301,7 @@ sub run_apt_consumer { my $manifest = $args{manifest}; unless ($manifest) { $manifest = "$args{output}/manifest.conf"; - make_path($args{output}); - # plus the shipped [shared] section verbatim: publishing a release gates the shared pool - # against it, and a manifest without it is refused rather than silently ungated. - my $shipped = read_binary("$repo_root/debs-manifest.conf"); - my ($shared) = $shipped =~ /^(\[shared\]\n(?:[^\[]*))/ms; - BAIL_OUT('debs-manifest.conf has no [shared] section') unless $shared; - write_binary($manifest, - join('', map { "[$_-amd64]\nxcat-genesis-base=*\n" } @APT_SUITES) . "\n" . $shared); + write_apt_manifest($manifest); } return run_capture( $args{log}, @@ -326,6 +319,23 @@ sub run_apt_consumer { ); } +sub write_apt_manifest { + my ($path, $mutate) = @_; + my $shipped = read_binary("$repo_root/debs-manifest.conf"); + my ($section) = $shipped =~ /^\[shared\]\n([^\[]*)/ms; + BAIL_OUT('debs-manifest.conf has no [shared] section') unless defined $section; + my %shared = map { /^([^=]+)=(.*)$/ ? ($1 => $2) : () } + grep { length } split(/\n/, $section); + $mutate->(\%shared) if $mutate; + make_path((File::Basename::dirname($path))); + write_binary( + $path, + join('', map { "[$_-amd64]\nxcat-genesis-base=*\n" } @APT_SUITES) + . "\n[shared]\n" + . join('', map { "$_=$shared{$_}\n" } sort keys %shared), + ); +} + # Stage one legacy genesis-base deb for the noble suite only -- enough for the runs that publish # no Genesis release. sub stage_legacy_deb { @@ -398,9 +408,8 @@ sub test_deb_consumer { } architectures(); is_deeply([ genesis_deb_names($shared_pool) ], \@expected_packages, 'shared APT pool contains one complete Genesis release'); - my $architecture_count = scalar(architectures()); like(read_binary($log), - qr/\[verify-repo\] shared pool complete: \Q$architecture_count\E packages present/, + qr/\[verify-repo\] shared pool complete: 8 packages present/, 'the shared pool is gated against the manifest\'s [shared] section'); my @suite_packages; for my $codename (@APT_SUITES) { @@ -549,10 +558,55 @@ sub test_version_1_deb_consumer { my $pool = "$apt_root/pool/main/xcat-genesis-openembedded"; is($status, 0, 'APT accepts a complete version 1 release'); - is(scalar(genesis_deb_names($pool)), scalar(@release_architectures), + is(scalar(genesis_deb_names($pool)), 7, 'the version 1 pool keeps its seven architectures'); is(scalar(grep { /s390x/ } genesis_deb_names($pool)), 0, 'the version 1 pool does not require s390x'); + like(read_binary($log), + qr/\[verify-repo\] shared pool complete: 7 packages present/, + 'the version 1 pool is gated against seven packages'); + + my $missing_manifest = "$tmp/deb-version-1-missing.conf"; + write_apt_manifest( + $missing_manifest, + sub { delete $_[0]->{ deb_package_name('s390x') } }, + ); + my $missing_apt = "$tmp/apt-version-1-missing"; + my $missing_output = "$tmp/deb-version-1-missing-output"; + stage_apt_suites($missing_output, "$tmp/deb-version-1-missing-legacy"); + my $missing_log = "$tmp/deb-version-1-missing.log"; + my $missing_status = run_apt_consumer( + log => $missing_log, output => $missing_output, apt_dir => $missing_apt, + manifest => $missing_manifest, + extra => [ '--genesis-release', $release_root ], + ); + isnt($missing_status, 0, + 'a version 1 release does not hide an incomplete current shared manifest'); + like(read_binary($missing_log), qr/\[shared\] is missing supported packages: .*s390x/, + 'the shared manifest failure identifies the missing current package'); + ok(!-d "$missing_apt/pool/main/xcat-genesis-openembedded", + 'an incomplete shared manifest publishes nothing'); + + my $unknown_manifest = "$tmp/deb-version-1-unknown.conf"; + write_apt_manifest( + $unknown_manifest, + sub { $_[0]->{'xcat-genesis-openembedded-unknown'} = '2.*' }, + ); + my $unknown_apt = "$tmp/apt-version-1-unknown"; + my $unknown_output = "$tmp/deb-version-1-unknown-output"; + stage_apt_suites($unknown_output, "$tmp/deb-version-1-unknown-legacy"); + my $unknown_log = "$tmp/deb-version-1-unknown.log"; + my $unknown_status = run_apt_consumer( + log => $unknown_log, output => $unknown_output, apt_dir => $unknown_apt, + manifest => $unknown_manifest, + extra => [ '--genesis-release', $release_root ], + ); + isnt($unknown_status, 0, 'an unknown shared manifest package is refused'); + like(read_binary($unknown_log), + qr/\[shared\] has unsupported packages: xcat-genesis-openembedded-unknown/, + 'the shared manifest failure identifies the unknown package'); + ok(!-d "$unknown_apt/pool/main/xcat-genesis-openembedded", + 'an unknown shared manifest package publishes nothing'); } sub test_signed_common_rpm_repository { diff --git a/t/genesis_openembedded_release.t b/t/genesis_openembedded_release.t index 68cb60a..b1adf4e 100644 --- a/t/genesis_openembedded_release.t +++ b/t/genesis_openembedded_release.t @@ -216,6 +216,19 @@ write_checksums($legacy_release); ok(validate_complete_release($legacy_release), 'complete version 1 releases remain publishable'); +my $invalid_legacy_release = "$tmp/invalid-legacy-release"; +copy_tree($complete_release, $invalid_legacy_release); +write_release_manifest( + $invalid_legacy_release, $version, $release, $revision, $epoch, + join(',', architectures()), 'deb,rpm', 1, +); +write_checksums($invalid_legacy_release); +dies_like( + sub { validate_release($invalid_legacy_release) }, + qr/Genesis architecture s390x is not valid in release version 1/, + 'version 1 rejects the version 2 architecture vocabulary', +); + my $unknown_release_version = "$tmp/unknown-release-version"; copy_tree($complete_release, $unknown_release_version); write_release_manifest( @@ -259,12 +272,13 @@ dies_like(sub { validate_release($missing_release) }, qr/Genesis release is miss 'incomplete architecture set fails'); SKIP: { - skip 'git is not installed', 2 unless command_exists('git'); + skip 'git is not installed', 4 unless command_exists('git'); my $source = "$tmp/dirty-xcat-core"; - make_path("$source/xCAT-genesis-builder/oe"); + make_path("$source/xCAT-genesis-builder/oe/kas"); write_binary("$source/Version", "$version\n"); write_binary("$source/xCAT-genesis-builder/oe/build", "#!/bin/sh\nexit 99\n"); write_binary("$source/xCAT-genesis-builder/oe/export", "#!/bin/sh\nexit 99\n"); + write_binary("$source/xCAT-genesis-builder/oe/kas/x86_64.yml", "header: {}\n"); for my $command ( [ 'git', '-C', $source, 'init', '-q' ], [ 'git', '-C', $source, 'add', '.' ], @@ -286,6 +300,19 @@ SKIP: { ); like(read_binary($log), qr/xcat-core checkout is not clean/, 'dirty checkout failure is explicit'); + + my $target_log = "$tmp/missing-target.log"; + isnt( + run_capture( + $target_log, $builder, '--xcat-source', $source, + '--architecture', 's390x', + '--output-dir', "$tmp/missing-target-output", + ), + 0, + 'release builder rejects an unsupported xcat-core target', + ); + like(read_binary($target_log), qr/does not support Genesis architecture s390x/, + 'missing target failure identifies the required xcat-core support'); } SKIP: { @@ -484,8 +511,9 @@ sub exercise_packager_from_unsearchable_cwd { sub exercise_builder_tmpdir { my $source = "$tmp/tmpdir-xcat-core"; my $oe = "$source/xCAT-genesis-builder/oe"; - make_path($oe); + make_path("$oe/kas"); write_binary("$source/Version", "$version\n"); + write_binary("$oe/kas/x86_64.yml", "header: {}\n"); write_binary( "$oe/build", <<'BUILD',