File Coverage

blib/lib/Test/Class/Moose/Executor/Parallel.pm
Criterion Covered Total %
statement 74 77 96.1
branch 3 4 75.0
condition n/a
subroutine 21 21 100.0
pod n/a
total 98 102 96.0


line stmt bran cond sub pod time code
1             package Test::Class::Moose::Executor::Parallel;
2              
3             # ABSTRACT: Execute tests in parallel (parallelized by instance)
4              
5 11     11   275 use 5.010000;
  11         33  
6              
7             our $VERSION = '0.97';
8              
9 11     11   55 use Moose 2.0000;
  11         187  
  11         99  
10 11     11   73172 use Carp;
  11         33  
  11         715  
11 11     11   77 use namespace::autoclean;
  11         22  
  11         110  
12             with 'Test::Class::Moose::Role::Executor';
13              
14             # Needs to come before we load other test tools
15 11     11   7051 use Test2::IPC;
  11         9603  
  11         66  
16              
17 11     11   65065 use List::SomeUtils qw( none part );
  11         33  
  11         594  
18 11     11   66 use Parallel::ForkManager;
  11         22  
  11         341  
19 11     11   66 use Scalar::Util qw(reftype);
  11         583  
  11         550  
20 11     11   6083 use TAP::Formatter::Color 3.29;
  11         103257  
  11         330  
21 11     11   77 use Test2::API qw( test2_stack );
  11         22  
  11         517  
22 11     11   5258 use Test2::AsyncSubtest 0.000018 ();
  11         100100  
  11         297  
23 11     11   88 use Test::Class::Moose::AttributeRegistry;
  11         33  
  11         275  
24 11     11   5456 use Test::Class::Moose::Report::Class;
  11         55  
  11         495  
25 11     11   88 use Try::Tiny;
  11         11  
  11         8877  
