mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-10-21 07:08:12 +00:00
c97b8d412b
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.
181 lines
8.3 KiB
Perl
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();
|