TAP-Formatter-JUnit-0.09000755001750001750 011710110733 14731 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/Build.PL000444001750001750 114011710110733 16356 0ustar00grahamgraham000000000000use strict; use warnings; use Module::Build; Module::Build->new( 'module_name' => 'TAP::Formatter::JUnit', 'license' => 'perl', 'dist_author' => 'Graham TerMarsch (cpan@howlingfrog.com)', 'requires' => { 'XML::Generator' => 0, 'TAP::Harness' => 3.12, 'Moose' => 0, 'MooseX::NonMoose' => 0, 'File::Slurp' => 0, }, 'build_requires' => { 'Test::More' => 0, 'Test::Differences' => 0, 'IO::Scalar' => 0, }, )->create_build_script(); TAP-Formatter-JUnit-0.09/Changes000444001750001750 622211710110733 16363 0ustar00grahamgraham000000000000Revision history for Perl extension TAP::Formatter::JUnit. 0.09 Wed Jan 25 15:13 PST 2012 - Switch from Class::Field to Moose. Thanks to Dave Lambley for the poke, and the patience. - Track and report timings for "(init)" and "(teardown)" of the test. Without this, Hudson does not properly report on the total time needed for a test suite (it calculates total time by adding up the constituent tests, not by looking at the "time" attribute). - Rewrite internals, switching from a streaming style to an iterative style of processing the TAP. Same results, but easier to work with. 0.08 Thu Jul 15 23:44 PDT 2010 - RT#58838, "Error reporting on die or missing plan". Thanks to Colin Robertson. Output compatible w/Hudson (so it now sees these as errors). - RT#59206, "Plan/Tests Mismatch". Thanks for Phillip Kimmey. JUnit output now reports mismatches with an "" so Hudson detects it. 0.07 Fri Jan 29 23:23 PST 2010 - Fix RT#53927, "Times reported by T:F:JUnit for individual test cases in a .t file are incorrect". Thanks to Marc Abramowitz. 0.06 Wed Jan 13 21:24 PST 2010 - Fix bug in tap2junit which would cause multiple TAP streams to have the *same* name in their output JUnit XML. Introduced in 0.04. 0.05 Wed Jan 13 16:32 PST 2010 - Add support for ALLOW_PASSING_TODOS environment variable, which forces T:F:JUnit to treat passing TODOs as a "pass" and not a "fail" condition. Thanks to Joe McMahon. - Removed need for Test::Output; I forgot that you can pass a FH directly in to TAP::Harness. Doh! 0.04 Wed Jan 13 15:51 PST 2010 - extra escaping/cleanup of characters before inserting them into the XML stream, to keep JUnit parsers like Hudson's from choking. Thanks go out to Joe McMahon and Michael Nachbaur for prodding to get this fixed and for patches. - new "--name" option for tap2junit, allowing for tests to be explicitly named. Aliased to "--junit_name" to provide compatibility with patch from Joe McMahon. - tap2junit can now filter stdin/stdout; use "-" as the filename. Thanks to Joe McMahon for the original patch on which this is based - switch unit tests to use Test::Output for capturing output, instead of trying to run "prove" directly - update unit tests to run against "blib/lib" and "blib/script" instead of just "lib" and "bin" 0.03 Sun Dec 13 22:36 PST 2009 - add timer output for each test case (not just for the suite as a whole); Hudson needs this in order to show timing output for test runs. Thanks to Mark Aufflick for the poke. - internal cleanups 0.02 Fri Jan 9 23:35 PST 2009 - POD updates - minor cleanup to the test names output in JUnit - attempt to fix failing CPAN Tester reports, where an older version of 'prove' was being picked up by t/formatter.t; provide our own 't/bin/my-prove' and use that instead. 0.01 Wed Jan 7 22:06 PST 2009 - initial version - had this sitting around on my HD for several months and am (finally) getting around to uploading it to CPAN TAP-Formatter-JUnit-0.09/MANIFEST.SKIP000444001750001750 7711710110733 16730 0ustar00grahamgraham000000000000Build _build/ .git/ \..*\.swp blib/ ^MYMETA.yml$ ^MYMETA.json$ TAP-Formatter-JUnit-0.09/README000444001750001750 45111710110733 15726 0ustar00grahamgraham000000000000TAP::Formatter::JUnit formats TAP output as JUnit XML Copyright (C) 2008, Graham TerMarsch. All Rights Reserved. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. To install: perl Build.PL ./Build ./Build test ./Build install TAP-Formatter-JUnit-0.09/MANIFEST000444001750001750 503611710110733 16223 0ustar00grahamgraham000000000000Build.PL Changes MANIFEST MANIFEST.SKIP META.yml META.json README bin/tap2junit lib/TAP/Formatter/JUnit.pm lib/TAP/Formatter/JUnit/Result.pm lib/TAP/Formatter/JUnit/Session.pm t/formatter.t t/passing-todos.t t/pod.t t/pod-coverage.t t/tap2junit.t t/tap2junit-filter.t t/tap2junit-name.t t/timer.t t/data/tap/bailout t/data/tap/bad_chars t/data/tap/descriptive t/data/tap/descriptive_trailing t/data/tap/die t/data/tap/die_head_end t/data/tap/die_last_minute t/data/tap/die_unfinished t/data/tap/empty t/data/tap/no_nums t/data/tap/simple t/data/tap/simple_fail t/data/tap/simple_yaml t/data/tap/skip t/data/tap/skipall t/data/tap/skipall_nomsg t/data/tap/skip_nomsg t/data/tap/stdout_stderr t/data/tap/todo t/data/tap/todo_inline t/data/tap/todo_misparse t/data/tap/too_many t/data/tap/junit/bailout t/data/tap/junit/bad_chars t/data/tap/junit/descriptive t/data/tap/junit/descriptive_trailing t/data/tap/junit/die t/data/tap/junit/die_head_end t/data/tap/junit/die_last_minute t/data/tap/junit/die_unfinished t/data/tap/junit/empty t/data/tap/junit/no_nums t/data/tap/junit/simple t/data/tap/junit/simple_fail t/data/tap/junit/simple_yaml t/data/tap/junit/skip t/data/tap/junit/skipall t/data/tap/junit/skipall_nomsg t/data/tap/junit/skip_nomsg t/data/tap/junit/stdout_stderr t/data/tap/junit/todo t/data/tap/junit/todo_inline t/data/tap/junit/todo_misparse t/data/tap/junit/too_many t/data/tests/bailout t/data/tests/bad_chars t/data/tests/descriptive t/data/tests/descriptive_trailing t/data/tests/die t/data/tests/die_head_end t/data/tests/die_last_minute t/data/tests/die_unfinished t/data/tests/empty t/data/tests/no_nums t/data/tests/simple t/data/tests/simple_fail t/data/tests/simple_yaml t/data/tests/skip t/data/tests/skipall t/data/tests/skipall_nomsg t/data/tests/skip_nomsg t/data/tests/stdout_stderr t/data/tests/todo t/data/tests/todo_inline t/data/tests/todo_misparse t/data/tests/too_many t/data/tests/junit/bailout t/data/tests/junit/bad_chars t/data/tests/junit/bad_chars t/data/tests/junit/descriptive t/data/tests/junit/descriptive_trailing t/data/tests/junit/die t/data/tests/junit/die_head_end t/data/tests/junit/die_last_minute t/data/tests/junit/die_unfinished t/data/tests/junit/empty t/data/tests/junit/no_nums t/data/tests/junit/simple t/data/tests/junit/simple_fail t/data/tests/junit/simple_yaml t/data/tests/junit/skip t/data/tests/junit/skipall t/data/tests/junit/skipall_nomsg t/data/tests/junit/skip_nomsg t/data/tests/junit/stdout_stderr t/data/tests/junit/todo t/data/tests/junit/todo_inline t/data/tests/junit/todo_misparse t/data/tests/junit/too_many TAP-Formatter-JUnit-0.09/META.yml000444001750001750 160111710110733 16335 0ustar00grahamgraham000000000000--- abstract: 'Harness output delegate for JUnit output' author: - 'Graham TerMarsch (cpan@howlingfrog.com)' build_requires: IO::Scalar: 0 Test::Differences: 0 Test::More: 0 configure_requires: Module::Build: 0.38 dynamic_config: 1 generated_by: 'Module::Build version 0.38, CPAN::Meta::Converter version 2.112150' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: 1.4 name: TAP-Formatter-JUnit provides: TAP::Formatter::JUnit: file: lib/TAP/Formatter/JUnit.pm version: 0.09 TAP::Formatter::JUnit::Result: file: lib/TAP/Formatter/JUnit/Result.pm version: 0 TAP::Formatter::JUnit::Session: file: lib/TAP/Formatter/JUnit/Session.pm version: 0 requires: File::Slurp: 0 Moose: 0 MooseX::NonMoose: 0 TAP::Harness: 3.12 XML::Generator: 0 resources: license: http://dev.perl.org/licenses/ version: 0.09 TAP-Formatter-JUnit-0.09/META.json000444001750001750 272511710110733 16515 0ustar00grahamgraham000000000000{ "abstract" : "Harness output delegate for JUnit output", "author" : [ "Graham TerMarsch (cpan@howlingfrog.com)" ], "dynamic_config" : 1, "generated_by" : "Module::Build version 0.38, CPAN::Meta::Converter version 2.112150", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "TAP-Formatter-JUnit", "prereqs" : { "build" : { "requires" : { "IO::Scalar" : 0, "Test::Differences" : 0, "Test::More" : 0 } }, "configure" : { "requires" : { "Module::Build" : "0.38" } }, "runtime" : { "requires" : { "File::Slurp" : 0, "Moose" : 0, "MooseX::NonMoose" : 0, "TAP::Harness" : "3.12", "XML::Generator" : 0 } } }, "provides" : { "TAP::Formatter::JUnit" : { "file" : "lib/TAP/Formatter/JUnit.pm", "version" : "0.09" }, "TAP::Formatter::JUnit::Result" : { "file" : "lib/TAP/Formatter/JUnit/Result.pm", "version" : 0 }, "TAP::Formatter::JUnit::Session" : { "file" : "lib/TAP/Formatter/JUnit/Session.pm", "version" : 0 } }, "release_status" : "stable", "resources" : { "license" : [ "http://dev.perl.org/licenses/" ] }, "version" : "0.09" } TAP-Formatter-JUnit-0.09/lib000755001750001750 011710110733 15477 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/lib/TAP000755001750001750 011710110733 16123 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/lib/TAP/Formatter000755001750001750 011710110733 20066 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/lib/TAP/Formatter/JUnit.pm000444001750001750 1151011710110733 21630 0ustar00grahamgraham000000000000package TAP::Formatter::JUnit; use Moose; use MooseX::NonMoose; extends qw( TAP::Formatter::Console ); use XML::Generator; use TAP::Formatter::JUnit::Session; our $VERSION = '0.09'; has 'testsuites' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] }, traits => [qw( Array )], handles => { add_testsuite => 'push', }, ); has 'xml' => ( is => 'rw', isa => 'XML::Generator', lazy_build => 1, ); sub _build_xml { return XML::Generator->new( ':pretty', ':std', 'escape' => 'always,high-bit,even-entities', 'encoding' => 'UTF-8', ); } ############################################################################### # Subroutine: open_test($test, $parser) ############################################################################### # Over-ridden 'open_test()' method. # # Creates a 'TAP::Formatter::JUnit::Session' session, instead of a console # formatter session. sub open_test { my ($self, $test, $parser) = @_; my $session = TAP::Formatter::JUnit::Session->new( { name => $test, formatter => $self, parser => $parser, passing_todo_ok => $ENV{ALLOW_PASSING_TODOS} ? 1 : 0, } ); return $session; } ############################################################################### # Subroutine: summary($aggregate) ############################################################################### # Prints the summary report (in JUnit) after all tests are run. sub summary { my ($self, $aggregate) = @_; return if $self->silent(); my @suites = @{$self->testsuites}; print { $self->stdout } $self->xml->testsuites( @suites ); } 1; =head1 NAME TAP::Formatter::JUnit - Harness output delegate for JUnit output =head1 SYNOPSIS On the command line, with F: prove --formatter TAP::Formatter::JUnit ... Or, in your own scripts: use TAP::Harness; my $harness = TAP::Harness->new( { formatter_class => 'TAP::Formatter::JUnit', merge => 1, } ); $harness->runtests(@tests); =head1 DESCRIPTION B C provides JUnit output formatting for C. By default (e.g. when run with F), the I test suite is gathered together into a single JUnit XML document, which is then displayed on C. You can, however, have individual JUnit XML files dumped for each individual test, by setting c to a directory that you would like the JUnit XML dumped to. Note, that this will B cause C to dump the original TAP output into that directory as well (but IMHO that's ok as you've now got the data in two parsable formats). Timing information is included in the JUnit XML, I you specified C<--timer> when you ran F. In standard use, "passing TODOs" are treated as failure conditions (and are reported as such in the generated JUnit). If you wish to treat these as a "pass" and not a "fail" condition, setting C in your environment will turn these into pass conditions. The JUnit output generated is partial to being grokked by Hudson (L). That's the build tool I'm using at the moment and needed to be able to generate JUnit output for. =head1 ATTRIBUTES =over =item testsuites List-ref of test suites that have been executed. =item xml An C instance, to be used to generate XML output. =back =head1 METHODS =over =item B Over-ridden C method. Creates a C session, instead of a console formatter session. =item B Prints the summary report (in JUnit) after all tests are run. =item B Adds the given XML test C<$suite> to the list of test suites that we've executed and need to summarize. =back =head1 AUTHOR Graham TerMarsch Many thanks to Andy Armstrong et al. for the B set of tests in C; they became the basis for the unit tests here. Other thanks go out to those that have provided feedback, comments, or patches: Mark Aufflick Joe McMahon Michael Nachbaur Marc Abramowitz Colin Robertson Phillip Kimmey Dave Lambley =head1 COPYRIGHT Copyright 2008-2010, Graham TerMarsch. All Rights Reserved. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L, L, L, L, L. =cut TAP-Formatter-JUnit-0.09/lib/TAP/Formatter/JUnit000755001750001750 011710110733 21117 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/lib/TAP/Formatter/JUnit/Session.pm000444001750001750 3274311710110733 23266 0ustar00grahamgraham000000000000package TAP::Formatter::JUnit::Session; use Moose; use MooseX::NonMoose; extends qw( TAP::Formatter::Console::Session ); use Storable qw(dclone); use File::Path qw(mkpath); use IO::File; use TAP::Formatter::JUnit::Result; has 'testcases' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] }, traits => [qw( Array )], handles => { add_testcase => 'push', num_testcases => 'count', }, ); has 'passing_todo_ok' => ( is => 'rw', isa => 'Bool', default => 0, ); has '_queue' => ( is => 'rw', isa => 'ArrayRef', default => sub { [] }, traits => [qw( Array )], handles => { _queue_add => 'push', }, ); ############################################################################### # Subroutine: _initialize($arg_for) ############################################################################### # Custom initializer, so we can accept a new "passing_todo_ok" argument at # instantiation time. sub _initialize { my ($self, $arg_for) = @_; $arg_for ||= {}; my $passing_todo_ok = delete $arg_for->{passing_todo_ok}; $self->passing_todo_ok($passing_todo_ok); return $self->SUPER::_initialize($arg_for); } ############################################################################### # Subroutine: result($result) ############################################################################### # Called by the harness for each line of TAP it receives. # # Queues up all of the TAP output for later conversion to JUnit. sub result { my ($self, $result) = @_; # except for a few things we don't want to process as a "test case", add # the test result to the queue. unless ( ($result->raw() =~ /^# Looks like you failed \d+ tests? of \d+/) || ($result->raw() =~ /^# Looks like you planned \d+ tests? but ran \d+/) || ($result->raw() =~ /^# Looks like your test died before it could output anything/) ) { my $wrapped = TAP::Formatter::JUnit::Result->new( 'time' => $self->get_time, 'result' => $result, ); $self->_queue_add($wrapped); } } ############################################################################### # Subroutine: close_test() ############################################################################### # Called to close the test session. # # Flushes the queue if we've got anything left in it, dumps the JUnit to disk # (if necessary), and adds the XML for this test suite to our formatter. sub close_test { my $self = shift; my $xml = $self->xml; my $parser = $self->parser; # Process the queued up TAP stream my $is_first = 1; my $t_start = $self->parser->start_time; my $t_last_test = $t_start; my $timer_enabled = $self->formatter->timer; my $queue = $self->_queue; my $index = 0; while ($index < @{$queue}) { my $result = $queue->[$index++]; # First line of output generates the "init" timing. if ($is_first) { if ($timer_enabled) { unless ($result->is_test) { my $duration = $result->time - $t_start; my $case = $xml->testcase( { 'name' => _squeaky_clean('(init)'), 'time' => $duration, } ); $self->add_testcase($case); $t_last_test = $result->time; } } $is_first = 0; } # Test output if ($result->is_test) { # how long did it take for this test? my $duration = $result->time - $t_last_test; # slurp in all of the content up until the next test my $content = $result->as_string; while ($index < @{$queue}) { last if ($queue->[$index]->is_test); last if ($queue->[$index]->is_plan); my $stuff = $queue->[$index++]; $content .= "\n" . $stuff->as_string; } # create a failure/error element if the test was bogus my $failure; my $bogosity = $self->_check_for_test_bogosity($result); if ($bogosity) { my $cdata = $self->_cdata($content); my $level = $bogosity->{level}; $failure = $xml->$level( { type => $bogosity->{type}, message => $bogosity->{message}, }, $cdata ); } # add this test to the XML stream my $case = $xml->testcase( { 'name' => _get_testcase_name($result), ( $timer_enabled ? ('time' => $duration) : () ), }, $failure, ); $self->add_testcase($case); # update time of last test seen $t_last_test = $result->time; } } # track time for teardown, if needed if ($timer_enabled) { my $duration = $self->parser->end_time - $queue->[-1]->time; my $case = $xml->testcase( { 'name' => _squeaky_clean('(teardown)'), 'time' => $duration, } ); $self->add_testcase($case); } # collect up all of the captured test output my $captured = join '', map { $_->raw . "\n" } @{$queue}; # if the test died unexpectedly, make note of that my $die_msg; my $exit = $parser->exit(); if ($exit) { my $wstat = $parser->wait(); my $status = sprintf("%d (wstat %d, 0x%x)", $exit, $wstat, $wstat); $die_msg = "Dubious, test returned $status"; } # add system-out/system-err data, as raw CDATA my $sys_out = 'system-out'; $sys_out = $xml->$sys_out($captured ? $self->_cdata($captured) : undef); my $sys_err = 'system-err'; $sys_err = $xml->$sys_err($die_msg ? $self->_cdata("$die_msg\n") : undef); # update the testsuite with aggregate info on this test suite # # tests - total number of tests run # time - wallclock time taken for test run (floating point) # failures - number of tests that we detected as failing # errors - number of errors: # - passing TODOs # - if a plan was provided, mismatch between that and the # number of actual tests that were run # - either "no plan was issued" or "test died" (a dying test # may not have a plan issued, but should still be considered # a single error condition) my $testsrun = $parser->tests_run() || 0; my $time = $parser->end_time() - $parser->start_time(); my $failures = $parser->failed(); my $noplan = $parser->plan() ? 0 : 1; my $planned = $parser->tests_planned() || 0; my $num_errors = 0; $num_errors += $parser->todo_passed() unless $self->passing_todo_ok(); $num_errors += abs($testsrun - $planned) if ($planned); my $suite_err; if ($die_msg) { $suite_err = $xml->error( { message => $die_msg } ); $num_errors ++; } elsif ($noplan) { $suite_err = $xml->error( { message => 'No plan in TAP output' } ); $num_errors ++; } elsif ($planned && ($testsrun != $planned)) { $suite_err = $xml->error( { message => "Looks like you planned $planned tests but ran $testsrun." } ); } my @tests = @{$self->testcases()}; my %attrs = ( 'name' => _get_testsuite_name($self), 'tests' => $testsrun, 'failures' => $failures, 'errors' => $num_errors, ( $timer_enabled ? ('time' => $time) : () ), ); my $testsuite = $xml->testsuite(\%attrs, @tests, $sys_out, $sys_err, $suite_err); $self->formatter->add_testsuite($testsuite); $self->dump_junit_xml($testsuite); } ############################################################################### # Subroutine: dump_junit_xml($testsuite) ############################################################################### # Dumps the JUnit for the given XML '$testsuite', to the directory specified by # 'PERL_TEST_HARNESS_DUMP_TAP'. sub dump_junit_xml { my ($self, $testsuite) = @_; if (my $spool_dir = $ENV{PERL_TEST_HARNESS_DUMP_TAP}) { my $spool = File::Spec->catfile($spool_dir, $self->name() . '.junit.xml'); # clone the testsuite; XML::Generator only lets us auto-vivify the # CDATA sections *ONCE*. $testsuite = dclone($testsuite); # create target dir my ($vol, $dir, undef) = File::Spec->splitpath($spool); my $path = File::Spec->catpath($vol, $dir, ''); mkpath($path); # create JUnit XML, and dump to disk my $junit = $self->xml->xml($self->xml->testsuites($testsuite) ); my $fout = IO::File->new( $spool, '>:utf8' ) || die "Can't write $spool ( $! )\n"; $fout->print($junit); $fout->close(); } } ############################################################################### # Subroutine: xml() ############################################################################### # Returns a new 'XML::Generator' to generate XML output. This is simply a # shortcut to '$self->formatter->xml()'. sub xml { my $self = shift; return $self->formatter->xml(); } ############################################################################### # Checks for bogosity in the test result. sub _check_for_test_bogosity { my $self = shift; my $result = shift; if ($result->todo_passed() && !$self->passing_todo_ok()) { return { level => 'error', type => 'TodoTestSucceeded', message => $result->explanation(), }; } if ($result->is_unplanned()) { return { level => 'error', type => 'UnplannedTest', message => $result->as_string(), }; } if (not $result->is_ok()) { return { level => 'failure', type => 'TestFailed', message => $result->as_string(), }; } return; } ############################################################################### # Generates the name for a test case. sub _get_testcase_name { my $test = shift; my $name = join(' ', $test->number(), _clean_test_description($test)); $name =~ s/\s+$//; return $name; } ############################################################################### # Generates the name for the entire test suite. sub _get_testsuite_name { my $self = shift; my $name = $self->name; $name =~ s{^\./}{}; $name =~ s{^t/}{}; return _clean_to_java_class_name($name); } ############################################################################### # Cleans up the given string, removing any characters that aren't suitable for # use in a Java class name. sub _clean_to_java_class_name { my $str = shift; $str =~ s/[^-:_A-Za-z0-9]+/_/gs; return $str; } ############################################################################### # Cleans up the description of the given test. sub _clean_test_description { my $test = shift; my $desc = $test->description(); return _squeaky_clean($desc); } ############################################################################### # Creates a CDATA block for the given data (which is made squeaky clean first, # so that JUnit parsers like Hudson's don't choke). sub _cdata { my ($self, $data) = @_; $data = _squeaky_clean($data); return $self->xml->xmlcdata($data); } ############################################################################### # Clean a string to the point that JUnit can't possibly have a problem with it. sub _squeaky_clean { my $string = shift; # control characters (except CR and LF) $string =~ s/([\x00-\x09\x0b\x0c\x0e-\x1f])/"^".chr(ord($1)+64)/ge; # high-byte characters $string =~ s/([\x7f-\xff])/'[\\x'.sprintf('%02x',ord($1)).']'/ge; return $string; } 1; =head1 NAME TAP::Formatter::JUnit::Session - Harness output delegate for JUnit output =head1 DESCRIPTION C provides JUnit output formatting for C. =head1 METHODS =over =item B<_initialize($arg_for)> Over-ridden private initializer, so we can accept a new "passing_todo_ok" argument at instantiation time. =item B Called by the harness for each line of TAP it receives. Internally, all of the TAP is added to a queue until we hit the start of the "next" test (at which point we flush the queue. This allows us to capture any error output or diagnostic info that comes after a test failure. =item B Called to close the test session. Flushes the queue if we've got anything left in it, dumps the JUnit to disk (if necessary), and adds the XML for this test suite to our formatter. =item B Dumps the JUnit for the given XML C<$testsuite>, to the directory specified by C. =item B Adds an XML test C<$case> to the list of testcases we've run in this session. =item B Returns a new C to generate XML output. This is simply a shortcut to C<$self-Eformatter-Exml()>. =back =head1 AUTHOR Graham TerMarsch =head1 COPYRIGHT Copyright 2008-2010, Graham TerMarsch. All Rights Reserved. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L. =cut TAP-Formatter-JUnit-0.09/lib/TAP/Formatter/JUnit/Result.pm000444001750001750 172711710110733 23077 0ustar00grahamgraham000000000000package TAP::Formatter::JUnit::Result; use Moose; has 'time' => ( is => 'ro', isa => 'Num', required => 1, ); has 'result' => ( is => 'ro', isa => 'TAP::Parser::Result', required => 1, handles => [qw( name number description as_string raw is_test is_plan is_unplanned is_ok todo_passed explanation )], ); 1; =head1 NAME TAP::Formatter::JUnit::Result - Wrapper for a TAP result =head1 DESCRIPTION C is an internal class, used to wrap/augment C objects with timing information. B =head1 AUTHOR Graham TerMarsch =head1 COPYRIGHT Copyright 2011, Graham TerMarsch. All Rights Reserved. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut TAP-Formatter-JUnit-0.09/t000755001750001750 011710110733 15174 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/pod.t000444001750001750 20111710110733 16251 0ustar00grahamgraham000000000000use Test::More; eval "use Test::Pod 1.00"; plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; all_pod_files_ok(); TAP-Formatter-JUnit-0.09/t/passing-todos.t000444001750001750 252511710110733 20314 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More tests => 6; use TAP::Harness; use IO::Scalar; ############################################################################### # TEST: passing TODOs are normally treated as failure condition. passing_todo_default_fail: { my $results = undef; my $fh = IO::Scalar->new(\$results); my $harness = TAP::Harness->new( { formatter_class => 'TAP::Formatter::JUnit', stdout => $fh, } ); $harness->runtests('t/data/tests/todo'); ok $results, 'Ran test with passing TODO'; like $results, qr/]+errors="1"/, '... with one error'; like $results, qr/TodoTestSucceeded/, '... passing TODO'; } ############################################################################### # TEST: over-ride allows for passing TODOs to be treated as a pass. passing_todo_ok: { local $ENV{ALLOW_PASSING_TODOS} = 1; my $results = undef; my $fh = IO::Scalar->new(\$results); my $harness = TAP::Harness->new( { formatter_class => 'TAP::Formatter::JUnit', stdout => $fh, } ); $harness->runtests('t/data/tests/todo'); ok $results, 'Re-ran test with passing TODO'; like $results, qr/]+errors="0"/, '... with NO errors'; unlike $results, qr/TodoTestSucceeded/, '... passing TODO was OK'; } TAP-Formatter-JUnit-0.09/t/timer.t000444001750001750 1076211710110733 16664 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More tests => 21; use TAP::Harness; use IO::Scalar; use File::Slurp qw(write_file); ############################################################################### # When timer is disabled, we should have *NO* timer info in the JUnit output. timer_disabled: { my $test = qq| use Test::More tests => 1; pass 'no timing in this test'; |; my $results = run_test($test, { timer => 0, } ); ok $results, 'got JUnit'; unlike $results, qr/time/ism, '... without any timing information'; } ############################################################################### # When timer is enabled, JUnit output *should* have timer info in it. timer_enabled: { my $test = qq| use Test::More tests => 2; pass 'one'; pass 'two'; |; my $results = run_test($test, { timer => 1, } ); ok $results, 'got JUnit'; like $results, qr/time/ism, '... with timing information'; } ############################################################################### # Ensure timing correctness, when test has a plan # # Test once with merged output off, then once with it on; want to make sure that # merging diagnostic output into the TAP doesn't monkey up the timings. correct_timing_test_has_plan: { my $test = qq| BEGIN { sleep 3 }; END { sleep 2 }; use Test::More tests => 3; sleep 0; pass "one"; sleep 2; pass "two"; sleep 1; diag "foo"; sleep 1; diag "bar"; sleep 3; diag "foobar"; pass "three"; |; my $expect = { '(init)' => 3, '1 - one' => 0, '2 - two' => 2, '3 - three' => 5, '(teardown)' => 2, }; unmerged: { my $results = run_test($test, { timer => 1, merge => 0, } ); ok $results, 'got JUnit - timing correctness w/test plan (unmerged)'; verify_timings($results, $expect); } merged: { my $results = run_test($test, { timer => 1, merge => 1, } ); ok $results, 'got JUnit - timing correctness w/test plan (merged)'; verify_timings($results, $expect); } } ############################################################################### # Ensure timing correctness, when test has no plan # # The *first* test isn't going to be predictable/accurate w.r.t. the calculated # timing, as it'll also involve the startup overhead. As such, its skipped (by # denoting it as "skip" in its test name). correct_timing_test_unplanned: { my $test = qq| BEGIN { sleep 3 }; END { sleep 2 }; use Test::More qw(no_plan); sleep 0; pass "one"; sleep 2; pass "two"; sleep 1; diag "foo"; sleep 1; diag "bar"; sleep 3; diag "foobar"; pass "three"; |; my $expect = { '1 - one' => 3, # init time is *hidden* in initial test '2 - two' => 2, '3 - three' => 5, '(teardown)' => 2, }; my $results = run_test($test, { timer => 1, merge => 1, } ); ok $results, 'got JUnit - timing correctness w/o test plan'; verify_timings($results, $expect); } sub run_test { my $code = shift; my $opts = shift; my $file = "test-$$.t"; my $junit = undef; my $fh = IO::Scalar->new(\$junit); my $harness = TAP::Harness->new( { formatter_class => 'TAP::Formatter::JUnit', stdout => $fh, %{$opts}, } ); write_file($file, $code); $harness->runtests($file); unlink $file; return $junit; } sub verify_timings { my $junit = shift; my $expect = shift; my @lines = split /^/, $junit; my @tests = grep { /{$name}) { rounds_to($time, $expect->{$name}, "... test timing: $name"); } else { fail "... unexpected test name: $name"; diag $test; } } } sub rounds_to { my ($got, $expected, $message) = @_; my $r_got = sprintf('%1.0f', $got); my $r_expected = sprintf('%1.0f', $expected); local $Test::Builder::Level = $Test::Builder::Level + 1; is $r_got, $r_expected, $message; } TAP-Formatter-JUnit-0.09/t/pod-coverage.t000444001750001750 23011710110733 20044 0ustar00grahamgraham000000000000use Test::More; eval "use Test::Pod::Coverage 1.00"; plan skip_all => "Test::Pod::Coverage 1.00 required for testing POD" if $@; all_pod_coverage_ok(); TAP-Formatter-JUnit-0.09/t/formatter.t000444001750001750 206411710110733 17523 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More; use Test::Differences; use File::Slurp qw(slurp); use TAP::Harness; use IO::Scalar; ############################################################################### # Figure out how many tests we have to run. # # *MANY* thanks Andy Armstrong et al. for the fabulous set of tests in # Test::Harness. :) my @tests = grep { -f $_ } ; plan tests => scalar(@tests); ############################################################################### # Run each of the tests in turn, and compare the output to the expected JUnit # output. foreach my $test (@tests) { (my $junit = $test) =~ s{/tests/}{/tests/junit/}; my $received = ''; my $fh = IO::Scalar->new(\$received); eval { my $harness = TAP::Harness->new( { stdout => $fh, merge => 1, formatter_class => 'TAP::Formatter::JUnit', } ); $harness->runtests($test); }; my $expected = slurp($junit); eq_or_diff $received, $expected, $test; } TAP-Formatter-JUnit-0.09/t/tap2junit-name.t000444001750001750 206611710110733 20360 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; use File::Slurp qw(slurp); ############################################################################### # TEST: Run "tap2junit" and let it name the test in the JUnit automatically. tap2junit_default_name: { my $test_file = 't/data/tap/simple'; my $xml_file = "$test_file.xml"; _tap2junit($test_file); my $xml = slurp($xml_file); unlink $xml_file; like $xml, qr/]+name="data_tap_simple"/m, 'default name based on TAP filename'; } ############################################################################### # TEST: Run "tap2junit" with "--name" and rename a test tap2junit_name: { my $test_file = 't/data/tap/simple'; my $xml_file = "$test_file.xml"; _tap2junit($test_file, '--name', 'foo'); my $xml = slurp($xml_file); unlink $xml_file; like $xml, qr/]+name="foo"/m, 'name explicitly provided'; } sub _tap2junit { my @args = @_; system(qq{ $^X -Iblib/lib blib/script/tap2junit @args 2>/dev/null }); } TAP-Formatter-JUnit-0.09/t/tap2junit.t000444001750001750 201011710110733 17427 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More; use Test::Differences; use File::Slurp qw(slurp); ############################################################################### # Figure out how many TAP files we have to run. Yes, the results *ARE* going # to be different when parsing the raw TAP output than when running under # 'prove'; we won't have any context of "did the test die a horrible death?" my @tests = grep { -f $_ } ; plan tests => scalar(@tests); ############################################################################### # Run each of the TAP files in turn through 'tap2junit', and compare the output # to the expected JUnit output in each case. foreach my $test (@tests) { (my $junit = $test) =~ s{/tap/}{/tap/junit/}; my $rc = system(qq{ $^X -Iblib/lib blib/script/tap2junit $test 2>/dev/null }); my $outfile = "$test.xml"; my $received = slurp($outfile); unlink $outfile; my $expected = slurp($junit); eq_or_diff $received, $expected, $test; } TAP-Formatter-JUnit-0.09/t/tap2junit-filter.t000444001750001750 135311710110733 20723 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use Test::More tests => 2; use Test::Differences; use IPC::Open2 qw(open2); use File::Slurp qw(slurp); ############################################################################### # TEST: Run "tap2junit" in filter mode (in STDIN, out STDOUT) tap2junit_filter: { my $tap = slurp('t/data/tap/simple'); my $xml = slurp('t/data/tap/junit/simple'); my ($chld_in, $chld_out); my $pid = open2($chld_out, $chld_in, qq($^X -Iblib/lib blib/script/tap2junit --name data_tap_simple -) ); ok $pid, 'tap2junit started'; print $chld_in $tap; close $chld_in; my $received = do { local $/; <$chld_out> }; eq_or_diff $received, $xml, 'results generated on STDOUT'; } TAP-Formatter-JUnit-0.09/t/data000755001750001750 011710110733 16105 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap000755001750001750 011710110733 16671 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/no_nums000444001750001750 3011710110733 20360 0ustar00grahamgraham0000000000001..5 ok ok not ok ok ok TAP-Formatter-JUnit-0.09/t/data/tap/descriptive_trailing000444001750001750 21111710110733 23135 0ustar00grahamgraham000000000000ok 1 Interlock activated ok 2 Megathrusters are go ok 3 Head formed ok 4 Blazing sword formed ok 5 Robeast destroyed 1..5 TAP-Formatter-JUnit-0.09/t/data/tap/die_unfinished000444001750001750 2411710110733 21662 0ustar00grahamgraham0000000000001..4 ok 1 ok 2 ok 3 TAP-Formatter-JUnit-0.09/t/data/tap/die000444001750001750 011710110733 17420 0ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/simple000444001750001750 3611710110733 20201 0ustar00grahamgraham0000000000001..5 ok 1 ok 2 ok 3 ok 4 ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/skip000444001750001750 6311710110733 17656 0ustar00grahamgraham0000000000001..5 ok 1 ok 2 # skip rain delay ok 3 ok 4 ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/simple_fail000444001750001750 4611710110733 21175 0ustar00grahamgraham0000000000001..5 ok 1 not ok 2 ok 3 ok 4 not ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/bad_chars000444001750001750 12511710110733 20635 0ustar00grahamgraham0000000000001..3 ok 1 - Control chars  ok 2 - Weird control chars  ok 3 - Unicode Þϴ쌇 TAP-Formatter-JUnit-0.09/t/data/tap/die_head_end000444001750001750 2411710110733 21255 0ustar00grahamgraham000000000000ok 1 ok 2 ok 3 ok 4 TAP-Formatter-JUnit-0.09/t/data/tap/todo000444001750001750 5411710110733 17655 0ustar00grahamgraham0000000000001..5 todo 3 2; ok 1 ok 2 not ok 3 ok 4 ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/todo_misparse000444001750001750 4711710110733 21562 0ustar00grahamgraham0000000000001..1 not ok 1 Hamlette # TODOORNOTTODO TAP-Formatter-JUnit-0.09/t/data/tap/skipall_nomsg000444001750001750 511710110733 21526 0ustar00grahamgraham0000000000001..0 TAP-Formatter-JUnit-0.09/t/data/tap/skip_nomsg000444001750001750 2111710110733 21053 0ustar00grahamgraham0000000000001..1 ok 1 # Skip TAP-Formatter-JUnit-0.09/t/data/tap/skipall000444001750001750 2611710110733 20346 0ustar00grahamgraham0000000000001..0 # skipping: rope TAP-Formatter-JUnit-0.09/t/data/tap/too_many000444001750001750 5011710110733 20531 0ustar00grahamgraham0000000000001..3 ok 1 ok 2 ok 3 ok 4 ok 5 ok 6 ok 7 TAP-Formatter-JUnit-0.09/t/data/tap/die_last_minute000444001750001750 3111710110733 22050 0ustar00grahamgraham000000000000ok 1 ok 2 ok 3 ok 4 1..4 TAP-Formatter-JUnit-0.09/t/data/tap/descriptive000444001750001750 21111710110733 21244 0ustar00grahamgraham0000000000001..5 ok 1 Interlock activated ok 2 Megathrusters are go ok 3 Head formed ok 4 Blazing sword formed ok 5 Robeast destroyed TAP-Formatter-JUnit-0.09/t/data/tap/bailout000444001750001750 7411710110733 20351 0ustar00grahamgraham0000000000001..5 ok 1 ok 2 ok 3 Bail out! GERONIMMMOOOOOO!!! ok 4 ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/empty000444001750001750 011710110733 20015 0ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/simple_yaml000444001750001750 32011710110733 21237 0ustar00grahamgraham000000000000TAP version 13 1..5 ok 1 ok 2 --- - fnurk: skib ponk: gleeb - bar: krup foo: plink ... ok 3 ok 4 --- expected: - 1 - 2 - 4 got: - 1 - pong - 4 ... ok 5 TAP-Formatter-JUnit-0.09/t/data/tap/stdout_stderr000444001750001750 12211710110733 21631 0ustar00grahamgraham000000000000# comments ok 1 ok 2 ok 3 # comment ok 4 # more ignored stuff # and yet more 1..4 TAP-Formatter-JUnit-0.09/t/data/tap/todo_inline000444001750001750 22611710110733 21234 0ustar00grahamgraham0000000000001..3 not ok 1 - Foo # TODO Just testing the todo interface. ok 2 - Unexpected success # TODO Just testing the todo interface. ok 3 - This is not todo TAP-Formatter-JUnit-0.09/t/data/tap/junit000755001750001750 011710110733 20022 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/junit/no_nums000444001750001750 101611710110733 21556 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/descriptive_trailing000444001750001750 120111710110733 24306 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/die_unfinished000444001750001750 64611710110733 23045 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/die000444001750001750 011710110733 20551 0ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/junit/simple000444001750001750 65411710110733 21360 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/skip000444001750001750 67711710110733 21042 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/simple_fail000444001750001750 120711710110733 22366 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/bad_chars000444001750001750 107511710110733 22013 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/die_head_end000444001750001750 66311710110733 22437 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/todo000444001750001750 101711710110733 21046 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/todo_misparse000444001750001750 74211710110733 22735 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/skipall_nomsg000444001750001750 35311710110733 22725 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/skip_nomsg000444001750001750 42711710110733 22236 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/skipall000444001750001750 36611710110733 21526 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/too_many000444001750001750 157411710110733 21736 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/die_last_minute000444001750001750 61511710110733 23231 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/descriptive000444001750001750 117011710110733 22422 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/bailout000444001750001750 71311710110733 21522 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/empty000444001750001750 011710110733 21146 0ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tap/junit/simple_yaml000444001750001750 114311710110733 22414 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/stdout_stderr000444001750001750 70411710110733 22770 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tap/junit/todo_inline000444001750001750 131211710110733 22402 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests000755001750001750 011710110733 17247 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tests/no_nums000444001750001750 6711710110733 20750 0ustar00grahamgraham000000000000print < TAP-Formatter-JUnit-0.09/t/data/tests/junit/descriptive_trailing000444001750001750 120311710110733 24666 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/die_unfinished000444001750001750 74211710110733 23420 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/die000444001750001750 51611710110733 21203 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/simple000444001750001750 65611710110733 21740 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/skip000444001750001750 70111710110733 21404 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/simple_fail000444001750001750 121111710110733 22737 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/bad_chars000444001750001750 107711710110733 22373 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/die_head_end000444001750001750 100311710110733 23022 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/todo000444001750001750 102111710110733 21417 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/todo_misparse000444001750001750 74411710110733 23315 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/skipall_nomsg000444001750001750 35511710110733 23305 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/skip_nomsg000444001750001750 43111710110733 22607 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/skipall000444001750001750 37011710110733 22077 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/too_many000444001750001750 167211710110733 22313 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/die_last_minute000444001750001750 101311710110733 23620 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/descriptive000444001750001750 117211710110733 23002 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/bailout000444001750001750 011710110733 22025 0ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/t/data/tests/junit/empty000444001750001750 40211710110733 21572 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/simple_yaml000444001750001750 114511710110733 22774 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/stdout_stderr000444001750001750 70611710110733 23350 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/t/data/tests/junit/todo_inline000444001750001750 131411710110733 22762 0ustar00grahamgraham000000000000 TAP-Formatter-JUnit-0.09/bin000755001750001750 011710110733 15501 5ustar00grahamgraham000000000000TAP-Formatter-JUnit-0.09/bin/tap2junit000555001750001750 1125411710110733 17527 0ustar00grahamgraham000000000000#!/usr/bin/perl use strict; use warnings; use TAP::Parser; use TAP::Parser::Aggregator; use TAP::Formatter::JUnit; use Getopt::Long; use Pod::Usage; use IO::File; use File::Slurp qw(slurp); ############################################################################### # Read in our command line options. my ($help, $man); my $name; my $verbose; my $suffix = '.xml'; GetOptions( 'suffix=s' => \$suffix, 'verbose' => \$verbose, 'name|junit_name=s' => \$name, 'help|?' => \$help, 'man' => \$man, ) || pod2usage(1); pod2usage(1) if ($help); pod2usage( -exitstatus=>0, -verbose=>2 ) if ($man); ############################################################################### # Get the names of all of the TAP files we're supposed to convert. my @tap_files = @ARGV; pod2usage(1) unless (@tap_files); ############################################################################### # Convert all of the TAP files to JUnit. foreach my $file (@tap_files) { verbose( "converting TAP to JUnit: $file" ); # Slurp in the TAP. my $tap = ($file eq '-') ? do { local $/; ; } : slurp($file); # Open up a FH for where we're going to dump the JUnit my $fout; if ($file eq '-') { open $fout, '>&STDOUT' || die "can't dup STDOUT; $!\n"; } else { my $junit_file = "${file}${suffix}"; $fout = IO::File->new( $junit_file, '>' ) || die "can't open '$junit_file' for writing; $!"; } # Name the test; if one wasn't provided, name it after the file itself. my $test_name = $name || $file; # Convert the TAP to JUnit eval { # Create the TAP formatter, aggregator, and parser that we're going to # use to convert the TAP to JUnit. my $formatter = TAP::Formatter::JUnit->new( { stdout => $fout } ); my $aggregator = TAP::Parser::Aggregator->new(); my $parser = TAP::Parser->new( { tap => $tap } ); # Parse all of the TAP in this file. $aggregator->start(); my $session = $formatter->open_test($test_name, $parser); while (my $result = $parser->next()) { $session->result($result); } $session->close_test(); $aggregator->add($file, $parser); $aggregator->stop(); # Summarize the results (in JUnit) $formatter->summary($aggregator); }; if ($@) { warn $@; } $fout->close(); } ############################################################################### # All done; exit peacefully. exit; sub verbose { print "$_[0]\n" if ($verbose); } =head1 NAME tap2junit - Converts TAP output to JUnit =head1 SYNOPSIS tap2junit [options] ... Options: --suffix Suffix for JUnit output files (default ".xml") --verbose Display verbose status during conversion --name Provide explicit name for the JUnit test --help/-? Display brief help message --man Display full documentation =head1 DESCRIPTION C converts TAP output to JUnit. Give it a list of files containing TAP results and it will create a series of F output files containing the JUnit representations of that TAP contained in the files. If you specify F<-> as the filename, C will read from STDIN and write to STDOUT. You may also want to use the C<--name> option to name the test explicitly (as the default name of "-" isn't going to make much sense). =head1 OPTIONS =over =item B<--suffix EsuffixE> Specifies the suffix which is appended to all of the input files, in order to generate the filename for the JUnit XML file that is being output. If you want to live dangerously and over-write your original TAP files, you can set this to "" and your original TAP files will be over-written. Defaults to F<.xml> =item B<--verbose> Display verbose status information during the conversion (telling you which TAP file its working on). =item B<--name EnameE> Specifies an explicit name for the JUnit test. If no name is provided, a default name will be constructed based on the full path of the TAP file being processed. This option has also been aliased as C<--junit_name> to provide compatibility with a patch from Joe McMahon. =item B<--help/-?> Display brief help message. =item B<--man> Displays the full documentation. =back =head1 AUTHOR Graham TerMarsch =head1 COPYRIGHT Copyright 2008-2010, Graham TerMarsch. All Rights Reserved. This is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO L. =cut