26              
27             has 'jobs' => (
28             is => 'ro',
29             isa => 'Int',
30             required => 1,
31             );
32              
33             has color_output => (
34             is => 'ro',
35             isa => 'Bool',
36             default => 1,
37             );
38              
39             has show_parallel_progress => (
40             is => 'ro',
41             isa => 'Bool',
42             default => 1,
43             );
44              
45             has '_fork_manager' => (
46             is => 'ro',
47             isa => 'Parallel::ForkManager',
48             init_arg => undef,
49             lazy => 1,
50             builder => '_build_fork_manager',
51             );
52              
53             has '_subtests' => (
54             traits => ['Hash'],
55             is => 'bare',
56             isa => 'HashRef[Test2::AsyncSubtest]',
57             init_arg => sub { {} },
58             handles => {
59             _save_subtest => 'set',
60             _saved_subtest => 'get',
61             },
62             );
63              
64             has '_color' => (
65             is => 'ro',
66             isa => 'TAP::Formatter::Color',
67             lazy => 1,
68             builder => '_build_color',
69             );
70              
71             around _run_test_classes => sub {
72             my $orig = shift;
73             my $self = shift;
74             my @test_classes = @_;
75              
76             my ( $seq, $par )
77             = part { $self->_test_class_is_parallelizable($_) } @test_classes;
78              
79             $self->_run_test_classes_in_parallel($par);
80              
81             $self->$orig( @{$seq} )
82             if $seq && @{$seq};
83              
84             return;
85             };
86              
87             sub _test_class_is_parallelizable {
88 121     121   242 my ( $self, $test_class ) = @_;
89              
90             return none {
91 297     297   803 Test::Class::Moose::AttributeRegistry->method_has_tag(
92             $test_class,
93             $_,
94             'noparallel'
95             );
96             }
97 121         506 $self->_test_methods_for($test_class);
98             }
99              
100             sub _run_test_classes_in_parallel {
101 11     11   22 my $self = shift;
102 11         33 my $test_classes = shift;
103              
104 11         33 for my $test_class ( @{$test_classes} ) {
  11         33  
105 65         3763 my $subtest = Test2::AsyncSubtest->new(
106             name => $test_class,
107             hub_init_args => { manual_skip_all => 1 },
108             );
109 65         75644 my $id = $subtest->cleave;
110 65 100       4610 if ( my $pid = $self->_fork_manager->start ) {
111 55         296725 $self->_save_subtest( $pid => $subtest );
112 55         1115 next;
113             }
114              
115             # This chunk of code only runs in child processes
116 10         68948 my $class_report;
117 10         511 $subtest->attach($id);
118             $subtest->run(
119             sub {
120 10     10   2033 $class_report = $self->run_test_class($test_class);
121             }
122 10         20102 );
123 10         768 $subtest->detach;
124 10         6889 $self->_fork_manager->finish( 0, \$class_report );
125             }
126              
127 1         95 $self->_fork_manager->wait_all_children;
128 1         5837 test2_stack()->top->cull;
129              
130 1         183 return;
131             }
132              
133             sub _build_fork_manager {
134 11     11   44 my $self = shift;
135              
136 11         330 my $pfm = Parallel::ForkManager->new( $self->jobs );
137             $pfm->run_on_finish(
138             sub {
139 48     48   65176216 my ( $pid, $class_report ) = @_[ 0, 5 ];
140              
141             try {
142 48         10498 $self->test_report->add_test_class( ${$class_report} );
  48         2555  
143             }
144             catch {
145 0         0 warn $_;
146 48         2631 };
147              
148 48         3591 my $subtest = $self->_saved_subtest($pid);
149 48 50       183 unless ($subtest) {
150 0         0 warn
151             "Child process $pid ended but there is no active subtest for that pid!";
152 0         0 return;
153             }
154              
155 48         746 $subtest->finish;
156             }
157 11         46156 );
158              
159 11         418 return $pfm;
160             }
161              
162             around run_test_method => sub {
163             my $orig = shift;
164             my $self = shift;
165              
166             my $method_report = $self->$orig(@_);
167              
168             return $method_report unless $self->show_parallel_progress;
169              
170             # we're running under parallel testing, so rather than having
171             # the code look like it's stalled, we'll output a dot for
172             # every test method.
173             my ( $color, $text )
174             = $method_report->passed
175             ? ( 'green', '.' )
176             : ( 'red', 'X' );
177              
178             # The set_color() method from TAP::Formatter::Color is just ugly.
179             if ( $self->color_output ) {
180             $self->_color->set_color(
181             sub { print STDERR shift, $text },
182             $color,
183             );
184             $self->_color->set_color( sub { print STDERR shift }, 'reset' );
185             }
186             else {
187             print STDERR $text;
188             }
189              
190             return $method_report;
191             };
192              
193             sub _build_color {
194 9     9   619 return TAP::Formatter::Color->new;
195             }
196              
197             1;
198              
199             __END__
200              
201             =pod
202              
203             =encoding UTF-8
204              
205             =head1 NAME
206              
207             Test::Class::Moose::Executor::Parallel - Execute tests in parallel (parallelized by instance)
208              
209             =head1 VERSION
210              
211             version 0.97
212              
213             =for Pod::Coverage Tags Tests runtests
214              
215             =head1 SUPPORT
216              
217             Bugs may be submitted at L<https://github.com/houseabsolute/test-class-moose/issues>.
218              
219             I am also usually active on IRC as 'autarch' on C<irc://irc.perl.org>.
220              
221             =head1 SOURCE
222              
223             The source code repository for Test-Class-Moose can be found at L<https://github.com/houseabsolute/test-class-moose>.
224              
225             =head1 AUTHORS
226              
227             =over 4
228              
229             =item *
230              
231             Curtis "Ovid" Poe <ovid@cpan.org>
232              
233             =item *
234              
235             Dave Rolsky <autarch@urth.org>
236              
237             =back
238              
239             =head1 COPYRIGHT AND LICENSE
240              
241             This software is copyright (c) 2012 - 2019 by Curtis "Ovid" Poe.
242              
243             This is free software; you can redistribute it and/or modify it under
244             the same terms as the Perl 5 programming language system itself.
245              
246             The full text of the license can be found in the
247             F<LICENSE> file included with this distribution.
248              
249             =cut