diff --git a/native/goconserver-openeuler.t b/native/goconserver-openeuler.t index 90c7b35..2383972 100644 --- a/native/goconserver-openeuler.t +++ b/native/goconserver-openeuler.t @@ -6,6 +6,7 @@ use File::Basename qw(dirname); use File::Copy qw(copy); use File::Path qw(make_path); use File::Temp qw(tempdir); +use File::Slurper qw(read_text write_text); use Digest::SHA qw(sha256_hex); use JSON::PP qw(decode_json); use Test::More; @@ -32,23 +33,6 @@ my $payload = "private compiler download fixture\n"; my $hash = sha256_hex($payload); my $sequence = 0; -sub write_file { - my ($path, $text) = @_; - make_path(dirname($path)); - open(my $fh, '>', $path) or die "$path: $!"; - print {$fh} $text; - close($fh) or die "$path: $!"; -} - -sub read_file { - my ($path) = @_; - return '' unless -f $path; - open(my $fh, '<', $path) or die "$path: $!"; - my $text = do { local $/; <$fh> }; - close($fh) or die "$path: $!"; - return $text // ''; -} - my $double = <<'DOUBLE'; #!/usr/bin/perl use strict; @@ -136,14 +120,15 @@ sub run_case { my $checkout = "$directory/source"; my $bin = "$directory/bin"; make_path($checkout, $bin); - for my $relative ('MockBuildUtils.pm', 'lib/XCAT/BuildUtils.pm', 'goconserver/gomod/go.mod', 'goconserver/gomod/go.sum') { + for my $relative ('MockBuildUtils.pm', 'lib/XCAT/BuildUtils.pm', 'lib/XCAT/NFSLock.pm', 'goconserver/gomod/go.mod', 'goconserver/gomod/go.sum') { make_path(dirname("$checkout/$relative")); copy("$root/$relative", "$checkout/$relative") or die $!; } copy($builder, "$checkout/goconserver/mockbuild.pl") or die $!; - write_file("$checkout/goconserver/toolchains/go1.25.12.sha256", + make_path("$checkout/goconserver/toolchains"); + write_text("$checkout/goconserver/toolchains/go1.25.12.sha256", join('', map { "$hash go1.25.12.linux-$_.tar.gz\n" } qw(amd64 ppc64le))); - write_file("$bin/double", $double); + write_text("$bin/double", $double); chmod 0755, "$bin/double"; symlink('double', "$bin/$_") or die $! for qw(uname bash git wget mock rpm go rpmbuild rpm2cpio cpio); my @arguments = ('--work-dir', "$directory/work", '--result-dir', "$directory/results", @@ -173,9 +158,11 @@ sub run_case { } waitpid($pid, 0); my $status = $?; - my @commands = map { decode_json($_) } grep { length } split /\n/, read_file("$directory/commands.jsonl"); - return { directory => $directory, status => $status, output => read_file("$directory/output"), - spec => read_file("$directory/work/goconserver.spec"), commands => \@commands }; + my $log = -f "$directory/commands.jsonl" ? read_text("$directory/commands.jsonl") : ''; + my @commands = map { decode_json($_) } grep { length } split /\n/, $log; + return { directory => $directory, status => $status, output => read_text("$directory/output"), + spec => -f "$directory/work/goconserver.spec" ? read_text("$directory/work/goconserver.spec") : '', + commands => \@commands }; } sub calls { @@ -204,7 +191,7 @@ for my $cell (@cells) { my $config = "openeuler-$version-$arch"; my $case = run_case(os_version => $os_version, arch => $arch); is($case->{status}, 0, "$config full CLI succeeds with external build doubles") or diag($case->{output}); - like(read_file("$case->{directory}/work/mock-deterministic.cfg"), qr/^include\('\/etc\/mock\/\Q$config\E\.cfg'\)/m, + like(read_text("$case->{directory}/work/mock-deterministic.cfg"), qr/^include\('\/etc\/mock\/\Q$config\E\.cfg'\)/m, "$config builds inside its exact native config"); like($case->{spec}, qr/^Release:\s+4$/m, "$config retains the native empty dist macro"); like($case->{spec}, qr/^BuildArch:\s+\Q$arch\E$/m, "$config retains its native architecture"); @@ -214,7 +201,7 @@ for my $cell (@cells) { like($case->{spec}, qr/^echo '\Q$hash\E %\{SOURCE3\}' \| sha256sum -c -$/m, "$config verifies the compiler again in RPM prep"); is(scalar @{calls($case, 'mock', '--buildsrpm')}, 1, "$config reaches SRPM construction after verification"); is(scalar @{calls($case, 'mock', '--rebuild')}, 1, "$config reaches native RPM reconstruction"); - is(read_file("$case->{directory}/results/goconserver-0.3.3-4.$arch.rpm"), "fixture binary RPM\n", "$config collects the build output"); + is(read_text("$case->{directory}/results/goconserver-0.3.3-4.$arch.rpm"), "fixture binary RPM\n", "$config collects the build output"); ok(!-d "$case->{directory}/work/goconserver-src/.git", "$config removes fetched Git metadata from the sources"); build_metadata($case, '2023-11-14T22:13:20Z', $config); } @@ -223,7 +210,7 @@ for my $cell (@cells) { my $case = run_case(config => 'openeuler-22.03sp4-x86_64'); is($case->{status}, 0, 'explicit native target overrides host release detection'); is(scalar @{calls($case, 'bash')}, 0, 'explicit target requires no host release query'); - like(read_file("$case->{directory}/work/mock-deterministic.cfg"), qr/openeuler-22\.03sp4-x86_64\.cfg/, 'explicit service pack is retained'); + like(read_text("$case->{directory}/work/mock-deterministic.cfg"), qr/openeuler-22\.03sp4-x86_64\.cfg/, 'explicit service pack is retained'); } for my $options ( @@ -279,7 +266,7 @@ for my $row ( my ($options, $config, $release) = @$row; my $case = run_case(%$options); is($case->{status}, 0, "EL$release full CLI succeeds") or diag($case->{output}); - like(read_file("$case->{directory}/work/mock-deterministic.cfg"), qr/\Q$config\E\.cfg/, "EL$release retains the EL10 build peer"); + like(read_text("$case->{directory}/work/mock-deterministic.cfg"), qr/\Q$config\E\.cfg/, "EL$release retains the EL10 build peer"); like($case->{spec}, qr/^Release:\s+4\.el\Q$release\E$/m, "EL$release retains its target dist suffix"); like($case->{spec}, qr/^BuildRequires:\s+golang$/m, "EL$release retains the distro compiler"); unlike($case->{spec}, qr/^Source3:/m, "EL$release has no native compiler source"); @@ -298,7 +285,7 @@ for my $row ( is_deeply([map { $_->{goarch} } @$go], ['riscv64', 'riscv64'], 'EL cross path selects the target GOARCH'); is(scalar @{calls($case, 'rpmbuild', 'riscv64')}, 1, 'EL cross path packages for the requested target'); is(scalar @{calls($case, 'mock')} + scalar @{calls($case, 'wget')}, 0, 'EL cross path does not invoke native mock or compiler staging'); - is(read_file("$case->{directory}/results/goconserver-0.3.3-4.riscv64.rpm"), "fixture binary RPM\n", 'EL cross path collects its package output'); + is(read_text("$case->{directory}/results/goconserver-0.3.3-4.riscv64.rpm"), "fixture binary RPM\n", 'EL cross path collects its package output'); } done_testing(); diff --git a/native/openeuler-power-inputs.t b/native/openeuler-power-inputs.t index e74c8ee..b13f3c4 100644 --- a/native/openeuler-power-inputs.t +++ b/native/openeuler-power-inputs.t @@ -10,8 +10,8 @@ use JSON::PP; use Test::More; use lib "$RealBin/..", "$RealBin/../lib", "$RealBin/../t/lib"; -use MockBuildUtils qw(read_manifest); -use XCAT::BuildUtils qw(capture_command command_exists digest_file read_binary write_binary); +use MockBuildUtils qw(read_manifest sign_and_index_repo); +use XCAT::BuildUtils qw(capture_command command_exists digest_file digest_manifest relative_files read_binary write_binary); use XCAT::GenesisReleaseTest qw(run_capture dies_like); use XCAT::NativeInputs qw(load_inputs stage_inputs publisher_trust verify_input validate_outputs); @@ -239,6 +239,59 @@ for my $case (['publisher-elf', qr/ELF payload/], ['publisher-arch', qr/not a no ok(!-f $ENV{NATIVE_CALLS}, 'standalone verification runs no downloader or builder'); } +{ + my $dest = "$tmp/signing-repo"; + make_path($dest); + my $publisher = "$dest/publisher-package-1-1.oe2403.noarch.rpm"; + my $child = "$dest/native-child-1-1.oe2403.noarch.rpm"; + copy($signed{'publisher-package'}, $publisher) or die $!; + copy($rpm{'native-child'}, $child) or die $!; + my @commands; + my $sequence = 0; + my %options = ( + gpg_sign => 1, gpg_home => $homes{build}, gpg_key_name => $keys{build}, + gpg_program => '/usr/bin/gpg', source_date_epoch => $epoch, + run => sub { + my ($command) = @_; + push @commands, $command; + my $log = "$tmp/signing-command-" . ++$sequence . '.log'; + die read_binary($log) if run_capture($log, '/bin/sh', '-c', $command); + }, + ); + my $ok = eval { sign_and_index_repo($dest, $plan, %options); 1 }; + ok($ok, 'repository signing accepts unchanged publisher input') or die($@); + is(digest_file($publisher), digest_file($signed{'publisher-package'}), + 'repository signing preserves the original publisher bytes'); + is(run_capture("$tmp/signing-publisher.log", 'rpmkeys', '--dbpath', $plan->{trust_db}, + '--checksig', '--verbose', $publisher), 0, + 'publisher RPM retains its original trusted signature'); + my $build_trust = "$tmp/signing-build-trust"; + make_path($build_trust); + is(run_capture("$tmp/signing-build-import.log", 'rpmkeys', '--dbpath', $build_trust, + '--import', "$homes{build}/public.asc"), 0, 'trust the build key in an isolated RPM database'); + is(run_capture("$tmp/signing-child.log", 'rpmkeys', '--dbpath', $build_trust, + '--checksig', '--verbose', $child), 0, 'generated RPM verifies with the build key'); + like(read_binary("$tmp/signing-child.log"), qr/Signature.*\bOK\b/i, + 'generated RPM has a verified signature'); + is(run_capture("$tmp/signing-metadata.log", 'gpg', '--homedir', $homes{build}, + '--verify', "$dest/repodata/repomd.xml.asc", "$dest/repodata/repomd.xml"), 0, + 'repository metadata has a valid build signature'); + + my $changed = signed_copy($rpm{'publisher-package'}, 'changed-before-signing', 'build'); + copy($changed, $publisher) or die $!; + copy($rpm{'native-child'}, $child) or die $!; + isnt(digest_file($publisher), $plan->{nodes}{'publisher-package'}{sha256}, + 'the changed publisher RPM differs from its pinned input'); + my $before = digest_manifest($dest, 'sha256', relative_files($dest)); + @commands = (); + dies_like(sub { sign_and_index_repo($dest, $plan, %options) }, + qr/\APublisher input changed before signing: \Q$publisher\E\n\z/, + 'changed publisher bytes stop repository signing'); + is_deeply(\@commands, [], 'changed publisher bytes stop before signing or indexing commands'); + is(digest_manifest($dest, 'sha256', relative_files($dest)), $before, + 'rejection preserves generated RPMs, publisher RPMs and repository metadata'); +} + my @namespace = ('unshare', ($> == 0 ? () : ('--user', '--map-root-user')), '--mount', '--propagation', 'private'); my $can_owner = $host_arch eq 'ppc64le' && run_capture("$tmp/mock-loader.log", 'python3', '-c', 'from mockbuild.util import load_config') == 0 diff --git a/native/openeuler-srpm.t b/native/openeuler-srpm.t index 860a109..fd9e899 100644 --- a/native/openeuler-srpm.t +++ b/native/openeuler-srpm.t @@ -2,6 +2,7 @@ use strict; use warnings; use Cwd qw(abs_path cwd); +use File::Basename qw(dirname basename); use File::Copy qw(copy); use File::Path qw(make_path); use File::Spec; @@ -13,6 +14,7 @@ use Test::More; use lib "$RealBin/../lib", "$RealBin/../t/lib"; use XCAT::BuildUtils qw(capture_command command_exists digest_file read_binary write_binary); use XCAT::GenesisReleaseTest qw(run_capture); +use XCAT::NFSLock (); plan skip_all => 'Linux RPM tools and user namespaces required' unless $^O eq 'linux' && !grep { !command_exists($_) } qw(rpm rpmkeys rpmbuild createrepo_c unshare gpg gpgconf); @@ -98,6 +100,14 @@ PERL make_path("$repo/$cell"); copy($opt{published}{$cell}, "$repo/$cell/python3-scp-0.14.5-1.noarch.rpm") or die $!; } + my ($held_lock, $lock_path, $lock_before); + if ($opt{hold_cell}) { + my $cell = "$repo/$opt{hold_cell}"; + make_path(dirname($cell)); + $lock_path = dirname($cell) . '/.' . basename($cell) . '.lock'; + $held_lock = XCAT::NFSLock->acquire($lock_path, quiet => 1); + $lock_before = read_binary("$lock_path/metadata"); + } local $ENV{PATH} = "$tmp/bin:$ENV{PATH}"; local $ENV{MOCKBUILD_ALL_MOUNTNS} = 1; local $ENV{SCP_CALLS} = "$tmp/$name calls.jsonl"; @@ -119,8 +129,11 @@ PERL '--skip-genesis', '--skip-perl', '--skip-tarball', @options); my @calls = -f $ENV{SCP_CALLS} ? map { decode_json($_) } split /\n/, read_binary($ENV{SCP_CALLS}) : (); + my $lock_after = $held_lock && -f "$lock_path/metadata" ? read_binary("$lock_path/metadata") : undef; + $held_lock->release if $held_lock; return {rc => $rc, calls => \@calls, log => read_binary("$tmp/$name.log"), input => $input, - repo => $repo, out => $out, root => $root}; + repo => $repo, out => $out, root => $root, lock_path => $lock_path, + lock_before => $lock_before, lock_after => $lock_after}; } my $selected = scenario('selected'); @@ -253,6 +266,93 @@ my $wrong_cell = scenario('carry-wrong-cell', missing => 1, isnt($wrong_cell->{rc}, 0, 'a signed package in the EL-shaped path cannot fill a native cell'); like($wrong_cell->{log}, qr/MISSING python3-scp/, 'the native gate reports the package absent from its own cell'); +for my $held ('openeuler20.03sp4/x86_64', 'rh20.03sp4/x86_64') { + my $native = $held =~ /^openeuler/; + my $result = scenario($native ? 'native-lock' : 'decoy-lock', missing => 1, + hold_cell => $held, published => {'openeuler20.03sp4/x86_64' => $published}, + options => ['--skip-xcat-dep', '--try-unlock-timeout', 0]); + if ($native) { + isnt($result->{rc}, 0, 'the native published cell lock excludes a second publisher'); + like($result->{log}, qr/Trying to unlock \Q$result->{lock_path}\E failed/, + 'the refusal names the lock beside the native published cell'); + is(read_binary("$result->{repo}/openeuler20.03sp4/x86_64/sentinel"), 'previous repository', + 'native lock refusal preserves the published repository'); + ok(!-d "$result->{out}/mockbuild-all/$target-source-contract", + 'native lock refusal precedes carry-over and collection'); + } else { + is($result->{rc}, 0, 'an EL-shaped decoy lock does not block native carry-over and deployment') + or diag($result->{log}); + ok(-f "$result->{repo}/openeuler20.03sp4/x86_64/python3-scp-0.14.5-1.noarch.rpm", + 'the unlocked native cell receives the carried package'); + my $metadata = "$result->{repo}/openeuler20.03sp4/x86_64/xcat-dep.repo"; + my $config = -f $metadata ? read_binary($metadata) : ''; + like($config, qr{^baseurl=.*\/openeuler20\.03sp4/x86_64$}m, + 'the native repository configuration names the deployed cell'); + } + is_deeply($result->{calls}, [], "$held lock case runs no package builder"); + is($result->{lock_after}, $result->{lock_before}, "$held remains held by its original owner"); +} + +for my $name ('native-only', 'mixed', 'legacy-locked') { + my $root = "$tmp/finalize-$name"; + my @native = ("$root/openeuler20.03sp4/x86_64", "$root/openeuler24.03/ppc64le"); + make_path(@native); + write_binary("$_/marker", 'native repository') for @native; + my @held_cells = @native; + my ($x, $p) = ("$root/rh9/x86_64", "$root/rh9/ppc64le"); + my ($xrpm, $prpm) = ('xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm', + 'xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm'); + if ($name ne 'native-only') { + make_path($x, $p); + copy($published, "$x/$xrpm") or die $!; + copy($published, "$p/$prpm") or die $!; + push @held_cells, $p; + push @held_cells, $x if $name eq 'legacy-locked'; + } + my @held; + for my $cell (@held_cells) { + my $path = dirname($cell) . '/.' . basename($cell) . '.lock'; + my $lock = XCAT::NFSLock->acquire($path, quiet => 1); + push @held, [$lock, $path, read_binary("$path/metadata")]; + } + my $logfile = "$tmp/finalize-$name.log"; + my $rc = run_capture($logfile, $^X, $collector, '--finalize-xcat-dep', + '--x86_64-repo', $root, '--ppc64le-repo', $root, '--finalize-arch', 'x86_64', + '--build-timestamp', $epoch, '--try-unlock-timeout', 0, '--no-verify-repo'); + my $log = read_binary($logfile); + if ($name eq 'legacy-locked') { + isnt($rc, 0, 'finalization refuses a held legacy destination lock'); + like($log, qr{Trying to unlock \Q$root\E/rh9/\.x86_64\.lock failed}, + 'finalization refuses the same sibling lock as an EL publisher'); + ok(!-e "$x/$prpm", 'lock refusal precedes the legacy cross-copy'); + ok(!-d "$x/repodata", 'lock refusal precedes legacy reindexing'); + } else { + is($rc, 0, "$name finalization ignores held native and unselected cell locks") or diag($log); + if ($name eq 'mixed') { + ok(-f "$x/$prpm", 'selected legacy destination receives its foreign Genesis'); + is(-f "$x/$prpm" ? digest_file("$x/$prpm") : undef, digest_file("$p/$prpm"), + 'legacy cross-copy preserves the source RPM'); + ok(-f "$x/repodata/repomd.xml", 'selected legacy destination is reindexed'); + ok(!-e "$p/$xrpm", 'unselected legacy source receives no foreign Genesis'); + ok(!-d "$p/repodata", 'unselected legacy source is not reindexed'); + } else { + like($log, qr/openEuler.*skipping/, 'native-only finalization explains the skip'); + unlike($log, qr/(?:acquired|took-over) repository cell lock/, + 'native-only finalization acquires no cell lock'); + } + } + for my $native (@native) { + is_deeply([glob("$native/*")], ["$native/marker"], "$name adds no native artifacts"); + is(read_binary("$native/marker"), 'native repository', "$name preserves native content"); + } + for my $held (@held) { + my ($lock, $path, $before) = @$held; + is(-f "$path/metadata" ? read_binary("$path/metadata") : undef, $before, + "$name preserves the existing owner of $path"); + $lock->release; + } +} + my $skipped = scenario('skip-dep', missing => 1, options => ['--skip-xcat-dep', '--dry-run']); is($skipped->{rc}, 0, 'skipping dependency builds does not require the source RPM'); is_deeply($skipped->{calls}, [], 'skip-dep executes no source action'); diff --git a/native/xnba-release-suffix.t b/native/xnba-release-suffix.t index ca36c80..8821e96 100644 --- a/native/xnba-release-suffix.t +++ b/native/xnba-release-suffix.t @@ -29,9 +29,10 @@ my $epoch = 1788718796; my $suffix = '.snap202609061819.21'; my $default_release = capture_command('rpm', '--eval', '1%{?dist}'); my $source = "$tmp/source tree"; -make_path("$source/xnba/binary"); +make_path("$source/xnba/binary", "$source/lib/XCAT"); copy($owner, "$source/xnba/mockbuild.pl") or die $!; copy("$repo/MockBuildUtils.pm", "$source/MockBuildUtils.pm") or die $!; +copy("$repo/lib/XCAT/NFSLock.pm", "$source/lib/XCAT/NFSLock.pm") or die $!; copy("$repo/xnba/xnba-undi.spec", "$source/xnba/xnba-undi.spec") or die $!; copy("$repo/xnba/binary/$_", "$source/xnba/binary/$_") or die $! for qw(xnba.kpxe xnba.efi); my %specs; diff --git a/t/mockbuild-all.t b/t/mockbuild-all.t index 15dbd06..c5ea55c 100644 --- a/t/mockbuild-all.t +++ b/t/mockbuild-all.t @@ -10,7 +10,7 @@ use lib "$RealBin/.."; use File::Temp qw(tempdir); use File::Path qw(make_path); use File::Basename qw(basename); -use File::Slurper qw(write_text); +use File::Slurper qw(read_text write_text); use MockBuildUtils qw(install_deps_packages install_deps_command missing_perl_modules required_pkgs version_matches rpm_sigmd5 rpm_version rpm_release rpm_is_signed rpm_arch rpm_in_cell resolve_mock_cfg @@ -403,6 +403,81 @@ SPEC 'the refusal names the arch'); } +{ + my $tmp = tempdir(CLEANUP => 1); + my ($x, $p) = ("$tmp/x/rh9/x86_64", "$tmp/p/rh9/ppc64le"); + my @native = ("$tmp/x/openeuler20.03sp4/x86_64", "$tmp/p/openeuler24.03/ppc64le"); + make_path($x, $p, @native); + write_text("$x/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm", "x86 genesis\n"); + write_text("$p/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm", "ppc genesis\n"); + write_text("$_/marker", "native repository\n") for @native; + my (@signed, @reindexed); + my $ok = eval { + quiet { finalize_xcat_dep("$tmp/x", "$tmp/p", + sign => sub { push @signed, $_[0] }, + reindex => sub { push @reindexed, $_[0] }) }; + 1; + }; + ok($ok, 'mixed roots finalize their legacy cells without native peer requirements') or diag($@); + is(-f "$x/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm" + ? read_text("$x/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm") : undef, "ppc genesis\n", + 'the legacy x86 cell receives its foreign Genesis'); + is(-f "$p/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm" + ? read_text("$p/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm") : undef, "x86 genesis\n", + 'the legacy ppc cell receives its foreign Genesis'); + is_deeply([sort @signed], [sort { $a cmp $b } ("$x/xCAT-genesis-base-ppc64-2.19.1-1.noarch.rpm", + "$p/xCAT-genesis-base-x86_64-2.19.1-1.noarch.rpm")], 'only legacy copies are signed'); + is_deeply([sort @reindexed], [sort { $a cmp $b } ($x, $p)], 'only legacy cells are indexed'); + for my $native (@native) { + is_deeply([glob("$native/*")], ["$native/marker"], 'finalize adds no native artifacts'); + is(read_text("$native/marker"), "native repository\n", 'finalize preserves native content'); + } +} + +{ + my $tmp = tempdir(CLEANUP => 1); + my @native = ("$tmp/x/openeuler20.03sp4/x86_64", "$tmp/p/openeuler24.03/ppc64le"); + make_path(@native); + write_text("$_/marker", "native repository\n") for @native; + my (@signed, @reindexed); + my $ok = eval { + quiet { finalize_xcat_dep("$tmp/x", "$tmp/p", only => ['x86_64'], + sign => sub { push @signed, $_[0] }, + reindex => sub { push @reindexed, $_[0] }) }; + 1; + }; + ok($ok, 'native-only roots require no legacy Genesis finalization') or diag($@); + is_deeply(\@signed, [], 'native-only finalization signs nothing'); + is_deeply(\@reindexed, [], 'native-only finalization indexes nothing'); + for my $native (@native) { + is_deeply([glob("$native/*")], ["$native/marker"], 'native-only finalization adds no artifacts'); + is(read_text("$native/marker"), "native repository\n", 'native-only finalization preserves content'); + } + my $bad = eval { quiet { finalize_xcat_dep("$tmp/x", "$tmp/p", only => ['riscv64']) }; 1 }; + ok(!$bad, 'native-only roots still reject an unsupported finalization architecture'); + like($@, qr/no cross-arch genesis for arch 'riscv64'/, 'native-only validation names the bad architecture'); +} + +for my $legacy ('x86-only', 'ppc-only', 'missing-genesis') { + my $tmp = tempdir(CLEANUP => 1); + make_path("$tmp/x/openeuler20.03sp4/x86_64", "$tmp/p/openeuler24.03/ppc64le"); + make_path("$tmp/x/rh9/x86_64") unless $legacy eq 'ppc-only'; + make_path("$tmp/p/rh9/ppc64le") unless $legacy eq 'x86-only'; + my (@signed, @reindexed); + my $ok = eval { + quiet { finalize_xcat_dep("$tmp/x", "$tmp/p", + sign => sub { push @signed, $_[0] }, + reindex => sub { push @reindexed, $_[0] }) }; + 1; + }; + ok(!$ok, "$legacy legacy input remains fatal in mixed roots"); + my $expected = $legacy eq 'x86-only' ? qr/no ppc64le peer repo/ + : $legacy eq 'ppc-only' ? qr/no x86_64 peer repo/ : qr/no x86_64 xCAT-genesis-base/; + like($@, $expected, "$legacy reports the missing legacy input"); + is_deeply(\@signed, [], "$legacy signs nothing"); + is_deeply(\@reindexed, [], "$legacy indexes nothing"); +} + # ---- restamp_release_line: CD --build-number Release stamping (PR #62 review point 1) ---------- # A fresh stamp is appended after the Release token, preserving any %{?dist} macro. { diff --git a/t/openeuler.t b/t/openeuler.t index b063f8b..a2ea7a8 100644 --- a/t/openeuler.t +++ b/t/openeuler.t @@ -5,8 +5,9 @@ use FindBin qw($RealBin); use lib "$RealBin/.."; use Test::More; use MockBuildUtils qw(openeuler_build_target openeuler_repo_subdir install_deps_command - install_deps_packages derive_target_from_repo_path); + install_deps_packages derive_target_from_repo_path read_manifest); +my %manifest = read_manifest("$RealBin/../packages-manifest.conf"); my @cells = ( ['20.03sp4', '20.03-LTS-SP4', '20.03LTS_SP4', 'x86_64'], ['22.03sp4', '22.03-LTS-SP4', '22.03LTS_SP4', 'x86_64'], @@ -20,6 +21,8 @@ for my $cell (@cells) { my ($base, $sp) = $version =~ /^(\d+\.\d+)(?:sp(\d+))?$/; my $native_version = "$base (LTS" . (defined($sp) ? "-SP$sp" : '') . ')'; my $target = "openeuler-$version-$arch"; + ok(-f "$RealBin/../mock-configs/$target.cfg", "$target has a native mock config"); + ok(exists $manifest{$target}{goconserver}, "$target has a native package manifest"); is(openeuler_build_target({ID => 'openEuler', VERSION => $native_version}, $arch), $target, "$target retains the native service pack"); is(openeuler_repo_subdir($target), "openeuler$version/$arch", "$target preserves repository provenance");