mirror of
https://github.com/xcat2/xcat-core.git
synced 2026-09-04 12:07:56 +00:00
test(go-xcat): cover os-release parsing
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use File::Path qw(make_path);
|
||||
use File::Spec;
|
||||
use File::Temp qw(tempdir);
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use POSIX qw(_exit);
|
||||
use Test::More;
|
||||
|
||||
use XCAT::Test::File qw(repo_path slurp_repo_file);
|
||||
|
||||
my $go_xcat_relative = 'xCAT-server/share/xcat/tools/go-xcat';
|
||||
my ( $source_go_xcat, $go_xcat_source );
|
||||
if ( defined $ENV{XCAT_TEST_GO_XCAT} ) {
|
||||
$source_go_xcat = $ENV{XCAT_TEST_GO_XCAT};
|
||||
die 'XCAT_TEST_GO_XCAT must name a readable regular file'
|
||||
unless length($source_go_xcat)
|
||||
&& -f $source_go_xcat
|
||||
&& -r _;
|
||||
$go_xcat_source = read_file($source_go_xcat);
|
||||
}
|
||||
else {
|
||||
$source_go_xcat = repo_path($go_xcat_relative);
|
||||
plan skip_all => "$source_go_xcat is required"
|
||||
unless -f $source_go_xcat && -r _;
|
||||
$go_xcat_source = slurp_repo_file($go_xcat_relative);
|
||||
}
|
||||
|
||||
is( system( 'bash', '-n', $source_go_xcat ), 0,
|
||||
'go-xcat has valid Bash syntax' );
|
||||
|
||||
my $gawk = find_command('gawk');
|
||||
|
||||
my @public_scenarios = (
|
||||
{
|
||||
name => 'quoted',
|
||||
os_release => qq{NAME="Ubuntu"\nID="ubuntu"\nVERSION_ID="24.04"\n},
|
||||
redhat => "Red Hat Enterprise Linux release 9.6 (Plow)\n",
|
||||
suse => "VERSION = 15.6\n",
|
||||
expected => "distro=ubuntu\nversion=24.04\n",
|
||||
},
|
||||
{
|
||||
name => 'unquoted',
|
||||
os_release => "ID_LIKE=rhel\nID=rocky\nVERSION_ID=9.6\n",
|
||||
expected => "distro=rocky\nversion=9.6\n",
|
||||
},
|
||||
{
|
||||
name => 'single-quoted',
|
||||
os_release => "ID='debian'\nVERSION_ID='13'\n",
|
||||
expected => "distro='debian'\nversion='13'\n",
|
||||
},
|
||||
{
|
||||
name => 'redhat-fallback',
|
||||
redhat => "Red Hat Enterprise Linux release 9.6 (Plow)\n",
|
||||
requires_gawk => 1,
|
||||
expected => "distro=rhel\nversion=9.6\n",
|
||||
},
|
||||
{
|
||||
name => 'suse-release-fallback',
|
||||
os_release => "ID=\nVERSION_ID=\n",
|
||||
suse => "VERSION = 15.6\n",
|
||||
expected => "distro=sles\nversion=15.6\n",
|
||||
},
|
||||
{
|
||||
name => 'suse-brand-fallback',
|
||||
os_release => '',
|
||||
suse_brand => "VERSION = 12.5\n",
|
||||
expected => "distro=sles\nversion=12.5\n",
|
||||
},
|
||||
{
|
||||
name => 'missing-release-data',
|
||||
expected => "distro=\nversion=\n",
|
||||
},
|
||||
{
|
||||
name => 'independent-fields',
|
||||
os_release => "VERSION_ID=10.1\n",
|
||||
redhat => "Red Hat Enterprise Linux release 8.10 (Ootpa)\n",
|
||||
expected => "distro=rhel\nversion=10.1\n",
|
||||
},
|
||||
{
|
||||
name => 'redhat-distro-precedes-suse',
|
||||
os_release => "VERSION_ID=10.1\n",
|
||||
redhat => "Red Hat Enterprise Linux release 9.6 (Plow)\n",
|
||||
suse => "VERSION = 15.6\n",
|
||||
suse_brand => "VERSION = 12.5\n",
|
||||
expected => "distro=rhel\nversion=10.1\n",
|
||||
},
|
||||
{
|
||||
name => 'redhat-version-precedes-suse',
|
||||
redhat => "Red Hat Enterprise Linux release 9.6 (Plow)\n",
|
||||
suse => "VERSION = 15.6\n",
|
||||
suse_brand => "VERSION = 12.5\n",
|
||||
requires_gawk => 1,
|
||||
expected => "distro=rhel\nversion=9.6\n",
|
||||
},
|
||||
{
|
||||
name => 'suse-release-precedes-brand',
|
||||
suse => "VERSION = 15.6\n",
|
||||
suse_brand => "VERSION = 12.5\n",
|
||||
expected => "distro=sles\nversion=15.6\n",
|
||||
},
|
||||
);
|
||||
|
||||
for my $scenario (@public_scenarios) {
|
||||
SKIP: {
|
||||
skip "$scenario->{name} requires gawk", 3
|
||||
if $scenario->{requires_gawk} && !defined $gawk;
|
||||
my $result = run_case(
|
||||
%{$scenario},
|
||||
command => ['public'],
|
||||
gawk => $scenario->{requires_gawk} ? $gawk : undef,
|
||||
);
|
||||
is( $result->{status}, 0, "$scenario->{name} detection completes" );
|
||||
is( $result->{stdout}, $scenario->{expected},
|
||||
"$scenario->{name} detection preserves distro and version" );
|
||||
is( $result->{stderr}, '',
|
||||
"$scenario->{name} detection stays quiet" );
|
||||
}
|
||||
}
|
||||
|
||||
my $helper_probe = run_case(
|
||||
name => 'helper-probe',
|
||||
command => ['helper-present'],
|
||||
);
|
||||
die 'os_release_value is required in the repository go-xcat'
|
||||
if $helper_probe->{status} != 0 && !defined $ENV{XCAT_TEST_GO_XCAT};
|
||||
|
||||
SKIP: {
|
||||
skip 'the legacy characterization source has no shared helper', 12
|
||||
if $helper_probe->{status} != 0;
|
||||
|
||||
my $quoted_id = run_case(
|
||||
name => 'helper-quoted-id',
|
||||
helper_contents => "ID_LIKE=debian\nID=\"ubuntu\"\nVERSION_ID=\"24.04\"\n",
|
||||
command => [ 'value', 'ID' ],
|
||||
);
|
||||
is( $quoted_id->{status}, 0, 'the helper reads a quoted ID' );
|
||||
is( $quoted_id->{stdout}, "ubuntu\n",
|
||||
'the helper removes double quotes from an ID' );
|
||||
is( $quoted_id->{stderr}, '', 'quoted ID parsing stays quiet' );
|
||||
|
||||
my $unquoted_version = run_case(
|
||||
name => 'helper-unquoted-version',
|
||||
helper_contents => "ID=rocky\nVERSION_ID=9.6\n",
|
||||
command => [ 'value', 'VERSION_ID' ],
|
||||
);
|
||||
is( $unquoted_version->{status}, 0,
|
||||
'the helper reads an unquoted version' );
|
||||
is( $unquoted_version->{stdout}, "9.6\n",
|
||||
'the helper preserves an unquoted version' );
|
||||
is( $unquoted_version->{stderr}, '',
|
||||
'unquoted version parsing stays quiet' );
|
||||
|
||||
my $missing_key = run_case(
|
||||
name => 'helper-missing-key',
|
||||
helper_contents => "ID=ubuntu\n",
|
||||
command => [ 'value', 'VERSION_ID' ],
|
||||
);
|
||||
is( $missing_key->{status}, 0, 'a missing key is not an error' );
|
||||
is( $missing_key->{stdout}, '', 'a missing key produces no value' );
|
||||
is( $missing_key->{stderr}, '', 'a missing key stays quiet' );
|
||||
|
||||
my $missing_file = run_case(
|
||||
name => 'helper-missing-file',
|
||||
command => [ 'value', 'ID' ],
|
||||
);
|
||||
isnt( $missing_file->{status}, 0, 'a missing file preserves the AWK failure' );
|
||||
is( $missing_file->{stdout}, '', 'a missing file produces no value' );
|
||||
is( $missing_file->{stderr}, '', 'a missing file suppresses AWK diagnostics' );
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
sub run_case
|
||||
{
|
||||
my (%option) = @_;
|
||||
my $root = tempdir( CLEANUP => 1 );
|
||||
my $fixtures = File::Spec->catdir( $root, 'fixtures' );
|
||||
my $bin = File::Spec->catdir( $root, 'bin' );
|
||||
make_path( $fixtures, $bin );
|
||||
|
||||
my $os_release = File::Spec->catfile( $fixtures, 'os-release' );
|
||||
my $redhat_release = File::Spec->catfile( $fixtures, 'redhat-release' );
|
||||
my $suse_release = File::Spec->catfile( $fixtures, 'SuSE-release' );
|
||||
my $suse_brand = File::Spec->catfile( $fixtures, 'SUSE-brand' );
|
||||
my $helper_dir = File::Spec->catdir( $root, 'helper fixture' );
|
||||
my $helper_file = File::Spec->catfile( $helper_dir, 'os-release' );
|
||||
my $sandboxed_source = File::Spec->catfile( $root, 'go-xcat' );
|
||||
my $driver = File::Spec->catfile( $root, 'driver.sh' );
|
||||
my $stdout_file = File::Spec->catfile( $root, 'stdout' );
|
||||
my $stderr_file = File::Spec->catfile( $root, 'stderr' );
|
||||
|
||||
my $body = $go_xcat_source;
|
||||
replace_required( \$body, '/etc/os-release', $os_release );
|
||||
replace_required( \$body, '/etc/redhat-release', $redhat_release );
|
||||
replace_required( \$body, '/etc/SuSE-release', $suse_release );
|
||||
replace_required( \$body, '/etc/SUSE-brand', $suse_brand );
|
||||
write_executable( $sandboxed_source, $body );
|
||||
write_executable( $driver, <<'DRIVER' );
|
||||
#!/bin/bash
|
||||
set -uo pipefail
|
||||
|
||||
function_body=$(
|
||||
for function_name in \
|
||||
os_release_value \
|
||||
check_linux_distro \
|
||||
check_linux_version
|
||||
do
|
||||
awk -v name="$function_name" '
|
||||
$0 == "function " name "()" { copy = 1 }
|
||||
copy { print }
|
||||
copy && /^}$/ { exit }
|
||||
' "$GO_XCAT_SOURCE"
|
||||
done
|
||||
)
|
||||
eval "$function_body"
|
||||
if [ "$?" -ne 0 ]; then
|
||||
printf 'unable to evaluate go-xcat functions\n' >&2
|
||||
exit 69
|
||||
fi
|
||||
for function_name in check_linux_distro check_linux_version; do
|
||||
if ! declare -F "$function_name" >/dev/null; then
|
||||
printf 'missing %s\n' "$function_name" >&2
|
||||
exit 70
|
||||
fi
|
||||
done
|
||||
|
||||
case "$1" in
|
||||
public)
|
||||
printf 'distro=%s\n' "$(check_linux_distro)"
|
||||
printf 'version=%s\n' "$(check_linux_version)"
|
||||
;;
|
||||
helper-present)
|
||||
declare -F os_release_value >/dev/null
|
||||
;;
|
||||
value)
|
||||
if ! declare -F os_release_value >/dev/null; then
|
||||
printf 'missing os_release_value\n' >&2
|
||||
exit 70
|
||||
fi
|
||||
os_release_value "$2" "$HELPER_OS_RELEASE"
|
||||
;;
|
||||
*)
|
||||
exit 64
|
||||
;;
|
||||
esac
|
||||
DRIVER
|
||||
|
||||
write_file( $os_release, $option{os_release} )
|
||||
if defined $option{os_release};
|
||||
write_file( $redhat_release, $option{redhat} )
|
||||
if defined $option{redhat};
|
||||
write_file( $suse_release, $option{suse} )
|
||||
if defined $option{suse};
|
||||
write_file( $suse_brand, $option{suse_brand} )
|
||||
if defined $option{suse_brand};
|
||||
if ( defined $option{helper_contents} ) {
|
||||
make_path($helper_dir);
|
||||
write_file( $helper_file, $option{helper_contents} );
|
||||
}
|
||||
if ( defined $option{gawk} ) {
|
||||
symlink( $option{gawk}, File::Spec->catfile( $bin, 'awk' ) )
|
||||
or die "Unable to select gawk: $!";
|
||||
}
|
||||
|
||||
local %ENV = (
|
||||
%ENV,
|
||||
GO_XCAT_SOURCE => $sandboxed_source,
|
||||
HELPER_OS_RELEASE => $helper_file,
|
||||
PATH => "$bin:$ENV{PATH}",
|
||||
);
|
||||
|
||||
my $pid = fork();
|
||||
die "Unable to fork go-xcat fixture: $!" unless defined $pid;
|
||||
if ( $pid == 0 ) {
|
||||
open( STDIN, '<', '/dev/null' ) or _exit(126);
|
||||
open( STDOUT, '>:raw', $stdout_file ) or _exit(126);
|
||||
open( STDERR, '>:raw', $stderr_file ) or _exit(126);
|
||||
exec 'bash', $driver, @{ $option{command} } or _exit(127);
|
||||
}
|
||||
my $reaped = waitpid( $pid, 0 );
|
||||
my $raw_status = $?;
|
||||
my $status = $reaped == $pid && !( $raw_status & 127 )
|
||||
? $raw_status >> 8
|
||||
: 255;
|
||||
|
||||
return {
|
||||
status => $status,
|
||||
stdout => read_optional($stdout_file),
|
||||
stderr => read_optional($stderr_file),
|
||||
};
|
||||
}
|
||||
|
||||
sub replace_required
|
||||
{
|
||||
my ( $body_ref, $from, $to ) = @_;
|
||||
my $count = $$body_ref =~ s/\Q$from\E/$to/g;
|
||||
die "Unable to sandbox $from" unless $count;
|
||||
die "Sandbox rewrite left $from in go-xcat"
|
||||
if index( $$body_ref, $from ) >= 0;
|
||||
}
|
||||
|
||||
sub write_executable
|
||||
{
|
||||
my ( $file, $contents ) = @_;
|
||||
write_file( $file, $contents );
|
||||
chmod 0755, $file or die "Unable to make $file executable: $!";
|
||||
}
|
||||
|
||||
sub write_file
|
||||
{
|
||||
my ( $file, $contents ) = @_;
|
||||
open( my $fh, '>:raw', $file ) or die "Unable to write $file: $!";
|
||||
print {$fh} $contents;
|
||||
close($fh) or die "Unable to close $file: $!";
|
||||
}
|
||||
|
||||
sub read_file
|
||||
{
|
||||
my ($file) = @_;
|
||||
open( my $fh, '<:raw', $file ) or die "Unable to read $file: $!";
|
||||
my $contents = do { local $/; <$fh> };
|
||||
close($fh) or die "Unable to close $file: $!";
|
||||
return $contents;
|
||||
}
|
||||
|
||||
sub read_optional
|
||||
{
|
||||
my ($file) = @_;
|
||||
return '' unless -f $file;
|
||||
return read_file($file) // '';
|
||||
}
|
||||
|
||||
sub find_command
|
||||
{
|
||||
my ($command) = @_;
|
||||
for my $directory ( File::Spec->path() ) {
|
||||
my $candidate = File::Spec->catfile( $directory, $command );
|
||||
return $candidate if -f $candidate && -x _;
|
||||
}
|
||||
return;
|
||||
}
|
||||
Reference in New Issue
Block a user