2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-10-21 15:18:19 +00:00
Files
xcat-core/xCAT-test/unit/github_action_noninteractive_install.t
T
Vinícius Ferrão c97b8d412b test(ci): exercise the GitHub action driver
Run the unchanged driver with command and filesystem fixtures instead of
matching its source. Check apt arguments, syntax exclusions, and failure
propagation. Parse the workflow with YAML::PP and install it in CI.
2026-10-02 19:24:04 -03:00

181 lines
8.3 KiB
Perl

#!/usr/bin/env perl
use strict;
use warnings;
use Capture::Tiny qw(capture);
use Cwd qw(abs_path getcwd);
use File::Path qw(make_path);
use File::Slurper qw(read_binary read_text write_text);
use File::Temp qw(tempdir);
use FindBin;
use JSON::PP qw(decode_json);
use Text::ParseWords qw(shellwords);
use Test::More;
use YAML::PP;
use lib "$FindBin::Bin/../lib";
use XCAT::Test::File qw(repo_path);
my $driver = repo_path('github_action_xcat_test.pl');
my $fixture = repo_path('xCAT-test/unit/fixtures/github_action');
my $runner = abs_path(tempdir(CLEANUP => 1));
my $workspace = "$runner/work";
my $checkout = "$workspace/xcat-core";
my $runner_temp = "$runner/temp";
my $repository = "$checkout/dist/debs/xcat-core";
make_path($repository, $runner_temp);
write_text("$repository/mklocalrepo.sh", "#!/bin/sh\nexit 0\n");
chmod(0755, "$repository/mklocalrepo.sh") or die "chmod repository fixture: $!";
sub run_driver {
my ($failure, $failed_case) = @_;
local $ENV{XCAT_TEST_CI_COMMANDS} = "$runner/commands.jsonl";
local $ENV{XCAT_TEST_CI_FAIL_COMMAND} = defined($failure) ? $failure : '';
local $ENV{XCAT_TEST_CI_FAIL_CASE} = $failed_case || 0;
local $ENV{RUNNER_WORKSPACE} = $workspace;
local $ENV{RUNNER_TEMP} = $runner_temp;
local $ENV{PWD} = $checkout;
write_text($ENV{XCAT_TEST_CI_COMMANDS}, '');
if (-e "$checkout/regression.conf") {
unlink("$checkout/regression.conf") or die "remove regression fixture: $!";
}
my $cwd = getcwd();
chdir($checkout) or die "chdir checkout: $!";
my ($stdout, $stderr, $status);
my $captured = eval {
($stdout, $stderr, $status) = capture {
system($^X, '-I', $fixture, '-MCommands', $driver);
};
1;
};
my $error = $@;
chdir($cwd) or die "restore working directory: $!";
die $error unless $captured;
my @commands = map { decode_json($_) }
split /\n/, read_binary($ENV{XCAT_TEST_CI_COMMANDS});
is($stderr, '', 'driver emits no warnings') or diag($stdout);
return ($status, \@commands, $stdout);
}
my ($status, $commands, $output) = run_driver();
is($status, 0, 'the full CI driver succeeds with command fixtures') or diag($output);
my @apt = grep { /\bapt-get\b/ } @$commands;
is(scalar @apt, 4, 'the driver updates apt and installs all three packages');
is_deeply(
[shellwords($apt[0] || '')],
[qw(sudo timeout 600 apt-get -qq -o Acquire::Retries=3 -o Acquire::http::Timeout=30
--allow-insecure-repositories update 2>&1)],
'apt update has a time limit and bounded network retries',
);
my @packages = qw(xcat xcat-probe xcat-test);
for my $index (0 .. $#packages) {
is_deeply(
[shellwords($apt[$index + 1] || '')],
[qw(sudo timeout 1200 env DEBIAN_FRONTEND=noninteractive apt-get
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 install -y),
$packages[$index], qw(--allow-remove-essential --allow-unauthenticated 2>&1)],
"$packages[$index] installation is noninteractive and bounded",
);
}
my @checked = grep { /\bperl .* -c / } @$commands;
my @expected_files = (
'/opt/xcat/lib/perl/xCAT/Example.pm',
'/opt/xcat/share/xcat/tools/autotest/unit-extra/example.pl',
'/opt/xcat/probe-extra/example.pl',
'/opt/xcat/share/xcat/netboot/genesis-extra/example.pl',
'/install/postscripts/example',
);
is_deeply(
\@checked,
[map { "sudo bash -c '. /etc/profile.d/xcat.sh && perl -I /opt/xcat/lib/perl -I /opt/xcat/lib -I /usr/lib/perl5 -I /usr/share/perl -c $_' 2>&1" } @expected_files],
'syntax checks exclude payloads, installed unit tests, and non-Perl files',
);
ok(grep(/xcattest -f .* -t example_case/, @$commands),
'the successful driver reaches the regression case');
like(read_text("$checkout/regression.conf"), qr/^MN=ci-node$/m,
'the regression configuration uses the reported hostname');
ok(!-e "$workspace/regression.conf", 'changing directory does not change the configuration destination');
my $unitsrc = "$runner_temp/xcat-core-unitsrc";
my $preserve = "rm -rf $unitsrc && cp -a $checkout $unitsrc 2>&1";
my $build = 'sudo ./builddebs.pl --force 2>&1';
my $unit = "cd $unitsrc && prove -r xCAT-test/unit 2>&1";
my $bats = "cd $unitsrc && bats -r xCAT-test/bats 2>&1";
is_deeply([grep { $_ eq $preserve || $_ eq $build || $_ eq $unit || $_ eq $bats } @$commands],
[$preserve, $build, $unit, $bats],
'the source is preserved before building and both suites use the preserved tree');
for my $stage (
['apt update', $apt[0]], ['xcat install', $apt[1]], ['xcat-test install', $apt[3]],
['source preservation', $preserve], ['package build', $build],
['unit tests', $unit], ['BATS tests', $bats],
) {
my ($name, $failure) = @$stage;
subtest "stop after failed $name" => sub {
my ($index) = grep { defined($failure) && $commands->[$_] eq $failure } 0 .. $#$commands;
ok(defined($index), 'the command was reached in the successful run') or return;
my ($failed_status, $failed_commands) = run_driver($failure);
is($failed_status, 256, 'the driver reports failure');
is_deeply($failed_commands, [@$commands[0 .. $index]],
'no later command runs after the failed operation');
};
}
my @setup_steps = grep { /setup-local-client\.sh/ } @$commands;
is(scalar @setup_steps, 1, 'local client setup runs once');
for my $stage (
['local client setup', $setup_steps[0]], ['xcat-probe install', $apt[2]],
) {
my ($name, $failure) = @$stage;
subtest "finish installation checks after failed $name" => sub {
ok(defined($failure), 'the command was reached in the successful run') or return;
my ($failed_status, $failed_commands) = run_driver($failure);
is($failed_status, 256, 'the driver reports installation failure');
my ($index) = grep { defined($apt[2]) && $commands->[$_] eq $apt[2] } 0 .. $#$commands;
ok(defined($index), 'the probe installation ends the checks') or return;
is_deeply($failed_commands, [@$commands[0 .. $index]],
'installation completes its checks but does not start unit tests');
};
}
subtest 'a syntax error does not suppress later file checks' => sub {
ok(@checked, 'the successful run checked Perl files') or return;
my ($failed_status, $failed_commands) = run_driver($checked[0]);
is($failed_status, 256, 'the driver reports syntax failure');
is_deeply([grep { /\bperl .* -c / } @$failed_commands], \@checked,
'all eligible files are checked after the first error');
ok(!grep(/\bapt-get .*install -y xcat-test\b/, @$failed_commands),
'regression installation does not run after a syntax error');
};
subtest 'a failed regression case fails the driver' => sub {
my ($index) = grep { $commands->[$_] =~ /xcattest -f .* -t example_case/ } 0 .. $#$commands;
ok(defined($index), 'the regression case was reached in the successful run') or return;
my ($failed_status, $failed_commands, $failed_output) = run_driver(undef, 1);
is($failed_status, 256, 'the driver reports regression failure');
is_deeply($failed_commands, [@$commands[0 .. $index]],
'the regression case runs after its prerequisites and stops later phases on failure');
like($failed_output, qr/FailedCases: example_case\./, 'the failing case is reported');
};
subtest 'installation requires the built repository' => sub {
rename("$repository/mklocalrepo.sh", "$repository/mklocalrepo.saved") or die "hide repository fixture: $!";
my ($failed_status, $failed_commands, $failed_output) = run_driver();
rename("$repository/mklocalrepo.saved", "$repository/mklocalrepo.sh") or die "restore repository fixture: $!";
is($failed_status, 256, 'the driver reports a missing repository');
ok(!grep(/\bapt-get\b/, @$failed_commands), 'apt does not run without the repository');
like($failed_output, qr/the build produced no apt repository/, 'the missing repository is reported');
};
my $workflow = YAML::PP->new->load_file(repo_path('.github/workflows/xcat_test.yml'));
my @dependency_steps = grep { ($_->{name} || '') eq 'Install dependencies' }
@{ $workflow->{jobs}{xcat_pr_test}{steps} };
is(scalar @dependency_steps, 1, 'the CI job has one dependency installation step');
my @dependency_command = shellwords($dependency_steps[0]{run} || '');
is_deeply([@dependency_command[0 .. 5]],
[qw(sudo env DEBIAN_FRONTEND=noninteractive apt-get install -y)],
'the parsed workflow dependency command is noninteractive');
done_testing();