669 lines
19 KiB
Perl
Executable File
669 lines
19 KiB
Perl
Executable File
=head1 expectedFail.pm
|
|
|
|
Documentation for expectedFail.pm
|
|
|
|
=head1 Overview
|
|
|
|
The module expectedFail.pm supplies the capability of checking if a failed test is expected to fail.
|
|
It is called directly from either test_driver.sh (for batch and interactive tests) or build-namelist_test.pl.
|
|
Future plans involve integrating this module into cesm tests.
|
|
|
|
=head1 Use Case
|
|
|
|
This is a new feature being added to the existing CLM test infrastructure. The use case would roughly be
|
|
along the lines of:
|
|
|
|
1) Run the test suite (CLM batch,interactive or namelist)
|
|
2) Search for test failures
|
|
a) Fix failed tests
|
|
b) -or- Add new xFail entries to XML file if a test is supposed to fail (eg. due to some missing resolution).
|
|
3) Check for new tests that now pass. This is for modifying the ChangeLog.
|
|
4) Update XML file by either adding new entries or removing old ones.
|
|
5) update the ChangeLog to reflect important changes in test behavior (Tests that now pass that failed before, tests that
|
|
are now xFail, etc...
|
|
|
|
=head2 Public methods
|
|
|
|
There are two public methods needed. The "new" ctor and one of the parseOutput* methods.
|
|
Everything else is private.
|
|
|
|
xFail::expectedFail->new
|
|
parseOutput
|
|
parseOutputCLM
|
|
|
|
=head2 Private methods
|
|
|
|
sub _searchExpectedFail
|
|
sub _readXml
|
|
sub _testNowPassing
|
|
sub _printOutput
|
|
sub _getTestType
|
|
sub _getMachInfo
|
|
|
|
=cut
|
|
|
|
package xFail::expectedFail;
|
|
|
|
our $VERSION = '1.00';
|
|
|
|
use Cwd;
|
|
use strict;
|
|
use Getopt::Long;
|
|
use English;
|
|
use Scalar::Util qw(looks_like_number);
|
|
|
|
my @testList={};
|
|
my $DEBUG=0;
|
|
|
|
my $pass=" PASS";
|
|
my $fail=" FAIL";
|
|
my $xfail="xFAIL";
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 CTOR
|
|
|
|
Constructor for the class. Reads in three arguments:
|
|
_callingName -> name of the script creating the new object
|
|
_compareGenerate -> compare or generate option
|
|
_totTests -> total number of tests to run
|
|
|
|
Calls _readXml which reads the file expectedClmTestFails.xml and stores it memory
|
|
for later searches.
|
|
|
|
returns: new object ($self)
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub new {
|
|
my ($class_name) = @_;
|
|
my $self = {
|
|
_className => shift,
|
|
_callingName => shift,
|
|
_compareGenerate => shift,
|
|
_totTests => shift,
|
|
_foundList => undef,
|
|
_numericalTestId => undef
|
|
};
|
|
|
|
if ($DEBUG) {
|
|
print "$self->{_callingName}\n";
|
|
print "$self->{_compareGenerate}\n";
|
|
}
|
|
|
|
bless ($self, $class_name);
|
|
|
|
$self->{_numericalTestId}=0;
|
|
$self->{_created} = 1;
|
|
|
|
$self->_readXml();
|
|
|
|
return $self;
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 parseOutput
|
|
|
|
parseOutput parsese the output from the build-namelist_test.pl script. It is similar
|
|
to, but not interchangable with parseOutputCLM.
|
|
|
|
The only argument is that of the reference variable that contains the information dumped
|
|
by Test::More.
|
|
|
|
returns: nothing
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub parseOutput
|
|
{
|
|
|
|
|
|
my $report;
|
|
my $testId;
|
|
my @testName={};
|
|
my $testReason;
|
|
|
|
my ($self, $output) = @_ ;
|
|
|
|
#_#===========================================
|
|
#_# keep this in for logging
|
|
#_#===========================================
|
|
print ("captured output is :: \n $output \n");
|
|
|
|
#_# split the output from Test::More output on newline
|
|
my @refList = split('\n', $output);
|
|
|
|
#_# process any buffered output which happens when a subroutine from build-namelist_test.pl
|
|
#_# itself calls some testing routines
|
|
foreach my $refSplit (@refList) {
|
|
|
|
#_# always look at the last element of refSplit since that will have the info. from the
|
|
#_# last test run
|
|
|
|
my @outArr=split(/ /,$refSplit);
|
|
|
|
if ($DEBUG) {
|
|
print ("\nxFail::expectedFail::parseOutput @outArr[0] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[1] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[2] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[3] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[4] \n");
|
|
}
|
|
|
|
my $size = @outArr-1;
|
|
|
|
#_# first case, we have a passed (ok) test
|
|
if (@outArr[0] eq "ok") {
|
|
$self->{_numericalTestId}++;
|
|
|
|
$report=$pass;
|
|
$testId=@outArr[1];
|
|
@testName=@outArr[3..$size];
|
|
$testReason="";
|
|
|
|
my ($retVal,$xFailText)=$self->_searchExpectedFail($testId);
|
|
|
|
my $testReason=$self->_testNowPassing($testId,$retVal,$xFailText);
|
|
|
|
if($DEBUG){
|
|
print("$testReason \n");
|
|
}
|
|
|
|
$self->_printOutput($report,$testId,$testReason,@testName);
|
|
|
|
|
|
#_# deal with the case of a failed (not ok) test
|
|
} elsif (@outArr[0] eq "not") {
|
|
$self->{_numericalTestId}++;
|
|
|
|
$testId=@outArr[2];
|
|
my ($retVal,$xFailText)=$self->_searchExpectedFail($testId);
|
|
|
|
if ($DEBUG) {
|
|
print ("xFail::expectedFail::parseOutput Id $retVal,$xFailText \n");
|
|
}
|
|
|
|
@testName=@outArr[4..$size];
|
|
|
|
if ($retVal eq "TRUE"){
|
|
#_# found an expected FAIL (xFAIL)
|
|
$report=$xfail;
|
|
$testReason= "<Note: $xFailText>";
|
|
} else {
|
|
#_# print a regular FAIL
|
|
$report=$fail;
|
|
$testReason="";
|
|
}
|
|
|
|
$self->_printOutput($report,$testId,$testReason,@testName);
|
|
|
|
} else {
|
|
#_# skipping line. Trying to parse error code from Test::More
|
|
}
|
|
|
|
}
|
|
|
|
#_# this resets the reference that points to $output (\$captOut) on the caller side
|
|
@_[1]="";
|
|
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 parseOutputCLM
|
|
|
|
parseOutputCLM parsese the output from the test_driver.sh script. It is similar
|
|
to, but not interchangable with parseOutput.
|
|
|
|
parseOutputCLM takes one arguments:
|
|
$statFoo-> the name of the td.<pid>.status file
|
|
|
|
returns: nothing
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub parseOutputCLM
|
|
{
|
|
|
|
my $report;
|
|
my $testId;
|
|
my @testName={};
|
|
my $testReason;
|
|
|
|
my ($self, $statFoo) = @_ ;
|
|
|
|
open(FOO, "< $statFoo"); # open for input
|
|
open(FOO_OUT, "> $statFoo.xFail"); # open for input
|
|
|
|
my(@reportLines);
|
|
|
|
while (<FOO>) {
|
|
|
|
my($line) = $_;
|
|
|
|
my @outArr=split(/ /,$line);
|
|
if (looks_like_number(@outArr[0])) {
|
|
|
|
$self->{_numericalTestId}++;
|
|
|
|
my $num=sprintf("%03d", $self->{_numericalTestId});
|
|
my $totNum=sprintf("%03d", $self->{_totTests});
|
|
|
|
#_# last element has the pass/fail info.
|
|
chomp(@outArr[-1]);
|
|
my $repPass=substr(@outArr[-1], -4, 4);
|
|
|
|
if ($DEBUG) {
|
|
print ("xFail::expectedFail::parseOutput @outArr[0] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[1] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[2] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[3] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[4] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[5] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[6] \n");
|
|
print ("xFail::expectedFail::parseOutput @outArr[-1] \n");
|
|
print ("xFail::expectedFail::parseOutput $repPass \n");
|
|
}
|
|
|
|
my $size = @outArr-1;
|
|
if ($DEBUG) {
|
|
print ("size of line $size \n");
|
|
}
|
|
my $endOfDesc=$size-1;
|
|
|
|
if ($repPass eq "PASS") {
|
|
$report=$pass;
|
|
$testId=@outArr[1];
|
|
@testName=@outArr[2..$endOfDesc];
|
|
|
|
my ($retVal,$xFailText)=$self->_searchExpectedFail($testId);
|
|
|
|
my $testReason=$self->_testNowPassing($testId,$retVal,$xFailText);
|
|
|
|
#_# print out the test results
|
|
print FOO_OUT ("$num/$totNum <$report> <Test Id: $testId> <Desc: @testName> $testReason \n");
|
|
|
|
} else {
|
|
$testId=@outArr[1];
|
|
my ($retVal,$xFailText)=$self->_searchExpectedFail($testId);
|
|
|
|
if ($DEBUG) {
|
|
print ("xFail::expectedFail::parseOutput Id $retVal,$xFailText \n");
|
|
}
|
|
|
|
@testName=@outArr[2..$endOfDesc];
|
|
|
|
if ($retVal eq "TRUE"){
|
|
#_# found an expected FAIL (xFAIL)
|
|
$report=$xfail;
|
|
$testReason= "<Note: $xFailText>";
|
|
} else {
|
|
#_# print a regular FAIL
|
|
$report=$fail;
|
|
$testReason="";
|
|
}
|
|
|
|
#_# print out the test results
|
|
print FOO_OUT ("$num/$totNum <$report> <Test Id: $testId> <Desc: @testName> $testReason \n");
|
|
|
|
}
|
|
|
|
} else {
|
|
print FOO_OUT $line;
|
|
}
|
|
}
|
|
close(FOO);
|
|
close(FOO_OUT);
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _searchExpectedFail
|
|
|
|
searches the list of expected fails for a match with testId.
|
|
|
|
_searchExpectedFail takes one arguments:
|
|
$testId-> the test id (numerical or string) that we want to search for
|
|
|
|
returns: $retVal (TRUE or FALSE) if id was found
|
|
$text text from XML file
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _searchExpectedFail
|
|
{
|
|
my ( $self,$testId) = @_;
|
|
|
|
#search through list for test ID
|
|
my $retVal="FALSE";
|
|
|
|
if ($DEBUG) {
|
|
print ("here 2 Id $self->{_foundList} \n");
|
|
}
|
|
if ($self->{_foundList} eq "FALSE"){
|
|
if ($DEBUG) {
|
|
print ("returning early Id \n");
|
|
}
|
|
return $retVal;
|
|
}
|
|
|
|
my $failType;
|
|
my $text;
|
|
foreach my $tL (@testList) {
|
|
my %tAtts = $tL->get_attributes();
|
|
my $tid=$tAtts{'testId'};
|
|
if ($DEBUG) {
|
|
print ("_seachExpectedFail Id $tid $testId \n");
|
|
}
|
|
if ($tid eq $testId) {
|
|
if ($DEBUG) {
|
|
print ("here Id \n");
|
|
}
|
|
#~# found the test we're looking for
|
|
$text=$tL->get_text();
|
|
$failType=$tAtts{'failType'};
|
|
if ($failType eq "xFail"){
|
|
$retVal="TRUE";
|
|
}
|
|
}
|
|
}
|
|
return ($retVal,$text);
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _readXml
|
|
|
|
reads the xml file for a particular machine, compiler, test type and (compare
|
|
| generate) setup and saves it in memory for searching by _searchExpectedFail.
|
|
|
|
_readXml takes no arguments
|
|
|
|
returns: nothing
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _readXml
|
|
{
|
|
my ( $self ) = @_;
|
|
|
|
#Figure out where configure directory is and where can use the XML/Lite module from
|
|
my $ProgName;
|
|
($ProgName = $PROGRAM_NAME) =~ s!(.*)/!!; # name of program
|
|
my $ProgDir = $1; # name of directory where program lives
|
|
|
|
my $cwd = getcwd(); # current working directory
|
|
my $cfgdir;
|
|
|
|
if ($ProgDir) { $cfgdir = $ProgDir; }
|
|
else { $cfgdir = $cwd; }
|
|
|
|
#-----------------------------------------------------------------------------------------------
|
|
# Add $cfgdir to the list of paths that Perl searches for modules
|
|
my @dirs = ( $cfgdir, "$cfgdir/perl5lib",
|
|
"$cfgdir/../../cime/utils/perl5lib",
|
|
"$cfgdir/../../../cime/utils/perl5lib"
|
|
);
|
|
unshift @INC, @dirs;
|
|
my $result = eval "require XML::Lite";
|
|
if ( ! defined($result) ) {
|
|
die <<"EOF";
|
|
** Cannot find perl module \"XML/Lite.pm\" from directories: @dirs **
|
|
EOF
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------------------------
|
|
|
|
my ($machine,$compiler)=_getMachInfo();
|
|
|
|
my $testType=$self->_getTestType($self->{_callingName});
|
|
|
|
|
|
my $xmlFile=undef;
|
|
if ($testType eq "clmInteractive" || $testType eq "clmBatch") {
|
|
$xmlFile = "$cfgdir/expectedClmTestFails.xml";
|
|
} elsif ($testType eq "namelistTest") {
|
|
$xmlFile = "xFail/expectedClmTestFails.xml";
|
|
} else {
|
|
$xmlFile = "xFail/expectedClmTestFails.xml";
|
|
}
|
|
my $xml = XML::Lite->new($xmlFile);
|
|
|
|
my $root = $xml->root_element();
|
|
|
|
if ($DEBUG) {
|
|
print "_readXml $self->{_callingName}\n";
|
|
print "_readXml $self->{_compareGenerate}\n";
|
|
print "_readXml $xmlFile \n";
|
|
print ("_readXml Debug testType $testType \n");
|
|
print ("_readXml Debug machine $machine \n");
|
|
print ("_readXml Debug compiler $compiler \n");
|
|
}
|
|
|
|
# Check for valid root node
|
|
my $name = $root->get_name();
|
|
$name eq "expectedFails" or die
|
|
"readExpectedFail.pm::_readXml :: $xmlFile is not a file that contains expected test failures\n";
|
|
|
|
my @e = $xml->elements_by_name($testType);
|
|
|
|
$self->{_foundList}="FALSE";
|
|
|
|
### populate list of tests for a specfic test type, machine and compiler
|
|
### there's got to be a better way to write this
|
|
while ( my $e = shift @e ) {
|
|
my @mChildren = $e->get_children();
|
|
foreach my $mChild (@mChildren) {
|
|
my $mName=$mChild->get_name();
|
|
if ($mName eq $machine){
|
|
my @cChildren = $mChild->get_children();
|
|
foreach my $cChild (@cChildren) {
|
|
my $cName=$cChild->get_name();
|
|
if ($cName eq $compiler) {
|
|
my @cgChildren=$cChild->get_children();
|
|
foreach my $cgChild (@cgChildren) {
|
|
my $cgName=$cgChild->get_name();
|
|
if($cgName eq $self->{_compareGenerate}){
|
|
@testList=$cgChild->get_children();
|
|
$self->{_foundList}="TRUE";
|
|
last;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($DEBUG) {
|
|
print ("here 1 $self->{_foundList} \n");
|
|
}
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _testNowPassing
|
|
|
|
reads the xml file for a particular machine, compiler, test type and (compare
|
|
| generate) setup and saves it in memory for searching by _searchExpectedFail.
|
|
|
|
_testNowPassing takes three arguments:
|
|
$id - test id to print out
|
|
$retVal - TRUE or FALSE. Was the id found in the expected fail list
|
|
$xmlText - text from the XML notes section of the file. (Currently not used,
|
|
may be used in future).
|
|
|
|
returns: a text string
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _testNowPassing
|
|
{
|
|
|
|
my ($self, $id, $retVal, $xmlText) = @_ ;
|
|
my $text=undef;
|
|
|
|
if ($retVal eq "TRUE") {
|
|
#_# found a test that passes now, but is listed as an xFail
|
|
$text = "<NOTE: $id is a new PASS; was xFAIL>\n";
|
|
|
|
} else {
|
|
#_# this test passes and was not previously listed as an xFail
|
|
#_# noOp
|
|
}
|
|
|
|
return $text;
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _printOutput
|
|
|
|
method that prints output for status files.
|
|
|
|
_printOutput takes four arguments:
|
|
$report - PASS,FAIL,xFAIL
|
|
$testId - test id to print out
|
|
$testReason - for xFAIL and new PASSES, additional reporting
|
|
@testName - test description from original test
|
|
|
|
returns: a text string
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _printOutput
|
|
{
|
|
|
|
my ($self, $report, $testId, $testReason, @testName) = @_ ;
|
|
|
|
#_# print out the test results
|
|
my $num=sprintf("%03d", $self->{_numericalTestId});
|
|
my $totNum=sprintf("%03d", $self->{_totTests});
|
|
print ("$num/$totNum <$report> <Test Id: $testId> <Desc: @testName> $testReason \n");
|
|
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _getTestType
|
|
|
|
method that takes the name of the calling script and returns the type of
|
|
test. Used for searching the expected fail list.
|
|
|
|
_getTestType takes four arguments:
|
|
$name - name of calling script
|
|
|
|
returns: $type, the type of test
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _getTestType
|
|
{
|
|
|
|
my ($self, $name) = @_ ;
|
|
|
|
if($DEBUG){
|
|
print ("_getTestType $name");
|
|
}
|
|
|
|
my %testTypes = (
|
|
"build-namelist_test.pl" => "namelistTest",
|
|
"test_driver.sh-i" => "clmInteractive",
|
|
"test_driver.sh" => "clmBatch",
|
|
"clm-cesm.sh" => "cesm"
|
|
);
|
|
|
|
my $type = $testTypes {lc $name} || "unknown";
|
|
return $type;
|
|
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
|
|
=head1 _getMachInfo
|
|
|
|
method that figures out on what platform this is running and returns a 2 digit
|
|
machine identifier and the compiler. This will eventually contain multiple
|
|
compiler for various machines.
|
|
|
|
_getMachInfo takes no arguments
|
|
|
|
returns: $mach - the machine I'm running on
|
|
$comp - the compiler being used
|
|
|
|
=cut
|
|
|
|
##############################################################################
|
|
#
|
|
##############################################################################
|
|
sub _getMachInfo
|
|
{
|
|
|
|
my $name=`uname -n`;
|
|
$name = substr($name, 0, 2);
|
|
|
|
my %machNames = (
|
|
"ys" => "yellowstone",
|
|
"fr" => "frankfurt"
|
|
);
|
|
|
|
my %compNames = (
|
|
"ys" => "INTEL",
|
|
"fr" => "INTEL"
|
|
);
|
|
|
|
my $mach = $machNames {lc $name} || "unknown";
|
|
my $comp = $compNames {lc $name} || "unknown";
|
|
|
|
return ($mach,$comp);
|
|
|
|
}
|
|
|
|
# A Perl module must end with a true value or else it is considered not to
|
|
# have loaded. By convention this value is usually 1 though it can be
|
|
# any true value. A module can end with false to indicate failure but
|
|
# this is rarely used and it would instead die() (exit with an error).
|
|
1;
|