line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Test2::Harness::Renderer::JUnit; |
2
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Test2::Harness itself requires 5.10. |
4
|
1
|
|
|
1
|
|
84949
|
use 5.010000; |
|
1
|
|
|
|
|
4
|
|
5
|
1
|
|
|
1
|
|
4
|
use strict; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
17
|
|
6
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
46
|
|
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
our $VERSION = '1.000000'; |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
# This is used frequently during development to determine what different events look like so we can determine how to capture test data. |
11
|
1
|
|
|
1
|
|
537
|
use Data::Dumper; |
|
1
|
|
|
|
|
6728
|
|
|
1
|
|
|
|
|
77
|
|
12
|
|
|
|
|
|
|
$Data::Dumper::Sortkeys = 1; |
13
|
|
|
|
|
|
|
|
14
|
1
|
|
|
1
|
|
7
|
use File::Spec; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
18
|
|
15
|
1
|
|
|
1
|
|
4
|
use POSIX (); |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
15
|
|
16
|
1
|
|
|
1
|
|
545
|
use Storable qw/dclone/; |
|
1
|
|
|
|
|
2832
|
|
|
1
|
|
|
|
|
54
|
|
17
|
1
|
|
|
1
|
|
529
|
use XML::Generator (); |
|
1
|
|
|
|
|
8857
|
|
|
1
|
|
|
|
|
26
|
|
18
|
1
|
|
|
1
|
|
6
|
use Carp (); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
38
|
|
19
|
|
|
|
|
|
|
|
20
|
1
|
|
|
1
|
|
399
|
BEGIN { require Test2::Harness::Renderer; our @ISA = ('Test2::Harness::Renderer') } |
|
1
|
|
|
|
|
2224
|
|
21
|
1
|
|
|
|
|
6
|
use Test2::Harness::Util::HashBase qw{ |
22
|
|
|
|
|
|
|
-io -io_err |
23
|
|
|
|
|
|
|
-formatter |
24
|
|
|
|
|
|
|
-show_run_info |
25
|
|
|
|
|
|
|
-show_job_info |
26
|
|
|
|
|
|
|
-show_job_launch |
27
|
|
|
|
|
|
|
-show_job_end |
28
|
|
|
|
|
|
|
-times |
29
|
1
|
|
|
1
|
|
7
|
}; |
|
1
|
|
|
|
|
1
|
|
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
sub init { |
32
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
33
|
|
|
|
|
|
|
|
34
|
0
|
|
|
|
|
|
my $settings = $self->{ +SETTINGS }; |
35
|
|
|
|
|
|
|
|
36
|
0
|
|
|
|
|
|
$self->{'xml'} = XML::Generator->new( ':pretty', ':std', 'escape' => 'always,high-bit,even-entities', 'encoding' => 'UTF-8' ); |
37
|
|
|
|
|
|
|
|
38
|
0
|
|
|
|
|
|
$self->{'xml_content'} = []; |
39
|
|
|
|
|
|
|
|
40
|
0
|
0
|
|
|
|
|
$self->{'allow_passing_todos'} = $ENV{'ALLOW_PASSING_TODOS'} ? 1 : 0; |
41
|
0
|
|
0
|
|
|
|
$self->{'junit_file'} //= $ENV{'JUNIT_TEST_FILE'} || 'junit.xml'; |
|
|
|
0
|
|
|
|
|
42
|
|
|
|
|
|
|
|
43
|
0
|
|
|
|
|
|
$self->{'tests'} = {}; # We need a pointer to each test so we know where to go for each event. |
44
|
|
|
|
|
|
|
} |
45
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# This sub is called for every Harness event. We capture the data we need so we can emit the appropriate junit file. |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub render_event { |
49
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
50
|
0
|
|
|
|
|
|
my ($event) = @_; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# We modify the event, which would be bad if there were multiple renderers, |
53
|
|
|
|
|
|
|
# so we deep clone it. |
54
|
0
|
|
|
|
|
|
$event = dclone($event); |
55
|
0
|
|
|
|
|
|
my $f = $event->{facet_data}; |
56
|
0
|
|
|
|
|
|
my $job = $f->{harness_job}; |
57
|
0
|
0
|
|
|
|
|
my $job_id = $f->{'harness'}->{'job_id'} or return; |
58
|
0
|
|
0
|
|
|
|
my $job_try = $f->{'harness'}->{'job_try'} // 0; |
59
|
0
|
|
|
|
|
|
my $stamp = $event->{'stamp'}; |
60
|
|
|
|
|
|
|
|
61
|
0
|
0
|
|
|
|
|
if ( !defined $stamp ) { |
62
|
0
|
|
0
|
|
|
|
$f //= 'unknown facet_data'; |
63
|
0
|
|
|
|
|
|
die "No time stamp found for event '$f' ?!?!?!? ...\n" . "Event:\n" . Dumper($event) . "\n" . Carp::longmess(); |
64
|
|
|
|
|
|
|
} |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
# Throw out job events if they are for a previous run and we've already started collecting job |
67
|
|
|
|
|
|
|
# information for a successive run. |
68
|
0
|
0
|
0
|
|
|
|
return if $self->{'tests'}->{$job_id} && $job_try < ( $self->{'tests'}->{$job_id}->{'job_try'} // 0 ); |
|
|
|
0
|
|
|
|
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# At job launch we need to start collecting a new junit testdata section. |
71
|
|
|
|
|
|
|
# We throw out anything we've collected to date on a previous run. |
72
|
0
|
0
|
|
|
|
|
if ( $f->{'harness_job_launch'} ) { |
73
|
0
|
|
|
|
|
|
my $full_test_name = $job->{'file'}; |
74
|
0
|
|
|
|
|
|
my $test_file = File::Spec->abs2rel($full_test_name); |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
$self->{'tests'}->{$job_id} = { |
77
|
|
|
|
|
|
|
'name' => $job->{'file'}, |
78
|
|
|
|
|
|
|
'file' => _squeaky_clean($test_file), |
79
|
|
|
|
|
|
|
'job_id' => $job_id, |
80
|
|
|
|
|
|
|
'job_try' => $job_try, |
81
|
0
|
|
|
|
|
|
'job_name' => $f->{'harness_job'}->{'job_name'}, |
82
|
|
|
|
|
|
|
'testcase' => [], |
83
|
|
|
|
|
|
|
'system-out' => '', |
84
|
|
|
|
|
|
|
'system-err' => '', |
85
|
|
|
|
|
|
|
'start' => $stamp, |
86
|
|
|
|
|
|
|
'last_job_start' => $stamp, |
87
|
|
|
|
|
|
|
'testsuite' => { |
88
|
|
|
|
|
|
|
'errors' => 0, |
89
|
|
|
|
|
|
|
'failures' => 0, |
90
|
|
|
|
|
|
|
'tests' => 0, |
91
|
|
|
|
|
|
|
'name' => _get_testsuite_name($test_file), |
92
|
|
|
|
|
|
|
'id' => $job_id, # add a UID in the XML output |
93
|
|
|
|
|
|
|
}, |
94
|
|
|
|
|
|
|
}; |
95
|
|
|
|
|
|
|
|
96
|
0
|
|
|
|
|
|
return; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
0
|
|
|
|
|
|
my $test = $self->{'tests'}->{$job_id}; |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# We have all the data. Print the XML. |
102
|
0
|
0
|
|
|
|
|
if ( $f->{'harness_job_end'} ) { |
103
|
0
|
|
|
|
|
|
$self->close_open_failure_testcase( $test, -1 ); |
104
|
0
|
|
|
|
|
|
$test->{'stop'} = $event->{'stamp'}; |
105
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'time'} = $test->{'stop'} - $test->{'start'}; |
106
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'timestamp'} = _timestamp( $test->{'start'} ); |
107
|
|
|
|
|
|
|
|
108
|
0
|
0
|
|
|
|
|
if ( $f->{'errors'} ) { |
109
|
0
|
|
|
|
|
|
my $test_error_messages = ''; |
110
|
0
|
|
|
|
|
|
foreach my $msg ( @{ $f->{'errors'} } ) { |
|
0
|
|
|
|
|
|
|
111
|
0
|
0
|
|
|
|
|
next unless $msg->{'from_harness'}; |
112
|
0
|
0
|
0
|
|
|
|
next unless $msg->{'tag'} // '' eq 'REASON'; |
113
|
|
|
|
|
|
|
|
114
|
0
|
0
|
|
|
|
|
if ( $msg->{details} =~ m/^Planned for ([0-9]+) assertions?, but saw ([0-9]+)/ ) { |
115
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'errors'} += abs( $1 - $2 ); |
116
|
|
|
|
|
|
|
} |
117
|
0
|
0
|
|
|
|
|
next if $msg->{details} =~ m/Test script returned error/; |
118
|
0
|
0
|
|
|
|
|
next if $msg->{details} =~ m/Assertion failures were encountered/; |
119
|
0
|
0
|
|
|
|
|
next if $msg->{details} =~ m/Subtest failures were encountered/; |
120
|
|
|
|
|
|
|
|
121
|
0
|
|
|
|
|
|
$test_error_messages .= "$msg->{details}\n"; |
122
|
|
|
|
|
|
|
} |
123
|
0
|
0
|
|
|
|
|
if ($test_error_messages) { |
124
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( |
125
|
0
|
|
|
|
|
|
{ 'name' => "Test Plan Failure", 'time' => $stamp - $test->{'last_job_start'}, 'classname' => $test->{'testsuite'}->{'name'} }, |
126
|
|
|
|
|
|
|
$self->xml->failure($test_error_messages) |
127
|
|
|
|
|
|
|
); |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
} |
130
|
|
|
|
|
|
|
|
131
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( |
132
|
0
|
|
|
|
|
|
{ 'name' => "Tear down.", 'time' => $stamp - $test->{'last_job_start'}, 'classname' => $test->{'testsuite'}->{'name'} }, |
133
|
|
|
|
|
|
|
); |
134
|
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
return; |
136
|
|
|
|
|
|
|
} |
137
|
|
|
|
|
|
|
|
138
|
0
|
0
|
|
|
|
|
if ( $f->{'plan'} ) { |
139
|
0
|
0
|
|
|
|
|
if ( $f->{'plan'}->{'skip'} ) { |
140
|
0
|
|
|
|
|
|
my $skip = $f->{'plan'}->{'details'}; |
141
|
0
|
|
|
|
|
|
$test->{'system-out'} .= "# SKIP $skip\n"; |
142
|
|
|
|
|
|
|
} |
143
|
0
|
0
|
|
|
|
|
if ( $f->{'plan'}->{'count'} ) { |
144
|
0
|
|
|
|
|
|
$test->{'plan'} = $f->{'plan'}->{'count'}; |
145
|
|
|
|
|
|
|
} |
146
|
|
|
|
|
|
|
|
147
|
0
|
|
|
|
|
|
return; |
148
|
|
|
|
|
|
|
} |
149
|
|
|
|
|
|
|
|
150
|
0
|
0
|
|
|
|
|
if ( $f->{'harness_job_exit'} ) { |
151
|
0
|
0
|
|
|
|
|
return unless $f->{'harness_job_exit'}->{'exit'}; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
# If we don't see |
154
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'errors'}++; |
155
|
0
|
|
0
|
|
|
|
$test->{'error-msg'} //= $f->{'harness_job_exit'}->{'details'} . "\n"; |
156
|
|
|
|
|
|
|
|
157
|
0
|
|
|
|
|
|
return; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
# We just hit an ok/not ok line. |
161
|
0
|
0
|
|
|
|
|
if ( $f->{'assert'} ) { |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# Ignore subtests |
164
|
0
|
0
|
0
|
|
|
|
return if ( $f->{'hubs'} && $f->{'hubs'}->[0]->{'nested'} ); |
165
|
|
|
|
|
|
|
|
166
|
0
|
|
0
|
|
|
|
my $test_num = sprintf( "%04d", $event->{'assert_count'} || $f->{'assert'}->{'number'} || die Dumper $event); |
167
|
0
|
|
0
|
|
|
|
my $test_name = _squeaky_clean( $f->{'assert'}->{'details'} // 'UNKNOWN_TEST?' ); |
168
|
0
|
0
|
|
|
|
|
my $test_string = defined $test_name ? "$test_num - $test_name" : $test_num; |
169
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'tests'}++; |
170
|
|
|
|
|
|
|
|
171
|
0
|
|
|
|
|
|
$self->close_open_failure_testcase( $test, $test_num ); |
172
|
|
|
|
|
|
|
|
173
|
0
|
0
|
|
|
|
|
warn Dumper $event unless $stamp; |
174
|
|
|
|
|
|
|
|
175
|
0
|
|
|
|
|
|
my $run_time = $stamp - $test->{'last_job_start'}; |
176
|
0
|
|
|
|
|
|
$test->{'last_job_start'} = $stamp; |
177
|
|
|
|
|
|
|
|
178
|
0
|
0
|
0
|
|
|
|
if ( $f->{'amnesty'} && grep { ( $_->{'tag'} // '' ) eq 'TODO' } @{ $f->{'amnesty'} } ) { # All TODO Tests |
|
0
|
0
|
0
|
|
|
|
|
|
0
|
|
|
|
|
|
|
179
|
0
|
0
|
|
|
|
|
if ( !$f->{'assert'}->{'pass'} ) { # Failing TODO |
|
|
0
|
|
|
|
|
|
180
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( { 'name' => "$test_string (TODO)", 'time' => $run_time, 'classname' => $test->{'testsuite'}->{'name'} }, "" ); |
|
0
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
} |
182
|
|
|
|
|
|
|
elsif ( $self->{'allow_passing_todos'} ) { # junit parsers don't like passing TODO tests. Let's just not tell them about it if $ENV{ALLOW_PASSING_TODOS} is set. |
183
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( { 'name' => "$test_string (PASSING TODO)", 'time' => $run_time, 'classname' => $test->{'testsuite'}->{'name'} }, "" ); |
|
0
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
} |
185
|
|
|
|
|
|
|
else { # Passing TODO (Failure) when not allowed. |
186
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'failures'}++; |
188
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'errors'}++; |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
# Grab the first amnesty description that's a TODO message. |
191
|
0
|
|
0
|
|
|
|
my ($todo_message) = map { $_->{'details'} } grep { $_->{'tag'} // '' eq 'TODO' } @{ $f->{'amnesty'} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( |
194
|
0
|
|
|
|
|
|
{ 'name' => "$test_string (TODO)", 'time' => $run_time, 'classname' => $test->{'testsuite'}->{'name'} }, |
195
|
|
|
|
|
|
|
$self->xml->error( |
196
|
|
|
|
|
|
|
{ 'message' => $todo_message, 'type' => "TodoTestSucceeded" }, |
197
|
|
|
|
|
|
|
$self->_cdata("ok $test_string") |
198
|
|
|
|
|
|
|
) |
199
|
|
|
|
|
|
|
); |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
} |
202
|
|
|
|
|
|
|
} |
203
|
|
|
|
|
|
|
elsif ( $f->{'assert'}->{'pass'} ) { # Passing test |
204
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $self->xml->testcase( |
205
|
0
|
|
|
|
|
|
{ 'name' => "$test_string", 'time' => $run_time, 'classname' => $test->{'testsuite'}->{'name'} }, |
206
|
|
|
|
|
|
|
"" |
207
|
|
|
|
|
|
|
); |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
else { # Failing Test. |
210
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'failures'}++; |
211
|
0
|
|
|
|
|
|
$test->{'testsuite'}->{'errors'}++; |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Trap the test information. We can't generate the XML for this test until we get all the diag information. |
214
|
0
|
|
|
|
|
|
$test->{'last_failure'} = { |
215
|
|
|
|
|
|
|
'test_num' => $test_num, |
216
|
|
|
|
|
|
|
'test_name' => $test_name, |
217
|
|
|
|
|
|
|
'time' => $run_time, |
218
|
|
|
|
|
|
|
'message' => "not ok $test_string\n", |
219
|
|
|
|
|
|
|
}; |
220
|
|
|
|
|
|
|
} |
221
|
|
|
|
|
|
|
|
222
|
0
|
|
|
|
|
|
return; |
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
0
|
0
|
0
|
|
|
|
if ( $f->{'info'} && $test->{'last_failure'} ) { |
226
|
0
|
|
|
|
|
|
foreach my $line ( @{ $f->{'info'} } ) { |
|
0
|
|
|
|
|
|
|
227
|
0
|
0
|
|
|
|
|
next unless $line->{'details'}; |
228
|
0
|
|
|
|
|
|
chomp $line->{'details'}; |
229
|
0
|
|
|
|
|
|
$test->{'last_failure'}->{'message'} .= "# $line->{details}\n"; |
230
|
|
|
|
|
|
|
} |
231
|
0
|
|
|
|
|
|
return; |
232
|
|
|
|
|
|
|
} |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
# This is called when the last run is complete and we're ready to emit the junit file. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
sub finish { |
239
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
240
|
|
|
|
|
|
|
|
241
|
0
|
0
|
|
|
|
|
open( my $fh, '>:encoding(UTF-8)', $self->{'junit_file'} ) or die("Can't open '$self->{junit_file}' ($!)"); |
242
|
|
|
|
|
|
|
|
243
|
0
|
|
|
|
|
|
my $xml = $self->xml; |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# These are method calls but you can't do methods with a dash in them so we have to store them as a SV and call it. |
246
|
0
|
|
|
|
|
|
my $out_method = 'system-out'; |
247
|
0
|
|
|
|
|
|
my $err_method = 'system-err'; |
248
|
|
|
|
|
|
|
|
249
|
0
|
|
|
|
|
|
print {$fh} "\n"; |
|
0
|
|
|
|
|
|
|
250
|
0
|
|
|
|
|
|
my @jobs = sort { $a->{'job_name'} <=> $b->{'job_name'} } values %{ $self->{'tests'} }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
251
|
0
|
|
|
|
|
|
foreach my $job (@jobs) { |
252
|
0
|
|
|
|
|
|
print {$fh} $xml->testsuite( |
253
|
|
|
|
|
|
|
$job->{'testsuite'}, |
254
|
0
|
|
|
|
|
|
@{ $job->{'testcase'} }, |
255
|
|
|
|
|
|
|
$xml->$out_method( $self->_cdata( $job->{$out_method} ) ), |
256
|
0
|
|
|
|
|
|
$xml->$err_method( $self->_cdata( $job->{$err_method} ) ), |
257
|
|
|
|
|
|
|
) . "\n"; |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
0
|
|
|
|
|
|
print {$fh} "\n"; |
|
0
|
|
|
|
|
|
|
261
|
0
|
|
|
|
|
|
close $fh; |
262
|
|
|
|
|
|
|
|
263
|
0
|
|
|
|
|
|
return; |
264
|
|
|
|
|
|
|
} |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
# Because we want to test diag messages after a failed test, we delay closing failures |
267
|
|
|
|
|
|
|
# until we see the end of the testcase or until we see a new test number. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub close_open_failure_testcase { |
270
|
0
|
|
|
0
|
1
|
|
my ( $self, $test, $new_test_number ) = @_; |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
# Need to handle failed TODOs |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
# The last test wasn't a fail. |
275
|
0
|
0
|
|
|
|
|
return unless $test->{'last_failure'}; |
276
|
|
|
|
|
|
|
|
277
|
0
|
|
|
|
|
|
my $fail = $test->{'last_failure'}; |
278
|
0
|
0
|
|
|
|
|
if ( $fail->{'test_num'} == $new_test_number ) { |
279
|
0
|
|
|
|
|
|
die("The same assert number ($new_test_number) was seen twice for $test->{name}"); |
280
|
|
|
|
|
|
|
} |
281
|
|
|
|
|
|
|
|
282
|
0
|
|
|
|
|
|
my $xml = $self->xml; |
283
|
0
|
|
|
|
|
|
push @{ $test->{'testcase'} }, $xml->testcase( |
284
|
|
|
|
|
|
|
{ 'name' => "$fail->{test_num} - $fail->{test_name}", 'time' => $fail->{'time'}, 'classname' => $test->{'testsuite'}->{'name'} }, |
285
|
|
|
|
|
|
|
$xml->failure( |
286
|
|
|
|
|
|
|
{ 'message' => "not ok $fail->{test_num} - $fail->{test_name}", 'type' => 'TestFailed' }, |
287
|
0
|
|
|
|
|
|
$self->_cdata( $fail->{'message'} ) |
288
|
|
|
|
|
|
|
) |
289
|
|
|
|
|
|
|
); |
290
|
|
|
|
|
|
|
|
291
|
0
|
|
|
|
|
|
delete $test->{'last_failure'}; |
292
|
0
|
|
|
|
|
|
return; |
293
|
|
|
|
|
|
|
} |
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
sub xml { |
296
|
0
|
|
|
0
|
1
|
|
my $self = shift; |
297
|
0
|
|
|
|
|
|
return $self->{'xml'}; |
298
|
|
|
|
|
|
|
} |
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
# These helpers were borrowed from https://metacpan.org/pod/TAP::Formatter::JUnit. Thanks! |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
############################################################################### |
303
|
|
|
|
|
|
|
# Generates the name for the entire test suite. |
304
|
|
|
|
|
|
|
sub _get_testsuite_name { |
305
|
0
|
|
|
0
|
|
|
my $name = shift; |
306
|
0
|
|
|
|
|
|
$name =~ s{^\./}{}; |
307
|
0
|
|
|
|
|
|
$name =~ s{^t/}{}; |
308
|
0
|
|
|
|
|
|
return _clean_to_java_class_name($name); |
309
|
|
|
|
|
|
|
} |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
############################################################################### |
312
|
|
|
|
|
|
|
# Cleans up the given string, removing any characters that aren't suitable for |
313
|
|
|
|
|
|
|
# use in a Java class name. |
314
|
|
|
|
|
|
|
sub _clean_to_java_class_name { |
315
|
0
|
|
|
0
|
|
|
my $str = shift; |
316
|
0
|
|
|
|
|
|
$str =~ s/[^-:_A-Za-z0-9]+/_/gs; |
317
|
0
|
|
|
|
|
|
return $str; |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
############################################################################### |
321
|
|
|
|
|
|
|
# Creates a CDATA block for the given data (which is made squeaky clean first, |
322
|
|
|
|
|
|
|
# so that JUnit parsers like Hudson's don't choke). |
323
|
|
|
|
|
|
|
sub _cdata { |
324
|
0
|
|
|
0
|
|
|
my ( $self, $data ) = @_; |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# When I first added this conditional, I returned $data and at one point it was returning ^A and breaking the xml parser. |
327
|
0
|
0
|
0
|
|
|
|
return '' if ( !$data or $data !~ m/\S/ms ); |
328
|
|
|
|
|
|
|
|
329
|
0
|
|
|
|
|
|
return $self->xml->xmlcdata( _squeaky_clean($data) ); |
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
############################################################################### |
333
|
|
|
|
|
|
|
# Clean a string to the point that JUnit can't possibly have a problem with it. |
334
|
|
|
|
|
|
|
sub _squeaky_clean { |
335
|
0
|
|
|
0
|
|
|
my $string = shift; |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# control characters (except CR and LF) |
338
|
0
|
|
|
|
|
|
$string =~ s/([\x00-\x09\x0b\x0c\x0e-\x1f])/"^".chr(ord($1)+64)/ge; |
|
0
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# high-byte characters |
341
|
0
|
|
|
|
|
|
$string =~ s/([\x7f-\xff])/'[\\x'.sprintf('%02x',ord($1)).']'/ge; |
|
0
|
|
|
|
|
|
|
342
|
0
|
|
|
|
|
|
return $string; |
343
|
|
|
|
|
|
|
} |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
sub _timestamp { |
346
|
0
|
|
|
0
|
|
|
my $time = shift; |
347
|
0
|
|
|
|
|
|
return POSIX::strftime( '%Y-%m-%dT%H:%M:%S', localtime( int($time) ) ); |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
1; |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
__END__ |