File Coverage

blib/lib/TAP/Formatter/BambooExtended.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package TAP::Formatter::BambooExtended;
2              
3 1     1   24464 use strict;
  1         3  
  1         51  
4 1     1   8 use warnings;
  1         2  
  1         52  
5              
6 1     1   822 use parent qw(TAP::Formatter::Console);
  1         387  
  1         7  
7              
8 1     1   21987 use XML::LibXML;
  0            
  0            
9             use Encode qw(is_utf8 decode);
10             use HTML::Entities qw(encode_entities);
11             use Cwd ();
12             use File::Path ();
13              
14             use TAP::Formatter::BambooExtended::Session;
15              
16             our $VERSION = '1.01';
17              
18             sub _initialize {
19             my ($self, $arg_for) = @_;
20              
21             # variables that we use for ourselves
22             $self->{'_test_results'} = [];
23              
24             return $self->SUPER::_initialize($arg_for || {});
25             }
26              
27             sub add_test_results {
28             my ($self, $results) = @_;
29             push(@{$self->{'_test_results'}}, $results) if defined($results);
30             return;
31             }
32              
33             sub open_test {
34             my ($self, $test, $parser) = @_;
35             my $session = TAP::Formatter::BambooExtended::Session->new({
36             'name' => $test,
37             'formatter' => $self,
38             'parser' => $parser,
39             });
40             return $session;
41             }
42              
43             sub summary {
44             my ($self, $aggregate) = @_;
45              
46             my $output_path = Cwd::cwd() . "/prove_db";
47             $output_path = $ENV{'FORMATTER_OUTPUT_DIR'} if defined($ENV{'FORMATTER_OUTPUT_DIR'});
48             File::Path::make_path($output_path) unless (-e $output_path);
49              
50             for my $test (@{$self->{'_test_results'}}) {
51             my $test_name = $test->{'description'};
52             $test_name =~ s/^[\.\/\\]+//g;
53             $test_name =~ s/\/|\\/-/g;
54             $test_name =~ s/\./_/g;
55             $self->_save_results($test, "${output_path}/${test_name}.xml");
56             }
57              
58             return if $self->silent();
59              
60             print { $self->stdout } "ALL DONE\n";
61             return;
62             }
63              
64             sub _save_results {
65             my ($self, $test, $file_path) = @_;
66             my $doc = XML::LibXML::Document->createDocument('1.0', 'UTF-8');
67              
68             my $testsuite_name = $test->{'description'};
69             $testsuite_name =~ s/^[\.\/\\]+//g;
70             $testsuite_name =~ s/\/|\\/-/g;
71             $testsuite_name =~ s/\./_/g;
72             $testsuite_name =~ s/^\s+|\s+$//g;
73              
74             my $suite = $doc->createElement('testsuite');
75             $suite->setAttribute('name', $testsuite_name);
76             $suite->setAttribute('errors', $test->{'parse_errors'});
77             $suite->setAttribute('failures', $test->{'failed'});
78             $suite->setAttribute('tests', $test->{'tests_run'});
79             $suite->setAttribute('time', $test->{'end_time'} - $test->{'start_time'});
80              
81             my $skipped = 0;
82             for my $result (@{$test->{'results'}}) {
83             next unless ($result->is_test() || $result->is_bailout());
84              
85             # bump the skip count?
86             ++$skipped if ($result->has_skip());
87              
88             # give it a name if there isn't one
89             my $testcase_name = $result->description();
90              
91             # clean up invalid characters
92             $testcase_name = decode("UTF-8", $testcase_name) unless is_utf8($testcase_name);
93             $testcase_name = encode_entities($testcase_name);
94              
95             # trim trailing/leading space
96             $testcase_name =~ s/^\s+|\s+$//g;
97              
98             my $testcase = $doc->createElement('testcase');
99             $testcase->setAttribute('name', $testcase_name || "test ${\$result->number()}");
100              
101             my @fail_reasons = _fail_reasons($result);
102             if (scalar(@fail_reasons)) {
103             my $failure = $doc->createElement('failure');
104             my $fail_description = '';
105              
106             $fail_description .= "Fail reason(s):\n";
107             for my $fail (@fail_reasons) {
108             $fail_description .= " $fail\n";
109             }
110              
111             my $explanation = $result->explanation();
112             if (defined($explanation)) {
113             chomp($explanation);
114             $explanation =~ s/^\s+|\s+$//g;
115             $explanation = decode("UTF-8", $explanation) unless is_utf8($explanation);
116             $fail_description .= "Explanation:\n " . $explanation . "\n" if ($explanation);
117             }
118              
119             my $output = $result->raw();
120             if (defined($output)) {
121             chomp($output);
122             $output =~ s/^\s+|\s+$//g;
123             $output = decode("UTF-8", $output) unless is_utf8($output);
124             $fail_description .= "Test output:\n " . $output . "\n" if ($output);
125             }
126              
127             $failure->appendChild(XML::LibXML::CDATASection->new($fail_description));
128             $testcase->appendChild($failure);
129             }
130              
131             $suite->appendChild($testcase);
132             }
133              
134             $suite->setAttribute('skipped', $skipped);
135             $doc->setDocumentElement($suite);
136             $doc->toFile($file_path, 2);
137              
138             return;
139             }
140              
141             sub _fail_reasons {
142             my $result = shift;
143             my @reasons = ();
144              
145             if (!$result->is_actual_ok()) {
146             push(@reasons, "failed test");
147             }
148             if ($result->todo_passed()) {
149             push(@reasons, "unexpected TODO passed");
150             }
151             if ($result->is_unplanned()) {
152             push(@reasons, "unplanned test");
153             }
154              
155             return wantarray ? @reasons : \@reasons;
156             }
157              
158             1;
159              
160             =encoding utf8
161              
162             =head1 NAME
163              
164             TAP::Formatter::BambooExtended - Harness output delegate for Atlassian's Bamboo CI server
165              
166             =head1 SYNOPSIS
167              
168             On the command line, with F:
169              
170             prove --formatter TAP::Formatter::BambooExtended ...
171              
172             Or, in your own scripts:
173              
174             use TAP::Harness;
175             my $harness = TAP::Harness->new({
176             formatter_class => 'TAP::Formatter::BambooExtended',
177             merge => 1,
178             });
179             $harness->runtests(@tests);
180              
181             =head1 DESCRIPTION
182              
183             C provides JUnit output formatting for C,
184             which can be used in Atlassian's Bamboo CI server or any other CI server that
185             looks for JUnit files.
186              
187             This module is based on TAP::Formatter::Bamboo by Piotr Piatkowski
188             , main differences are:
189              
190             =over
191              
192             =item Resulting XML is saved as one output file per source test script.
193              
194             =item Each test gets its own result line in the JUnit output rather than
195             grouping all the tests from one test script into one result.
196              
197             =item A summary test result is appended to indicate if there were any problems
198             with the test script itself outside of individual tests.
199              
200             =item Output of failed tests are attached to the test that failed AND the test
201             script itself. Each test script will create one JUnit compatible test result
202             file. The test result file names will match the full path and file name of the
203             test script. By default these files are created in a directory called
204             C that is created in your current working directory. This can be
205             changed by setting the environment variable C to a
206             relative or absolute path.
207              
208             =back
209              
210             By way of example, when you run a test like this:
211              
212             prove -l --formatter TAP::Formatter::BambooExtended
213              
214             You might see these results on the command line:
215              
216             PASS t/00-load.t
217             ALL DONE
218              
219             Then you'll see a new directory called C<$ENV{'FORMATTER_OUTPUT_DIR'}>. By
220             default, this directory will be created as C in your current working
221             directory. In the output directory you'll see one file for each test script,
222             like this:
223              
224             > ls
225             t-00-load_t.xml
226              
227             In that file you will see one test output for the file itself, named after the
228             file. You'll also see one test output for each individual test in the test
229             script. So if your test script has twenty C statements, you'll have twenty-
230             one tests in Bamboo -- one for the file itself and then one for each C
231             statement. This makes it easier to track exactly which tests are failing with
232             Bamboo.
233              
234             =head1 AUTHOR
235              
236             Paul Lockaby
237              
238             Piotr Piatkowski (original C)
239              
240             Graham TerMarsch (original C)
241              
242             =head1 COPYRIGHT
243              
244             This is free software; you can redistribute it and/or modify it under the same
245             terms as Perl itself.
246              
247             =head1 SEE ALSO
248              
249             L,
250             L,
251             L,
252             L.
253              
254             =cut