########################################################################
# 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 $pattern>
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;