######################################################################## # Copyright (c) VIX TECHNOLOGY (AUST) LTD # # Module name : UtfFilter_androidStudio.pm # Module type : JATS Utility # Environment(s): jats # # Description : JATS UTF Filter - internal # Jats UTF plugin module to format the Android Studio JUNIT output # into a standard XML for JATS to import into the # Release Manager database. # # Usage : See TECHGP-00316 Post Processing Unit Tests Results # #......................................................................# package UtfFilter_androidstudio; require 5.008_002; use diagnostics; use strict; use warnings; use Getopt::Long; use Time::HiRes; use File::Find; use File::Spec; use Cwd; use JatsError; use JatsSystem; # # Globals # my $dirName; my $className; my $options; my %Type2Name = ( 'P' => '/build/test-results/release', 'D' => '/build/test-results/debug' ); #------------------------------------------------------------------------------ # Function : processUtf # # Description : perform the actions required by this module as described above # Static method within the package # # Inputs : $name - Name of the class (Static method call) # $options - Ref to HASH of Data Items # # Output : The result XML file. # # Returns : 0 - some tests failed # 1 - all tests passed # sub processUtf { ($className, $options) = @_; $dirName = $Type2Name{$options->{TYPE}} ; Error ("Cannot convert build type to test path: $options->{TYPE}") unless defined $dirName; my ($passed, @instance) = createBuildInstance($options->{TARGET}); # Use common routine to create XML file jats_runutf::writeXmlResults($options, \@instance ); return $passed; } #------------------------------------------------------------------------------ # Function : createBuildInstance # # Description : Reads an JUNIT-like test results file and returns an array with an # entry per test. Each entry is a Hash as described below. # # Inputs : $target - the build target platform. Cannot be undef # # Output : None # # Returns : A pair of values ($passed, $test_results) # $passed - 1 if there were no failed tests # 0 if there were failed tests # @test_results - A data structure consisting of an array of # hashes # Each entry in the hash holds: # NAME - the class and method name of the test (or as # much information as is necessary to uniquely # identify the test) # OUTCOME - one of 'UNKNOWN', 'PASS', 'FAIL', 'ERROR'. # Being the test result. # DURATION - the time (in seconds) that the test took to run # MESSAGE - if the test did not pass, there is more # information here. Most likely a stack dump. # sub createBuildInstance { my ($target) = @_; Error("Must provide a target") unless defined($target); my @filename = findResultsFile(); my ($passed, @test_results) = parseTestRun($target, @filename); return ($passed, @test_results); } #------------------------------------------------------------------------------ # Function : findResultsFile # # Description : Find all files matching the pattern '^Test-.*\.xml' below # the current folder. # # Inputs : none # # Output : none # # Returns : The complete path and filenames of the matching file. # sub findResultsFile { my @testResultsFile; find(sub { if (/^test-.*\.xml/i) { # # Need the file to be in the right directory too # if ($File::Find::dir =~ m/$dirName$/) { push @testResultsFile, File::Spec->rel2abs($_); } } }, '.'); Error("Could not find any Unit Test Results files.", "Check that it has a filename of 'TEST-*.xml'", "Scan starts at: " . cwd()) unless @testResultsFile; Verbose("Processing AndroidStudio Results files: ". join(', ', @testResultsFile)."\n"); return sort @testResultsFile; } #------------------------------------------------------------------------------ # Function : parseTestRun # # Description : # # Inputs : $target - the build target platform to assign to each test # @filenames - the list of junit results.xml files to parse # # Output : none # # Returns : a pair of values ($passed, @test_results) # $passed - 1 if all tests passed # 0 if some tests failed # @test_results - A list of TestResult's (see above) # sub parseTestRun { my ($target, @filenames) = @_; my ($passed, @test_results); my ($project_name, $package_name, $package_version, $timestamp); $passed = 1; while (my $filename = shift @filenames) { open( my $infile, "<$filename" ) || Error ( "Cannot read from $filename", $! ); # Read the file, line by line while ( <$infile> ) { # Extract one test case # # This may progress the file pointer if ... # is multiline my @test_case = getTestCase($_, $infile) if /\' or '/>' tag. # # Inputs : $line - the current line in the results.xml file. # # Output : none # # Returns : 1 - closing tag found # 0 - o closing tag found # sub containsClosingTag { my ($line) = @_; return 1 if ($line =~ /\<\/testcase\>/); return 1 if ($line =~ /\/\>$/); return 0; } #------------------------------------------------------------------------------ # Function : getTestCase # # Description : Reads from the file, and advances the file pointer, until the # closing '' or '/>' tag is read. # # Inputs : $line - the current line in the results.xml file. This line # will contain ' tag push(@result, $line); # No more to do if it's all on one line return @result if containsClosingTag($line); # Save subsequent lines up to and including the closing tag while (<$file>) { push (@result, $_); last if /\<\/testcase\>/; # don't check for '/>' here as we're multi-line. } return @result; } #------------------------------------------------------------------------------ # Function : getDetails # # Description : # # Inputs : $line - a line of XML containing all the attributes of the # tag. # # Output : none # # Returns : A tuple of values ($name, $duration, $outcome) # $name - The test name, concatenated from the 'classname' and # 'name' attributes. # $duration - The test duration, in seconds, from the 'time' # attribute. # $outcome - The test outcome (= 'PASS') if we know it (i.e. the # closing '' or '/>' tag is on the same # line). # Otherwise, if we don't know it, return undef. # sub getDetails { my ($line) = @_; # Pattern to extract a thing between two quotes (' or "). my ($xml_value) = qr/["\']([^"\']*)["\']/; my ($name, $duration, $outcome); if ($line =~ /\sname=${xml_value}\s*classname=${xml_value}\s*time=${xml_value}/) { $name = $2. '::'. $1; # Convert float into milliseconds $duration = $3; $duration *= 1000; $duration = int($duration + 0.5); $outcome = 'PASS' if containsClosingTag($line); } return ($name, $duration, $outcome); } #------------------------------------------------------------------------------ # Function : parseMessage # # Description : parse the given element tag, and return its contents. # # Inputs : $pattern - The XML element name from which to extract the # message. # $line - The line with the open tag. E.g. # # @lines - all lines until, and including, the closing tag. E.g. # ... # ... # # # Output : none # # Returns : The value of the matched element. # sub parseMessage { my ($pattern, $line, @lines) = @_; my ($message); if ($line =~ /\<${pattern} /) { my $temp_message = $line; # consume until while ($line = shift @lines) { $temp_message .= $line; last if $line =~ /\<\/${pattern}\>/; } # Extract between '' and '' $temp_message =~ m/\<${pattern}[^>]*>([^\<]*)\<\/${pattern}>/; $message = $1; } return ($message); } #------------------------------------------------------------------------------ # Function : parseTestCase # # Description : Takes a element and parses it into a hash. # # Inputs : @lines - The lines from the file from the opening, to the # closing tag (inclusive). # # Output : none # # Returns : A hash with the following keys: # NAME - the test method name. # DURATION - the test duration, in seconds. # OUTCOME - one of 'PASS', 'FAILED', 'ERROR' # sub parseTestCase { my %testRun; while (my $line = shift @_) { my ($name, $duration, $outcome, $message); ($name, $duration, $outcome) = getDetails($line); if (defined($name) && defined($duration)) { $testRun{NAME} = $name; $testRun{DURATION} = $duration; $testRun{OUTCOME} = $outcome if (defined($outcome)); next; } last if containsClosingTag($line); ($message) = parseMessage(qr/error/ , $line, @_); if (defined($message)) { $testRun{OUTCOME} = 'ERROR'; $testRun{MESSAGE} = $message; next; } ($message) = parseMessage(qr/failure/, $line, @_); if (defined($message)) { $testRun{OUTCOME} = 'FAILURE'; $testRun{MESSAGE} = $message; next; } } return %testRun; } 1;