File Coverage

blib/lib/Test/Class/Moose/Executor/Parallel.pm
Criterion Covered Total %
statement 81 85 95.2
branch 3 4 75.0
condition n/a
subroutine 23 24 95.8
pod n/a
total 107 113 94.6


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