File Coverage

blib/lib/Test/Class/Moose/Executor/Parallel.pm
Criterion Covered Total %
statement 79 83 95.1
branch 3 4 75.0
condition n/a
subroutine 22 23 95.6
pod n/a
total 104 110 94.5


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