Subversion Repositories DevTools

Rev

Rev 5412 | Blame | Compare with Previous | Last modification | View Log | RSS feed

########################################################################
# 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 <testcase>...</testcase>
            #   is multiline
            my @test_case = getTestCase($_, $infile) if /\<testcase/;

            #   Parse the test case creating a hash
            my %test_run = parseTestCase( @test_case) if (@test_case);

            #   Save the test result in the array
            push(@test_results, {%test_run}) if (%test_run);

            #   Record that there was at least one failed test
            $passed = 0 if (%test_run && ($test_run{OUTCOME} ne 'PASS'));
        }
        close ($infile);    # Just so we don't hog file handles
    }
    return ($passed, @test_results);
}

#------------------------------------------------------------------------------
# Function    : containsClosingTag
#
# Description : Handles the determination of checking for the closing
#               '</testcase>' 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 '</testcase>' or '/>' tag is read.
#
# Inputs      : $line - the current line in the results.xml file. This line
#                       will contain '<testcase'.
#               $file - the file handle of the results.xml file.
#
# Output      : none
#
# Returns     : A string array of all lines read, including the start and end
#               'testcase' tag.
#
sub getTestCase {
    my ($line, $file) = @_;
    my (@result);

    #   Save the first line, containing the opening <testcase> 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 </testcase> 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
#                       <testcase> 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 '</testcase>' 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.
#                          <error ...>
#               @lines - all lines until, and including, the closing tag. E.g.
#                            ...
#                            ...
#                          </error>
#
# 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 '<pattern ...>' and '</pattern>'
        $temp_message =~ m/\<${pattern}[^>]*>([^\<]*)\<\/${pattern}>/;

        $message = $1;
    }
    return ($message);
}

#------------------------------------------------------------------------------
# Function    : parseTestCase
#
# Description : Takes a <testCase> element and parses it into a hash.
#
# Inputs      : @lines - The lines from the file from the opening, to the
#                        closing <testCase> 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;