From ea1cb82a6d79e93a9b4ed6e23158160ffc4eeb91 Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:08:41 -0300 Subject: [PATCH] fix(xcat-core): five check lines the xcattest harness never evaluates Three case files ship a check line the harness cannot use. pscp/cases0 asks for "$$CN: done" with no operator, and load_case drops a check whose content does not start with a word character, so the two lines go without a message and the case asserts less than it reads. rscan/cases0 writes a command as a check, and ngpfb/cases0 compares rc against a pattern, which no operator accepts, so both cases report "Unrecognized testcase syntax" and fail on every run. Each line is repaired to what the case around it says it means. pscp prints ": done" for each node it copied, in xCAT-client/bin/pscp, so the two lines become "output=~$$CN: done". rscan runs its check against the definitions that "rscan -z -w" wrote, so "check:lsdef -l $$CN" becomes a cmd and keeps the two checks that follow it. rmhwconn is asked for output without "state=LINE UP", which is what the lshwconn checks in the same case assert the other way round. xCAT-test/unit/autotest_check_lines_are_understood.t reads every check line under xCAT-test/autotest/testcase and asserts the harness reports one result for each of them, and that none uses an operator it does not know. Without this change it names all five: two lines from ngpfb, one from rscan, and pscp reporting 37 results for the 39 check lines it ships. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- xCAT-test/autotest/testcase/ngpfb/cases0 | 4 +- xCAT-test/autotest/testcase/pscp/cases0 | 4 +- xCAT-test/autotest/testcase/rscan/cases0 | 2 +- .../autotest_check_lines_are_understood.t | 105 ++++++++++++++++++ 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 xCAT-test/unit/autotest_check_lines_are_understood.t diff --git a/xCAT-test/autotest/testcase/ngpfb/cases0 b/xCAT-test/autotest/testcase/ngpfb/cases0 index e3604488d..4d3c1b8c3 100644 --- a/xCAT-test/autotest/testcase/ngpfb/cases0 +++ b/xCAT-test/autotest/testcase/ngpfb/cases0 @@ -78,7 +78,7 @@ description:for hwconn label:others,hctrl_fsp cmd:rmhwconn $$CN check:rc==0 -check:rc!~(state=LINE UP) +check:output!~(state=LINE UP) cmd:mkhwconn $$CN -t check:rc==0 cmd:sleep 40 @@ -87,7 +87,7 @@ check:rc==0 check:output=~(LINE UP) cmd:rmhwconn blade check:rc==0 -check:rc!~(state=LINE UP) +check:output!~(state=LINE UP) cmd:mkhwconn blade -t check:rc==0 cmd:sleep 50 diff --git a/xCAT-test/autotest/testcase/pscp/cases0 b/xCAT-test/autotest/testcase/pscp/cases0 index e7050691e..ff061794c 100644 --- a/xCAT-test/autotest/testcase/pscp/cases0 +++ b/xCAT-test/autotest/testcase/pscp/cases0 @@ -19,7 +19,7 @@ cmd:echo "test" > /tmp/pscp.tmp check:rc==0 cmd:pscp /tmp/pscp.tmp $$CN:/tmp/ check:rc==0 -check:$$CN: done +check:output=~$$CN: done cmd:xdsh $$CN "ls -l /tmp |grep pscp.tmp" check:rc==0 check:output=~pscp.tmp @@ -41,7 +41,7 @@ cmd:echo "test" > /tmp/pscp/pscp.tmp check:rc==0 cmd:pscp -r /tmp/pscp $$CN:/tmp/ check:rc==0 -check:$$CN: done +check:output=~$$CN: done cmd:xdsh $$CN "ls -l /tmp |grep pscp" check:rc==0 check:output=~pscp diff --git a/xCAT-test/autotest/testcase/rscan/cases0 b/xCAT-test/autotest/testcase/rscan/cases0 index 98f09ae30..aaf6752e2 100644 --- a/xCAT-test/autotest/testcase/rscan/cases0 +++ b/xCAT-test/autotest/testcase/rscan/cases0 @@ -77,7 +77,7 @@ cmd:rmdef $$CN cmd:rscan __GETNODEATTR(testnode,hcp)__ -z -w check:rc==0 check:output=~parent=[\w-]+ -check:lsdef -l $$CN +cmd:lsdef -l $$CN check:rc==0 check:output=~parent=[\w-]+ cmd:rmdef all diff --git a/xCAT-test/unit/autotest_check_lines_are_understood.t b/xCAT-test/unit/autotest_check_lines_are_understood.t new file mode 100644 index 000000000..b589be314 --- /dev/null +++ b/xCAT-test/unit/autotest_check_lines_are_understood.t @@ -0,0 +1,105 @@ +#!/usr/bin/env perl +use strict; +use warnings; + +use File::Copy qw(copy); +use File::Find qw(find); +use File::Path qw(make_path); +use File::Temp qw(tempdir); +use FindBin; +use Test::More; + +my $program = "$FindBin::Bin/../xcattest"; +my $casedir = "$FindBin::Bin/../autotest/testcase"; +BAIL_OUT("xcattest is not at $program") unless -f $program; +BAIL_OUT("no test cases under $casedir") unless -d $casedir; + +# A check line xcattest does not understand costs the case the assertion it describes, and the +# case says nothing about it: an unknown operator reports "Unrecognized testcase syntax", and a +# line whose content does not start with a word character is dropped while the case is loaded. +# Read the shipped check lines and let the harness report on them. +my @files; +find({ wanted => sub { push(@files, $File::Find::name) if -f $File::Find::name }, no_chdir => 1 }, $casedir); +BAIL_OUT("no case files under $casedir") unless @files; + +my (%checks, %vars); +for my $file (sort @files) { + open(my $fh, '<', $file) or BAIL_OUT("open $file: $!"); + while (my $line = <$fh>) { + chomp($line); + next unless $line =~ /^check\s*:\s*(\S.*)$/; + my $check = $1; + + # __GETNODEATTR(...)__ and its siblings read the xCAT database, one lsdef for each + # check. The shape of the line is what this test reads, so a fixed value stands in. + $check =~ s/__\w+\([^)]*\)__/placeholder/g; + $vars{$1} = 1 while ($check =~ /\$\$(\w+)/g); + push(@{ $checks{$file} }, $check); + } + close($fh) or BAIL_OUT("close $file: $!"); +} +BAIL_OUT("no check lines under $casedir") unless keys %checks; + +# One case per shipped file, so a check that reports nothing is attributed to its own file. +my %case_of_file = map { $_ => 'syntax_' . do { my $n = $_; $n =~ s{^\Q$casedir\E/?}{}; $n =~ s/[^A-Za-z0-9_-]/_/g; $n } } keys %checks; + +my $fixture = ''; +for my $file (sort keys %checks) { + $fixture .= "start:$case_of_file{$file}\n"; + $fixture .= "cmd:true\n"; + $fixture .= "check:$_\n" for @{ $checks{$file} }; + $fixture .= "end\n"; +} + +# xcattest derives its result directory from the location of the program, so the copy under the +# scratch tree keeps every file the run writes inside that tree. +my $root = tempdir(CLEANUP => 1); +make_path("$root/bin", "$root/cases"); +copy($program, "$root/bin/xcattest") or BAIL_OUT("copy xcattest: $!"); +chmod 0755, "$root/bin/xcattest"; +open(my $fixture_fh, '>', "$root/cases/fixture") or BAIL_OUT("write the fixture case: $!"); +print $fixture_fh $fixture; +close($fixture_fh) or BAIL_OUT("close the fixture case: $!"); + +# Every variable a check line names has to resolve, or xcattest drops the whole case. +# A "local" here would be undone at the end of its own statement, before the run. +$ENV{"XCATTEST_$_"} = 'placeholder' for keys %vars; +$ENV{XCATTEST_CASEDIR} = "$root/cases"; +# Some shipped patterns warn when perl compiles them, and the warnings say nothing about the +# operator. The log file carries what this test reads, so the warnings go to the scratch tree. +open(my $stderr_save, '>&', \*STDERR) or BAIL_OUT("save STDERR: $!"); +open(STDERR, '>', "$root/stderr") or BAIL_OUT("redirect STDERR: $!"); +system($^X, "$root/bin/xcattest", '-q', '-t', join(',', sort values %case_of_file)); +open(STDERR, '>&', $stderr_save) or BAIL_OUT("restore STDERR: $!"); + +my ($logname) = glob("$root/share/xcat/tools/autotest/result/xcattest.log.*"); +BAIL_OUT("the harness wrote no log under $root") unless $logname; +open(my $log_fh, '<', $logname) or BAIL_OUT("open $logname: $!"); +my @log = <$log_fh>; +close($log_fh) or BAIL_OUT("close $logname: $!"); +chomp(@log); + +# Count what the harness reported for each case, and keep the lines it did not understand. +my (%reported, @unrecognized, $current); +for my $line (@log) { + $current = $1 if ($line =~ /^------START::(\S+)::/); + next unless defined $current; + $reported{$current}++ if ($line =~ /^CHECK:/ or $line =~ /^Unrecognized testcase syntax:/); + push(@unrecognized, "$current: $line") if ($line =~ /^Unrecognized testcase syntax:/); + $current = undef if ($line =~ /^------END::/); +} + +is(join("\n", @unrecognized), '', + 'every check line in the shipped cases uses an operator xcattest understands'); + +my @silent; +for my $file (sort keys %checks) { + my $case = $case_of_file{$file}; + my $fed = scalar @{ $checks{$file} }; + my $got = $reported{$case} || 0; + push(@silent, "$file: $fed check lines, $got reported") if ($got != $fed); +} +is(join("\n", @silent), '', + 'every check line in the shipped cases reports a result, so none is dropped while the case loads'); + +done_testing();