Sunday, January 21, 2007

JUnit-Style XML from Perl Test Files

Here is an experimental program that runs Perl test files and produces the same sort of XML output as the <junit> ant task. Getting this XML output is helpful when running Perl tests under CruiseControl.
#!/usr/bin/perl

use strict;
use warnings;

use Test::Harness::Straps;
use Time::HiRes qw(gettimeofday tv_interval);
use XML::Generator ':noimport';

my @files = @ARGV;

my $strap     = Test::Harness::Straps->new;
my $generator = XML::Generator->new(':pretty');

my @properties = _get_properties($generator);
my $test_results = _run_tests( $strap, $generator, @files );

my $xml = _get_junit_xml( $generator, \@properties, $test_results );

print "$xml\n";

exit;

#-------------------------------------------------------------------------------

sub _get_junit_xml {
    my ( $generator, $properties, $test_results ) = @_;

    my $system_out = 'system-out';
    my $system_err = 'system-err';
    my $xml        = '';
    $xml .= "\n";
    $xml .= $generator->testsuite(
        {
            errors   => 0,
            failures => $test_results->{total_failures},
            name     => 'name of the test suite',
            tests    => $test_results->{total_tests},
            'time'   => $test_results->{total_time},
        },
        $generator->properties(@properties),
        @{ $test_results->{test_cases} },
        $generator->$system_out(),
        $generator->$system_err(),

    );
    return $xml;
}

sub _run_tests {
    my ( $strap, $generator, @files ) = @_;
    my @test_cases     = ();
    my $total_tests    = 0;
    my $total_time     = 0;
    my $total_failures = 0;

    foreach my $test_file (@files) {
        my $start_time   = [gettimeofday];
        my $file_results = $strap->analyze_file($test_file);
        my $elapsed_time = tv_interval( $start_time, [gettimeofday] );
        $total_time += $elapsed_time;
        foreach my $assertion ( @{ $file_results->details } ) {
            if ( _skip($assertion) ) {
                next;
            }
            $total_tests++;
            $total_failures += _is_failure($assertion);
            my $test_case = {
                classname => $test_file,
                name      => $assertion->{name},
                'time'    => 0,
            };
            push @test_cases, $generator->testcase($test_case);
        }
    }
    my $test_results = {
        total_time     => $total_time,
        test_cases     => \@test_cases,
        total_tests    => $total_tests,
        total_failures => $total_failures,
    };
    return $test_results;
}

sub _skip {
    my $assertion = shift;
    return $assertion->{type} =~ / skip | todo /x;
}

sub _is_failure {
    my $assertion = shift;
    return !$assertion->{ok};
}

sub _get_properties {
    my $generator  = shift;
    my @properties = ();
    foreach my $key ( sort keys %ENV ) {
        push @properties,
          $generator->property( { name => "$key", value => $ENV{$key} } );
    }
    return @properties;
}

__END__

=head1 NAME

junit_xml.pl - Run Perl tests and get JUnit-style XML output

=head1 SYNOPSIS

   junit_xml.pl file1 [ file2 ... ]

=head1 DESCRIPTION

Experimental script to run perl test files and produce the
same XML output as produced by the <junit> ant task.

=head1 DEPENDENCIES

 Test::Harness
 Time::HiRes
 XML::Generator 

=head1 BUGS

  - Doesn't do anything with the STDERR from tests.
  - Doesn't fill in the 'errors' attribute in the  <testsuite> element.
  - Doesn't handle when all tests in file are skipped (skip_all)
  - Doesn't get the elapsed time for each 'test' (i.e. assertion.)


=head1 AUTHOR

Matisse Enzer <matisse@matisse.net>

=head1 COPYRIGHT & LICENSE

Copyright (c) 2007 Matisse Enzer. All Rights Reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.
=cut
Updates: This post was originally published on 2007-01-21. 2008-05-26: I noticed that Justin Mason has created a better script for converting TAP output to JUnit-style XML

Technorati Tags: ,

Unix shell: Finding the run-time directory of a script

I've been trying to come up with an elegant way, in a shell script, to find the full path of the directory in which the script is located.

So far, I have this, which works for bash but not for sh (sh does not support substring expansion on variables):


 #!/usr/local/bin/bash -x
# Finds the full path to the directory containing this script

dir_containing_this_script=""
script_dirname=`dirname $0`
first_char="${script_dirname:0:1}" ; # Starting at index 0, 1 character
case "$first_char" in
("/")
# It's a full path. Use it unmodified.
dir_containing_this_script="${script_dirname}"
;;
(*)
# Strip leading . characters
while [ "${first_char}" = "." ]; do
script_dirname="${script_dirname:1}" ; # substring from index 1 to end
first_char="${script_dirname:0:1}"
done
current_dir=`pwd`
if [ -s "${script_dirname}" ] ; then
dir_containing_this_script="${current_dir}/${script_dirname}"
else
dir_containing_this_script="${current_dir}"
fi
;;
esac

echo "$dir_containing_this_script"


Update: Eric M on The WELL posted this most excellent solution:

dn=`dirname $0`
path=`(cd $dn; pwd)`
echo "path = $path"

Technorati Tags: , ,

Friday, January 19, 2007

Super Easy Graphics Programming

Eric Townsend pointed me to Processing - super easy programming environment for creating graphics programs.

Here's an applet I generated from it in a few minutes work, starting with an example Eric posted on The WELL.

/// start
void setup() {
size(600,600);
}

void draw(){
background(255);

int left_x = 0;
int right_x = width;
int top_y = 0;
int bottom_y = height;
int light_green = #99FF99;
int light_blue = #66CCFF;
int light_yellow = #FFCC66;
int pale_red = #FF3366;

for (int n=0; n < 100; n++) {
int y_of_n = n * (bottom_y/100);
int x_of_n = n * (right_x/100);

stroke(pale_red);
line(left_x,y_of_n,mouseX,mouseY);

stroke(light_yellow);
line(right_x,y_of_n,mouseX,mouseY);

stroke(light_blue);
line(x_of_n,0,mouseX,mouseY);

stroke(light_green);
line(x_of_n,bottom_y,mouseX,mouseY);
}
}

/// end

Technorati Tags: ,