line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package TAP::Harness::Async; |
2
|
|
|
|
|
|
|
# ABSTRACT: Asynchronous subclass for TAP::Harness |
3
|
1
|
|
|
1
|
|
751
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
4
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
32
|
|
5
|
1
|
|
|
1
|
|
663
|
use parent qw(TAP::Harness); |
|
1
|
|
|
|
|
253
|
|
|
1
|
|
|
|
|
8
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
our $VERSION = '0.001'; |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 NAME |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
TAP::Harness::Async - Run tests in a subprocess through L |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=head1 VERSION |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
version 0.001 |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
=head1 SYNOPSIS |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
use TAP::Harness::Async; |
20
|
|
|
|
|
|
|
use IO::Async::Loop; |
21
|
|
|
|
|
|
|
my $loop = IO::Async::Loop->new; |
22
|
|
|
|
|
|
|
my $harness = TAP::Harness::Async->new({ |
23
|
|
|
|
|
|
|
loop => $loop, |
24
|
|
|
|
|
|
|
}); |
25
|
|
|
|
|
|
|
$harness->runtests(@ARGV); |
26
|
|
|
|
|
|
|
$harness->on_complete(sub { $loop->later(sub { $loop->loop_stop }) }); |
27
|
|
|
|
|
|
|
$loop->loop_forever; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 DESCRIPTION |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
This is a simple test harness which does the bare minimum required to |
32
|
|
|
|
|
|
|
run the test process under L. |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
WARNING: This is an early proof-of-concept version, see examples/tickit.pl |
35
|
|
|
|
|
|
|
for a simple demonstration and please note that the API is not stable |
36
|
|
|
|
|
|
|
and may change significantly in the next version. |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=cut |
39
|
|
|
|
|
|
|
|
40
|
1
|
|
|
1
|
|
29391
|
use Carp; |
|
1
|
|
|
|
|
4
|
|
|
1
|
|
|
|
|
86
|
|
41
|
1
|
|
|
1
|
|
6
|
use TAP::Base; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1176
|
|
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
############################################################################## |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
=head1 METHODS |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=cut |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub _initialize { |
50
|
0
|
|
|
0
|
|
|
my ($self, $args) = @_; |
51
|
0
|
0
|
|
|
|
|
my $loop = delete $args->{loop} or die 'loop?'; |
52
|
0
|
|
|
|
|
|
$self->{loop} = $loop; |
53
|
0
|
|
|
|
|
|
$self->SUPER::_initialize($args); |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
sub _aggregate_parallel { |
57
|
0
|
|
|
0
|
|
|
my ( $self, $aggregate, $scheduler ) = @_; |
58
|
|
|
|
|
|
|
|
59
|
0
|
|
|
|
|
|
my $jobs = $self->jobs; |
60
|
0
|
|
|
|
|
|
my $mux = $self->_construct( $self->multiplexer_class ); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
RESULT: { |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
# Keep multiplexer topped up |
65
|
0
|
|
|
|
|
|
FILL: |
66
|
0
|
|
|
|
|
|
while ( $mux->parsers < $jobs ) { |
67
|
0
|
|
|
|
|
|
my $job = $scheduler->get_job; |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# If we hit a spinner stop filling and start running. |
70
|
0
|
0
|
0
|
|
|
|
last FILL if !defined $job || $job->is_spinner; |
71
|
|
|
|
|
|
|
|
72
|
0
|
|
|
|
|
|
my ( $parser, $session ) = $self->make_parser($job); |
73
|
0
|
|
|
|
|
|
$mux->add( $parser, [ $session, $job ] ); |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
|
if(my ($parser, $stash, $result) = $mux->next) { |
77
|
0
|
|
|
|
|
|
my ($session, $job) = @$stash; |
78
|
0
|
0
|
|
|
|
|
if(defined $result) { |
79
|
0
|
|
|
|
|
|
$session->result($result); |
80
|
0
|
0
|
|
|
|
|
$self->_bailout($result) if $result->is_bailout; |
81
|
|
|
|
|
|
|
} else { |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# End of parser. Automatically removed from the mux. |
84
|
0
|
|
|
|
|
|
$self->finish_parser( $parser, $session ); |
85
|
0
|
|
|
|
|
|
$self->_after_test( $aggregate, $job, $parser ); |
86
|
0
|
|
|
|
|
|
$job->finish; |
87
|
|
|
|
|
|
|
} |
88
|
0
|
|
|
|
|
|
redo RESULT; |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
} |
91
|
|
|
|
|
|
|
|
92
|
0
|
|
|
|
|
|
return; |
93
|
|
|
|
|
|
|
} |
94
|
|
|
|
|
|
|
|
95
|
0
|
|
|
0
|
0
|
|
sub loop { shift->{loop} } |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub _aggregate_single { |
98
|
0
|
|
|
0
|
|
|
my ( $self, $aggregate, $scheduler ) = @_; |
99
|
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
my $code; |
101
|
|
|
|
|
|
|
$code = sub { |
102
|
0
|
0
|
|
0
|
|
|
if(my $job = $scheduler->get_job ) { |
103
|
0
|
0
|
|
|
|
|
return $code->() if $job->is_spinner; |
104
|
|
|
|
|
|
|
|
105
|
0
|
|
|
|
|
|
my ( $parser, $session ) = $self->make_parser($job); |
106
|
0
|
|
|
|
|
|
my $it = $parser->_iterator; |
107
|
|
|
|
|
|
|
$it->{on_line} = sub { |
108
|
0
|
|
|
|
|
|
my ($line) = @_; |
109
|
0
|
|
0
|
|
|
|
while ($it->lines && defined( my $result = $parser->next ) ) { |
110
|
0
|
|
|
|
|
|
$session->result($result); |
111
|
0
|
0
|
|
|
|
|
if ( $result->is_bailout ) { |
112
|
0
|
|
|
|
|
|
$self->_bailout($result); |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
} |
115
|
0
|
|
|
|
|
|
}; |
116
|
|
|
|
|
|
|
$it->{on_finished} = sub { |
117
|
0
|
|
|
|
|
|
$self->finish_parser( $parser, $session ); |
118
|
0
|
|
|
|
|
|
$self->_after_test( $aggregate, $job, $parser ); |
119
|
0
|
|
|
|
|
|
$job->finish; |
120
|
0
|
|
|
|
|
|
$self->loop->later($code); |
121
|
|
|
|
|
|
|
}, |
122
|
0
|
|
|
|
|
|
} else { |
123
|
0
|
0
|
|
|
|
|
$self->{on_tests_complete}->($self, $aggregate) if exists $self->{on_tests_complete}; |
124
|
|
|
|
|
|
|
} |
125
|
0
|
|
|
|
|
|
}; |
126
|
0
|
|
|
|
|
|
$code->(); |
127
|
0
|
|
|
|
|
|
return; |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub runtests { |
131
|
0
|
|
|
0
|
1
|
|
my ( $self, @tests ) = @_; |
132
|
|
|
|
|
|
|
|
133
|
0
|
|
|
|
|
|
my $aggregate = $self->_construct( $self->aggregator_class ); |
134
|
|
|
|
|
|
|
|
135
|
0
|
|
|
|
|
|
$self->_make_callback( 'before_runtests', $aggregate ); |
136
|
0
|
|
|
|
|
|
$aggregate->start; |
137
|
|
|
|
|
|
|
my $finish = sub { |
138
|
0
|
|
|
0
|
|
|
my $interrupted = shift; |
139
|
0
|
|
|
|
|
|
$aggregate->stop; |
140
|
0
|
|
|
|
|
|
$self->summary( $aggregate, $interrupted ); |
141
|
0
|
|
|
|
|
|
$self->_make_callback( 'after_runtests', $aggregate ); |
142
|
0
|
|
|
|
|
|
}; |
143
|
|
|
|
|
|
|
my $run = sub { |
144
|
0
|
|
|
0
|
|
|
$self->{on_tests_complete} = sub { $finish->(0) }; |
|
0
|
|
|
|
|
|
|
145
|
0
|
|
|
|
|
|
$self->aggregate_tests( $aggregate, @tests ); |
146
|
0
|
|
|
|
|
|
}; |
147
|
|
|
|
|
|
|
|
148
|
0
|
0
|
|
|
|
|
if ( $self->trap ) { |
149
|
|
|
|
|
|
|
local $SIG{INT} = sub { |
150
|
0
|
|
|
0
|
|
|
print "\n"; |
151
|
0
|
|
|
|
|
|
$finish->(1); |
152
|
0
|
|
|
|
|
|
exit; |
153
|
0
|
|
|
|
|
|
}; |
154
|
0
|
|
|
|
|
|
$run->(); |
155
|
|
|
|
|
|
|
} |
156
|
|
|
|
|
|
|
else { |
157
|
0
|
|
|
|
|
|
$run->(); |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
0
|
|
|
|
|
|
return $aggregate; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head2 on_complete |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
Accessor for code to run on test completion. |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
=cut |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
sub on_complete { |
170
|
0
|
|
|
0
|
1
|
|
my ($self, $code) = @_; |
171
|
0
|
0
|
|
|
|
|
if($code) { |
172
|
0
|
|
|
|
|
|
$self->{on_complete} = $code; |
173
|
0
|
|
|
|
|
|
return $self; |
174
|
|
|
|
|
|
|
} |
175
|
0
|
|
|
|
|
|
return $self->{on_complete}; |
176
|
|
|
|
|
|
|
} |
177
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
1; |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
__END__ |