File Coverage

blib/lib/TAP/Formatter/BambooExtended.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


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