File Coverage

blib/lib/Test/Expander.pm
Criterion Covered Total %
statement 386 387 99.7
branch 126 128 100.0
condition 32 32 100.0
subroutine 75 76 98.6
pod 0 10 0.0
total 619 633 98.1


line stmt bran cond sub pod time code
1             ## no critic ( ProhibitStringyEval ProhibitSubroutinePrototypes RequireLocalizedPunctuationVars)
2             package Test::Expander;
3              
4             # The versioning is conform with https://semver.org
5             our $VERSION = '2.6.1'; ## no critic (RequireUseStrict, RequireUseWarnings)
6              
7 27     27   1399513 use strict;
  27         59  
  27         1612  
8             use warnings
9 27         3759 FATAL => qw( all ),
10 27     27   175 NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );
  27         117  
11              
12 27     27   12037 use Const::Fast;
  27         76028  
  27         243  
13 27     27   15139 use File::chdir;
  27         88247  
  27         3604  
14 27     27   22153 use File::Temp qw( tempdir tempfile );
  27         650103  
  27         2678  
15 27     27   20042 use Getopt::Long qw( GetOptions :config posix_default );
  27         348757  
  27         284  
16 27     27   21563 use Importer;
  27         162697  
  27         281  
17 27     27   23829 use Path::Tiny qw( cwd path );
  27         403149  
  27         2886  
18 27     27   12637 use Scalar::Readonly qw( readonly_off readonly_on );
  27         17355  
  27         2192  
19 27     27   12002 use Sub::Delete qw( delete_sub );
  27         30248  
  27         2272  
20 27     27   16588 use Term::ANSIColor qw( color colored );
  27         290584  
  27         23389  
21 27     27   10631 use Test::Builder;
  27         1247715  
  27         1329  
22 27     27   224 use Test2::API qw( context );
  27         70  
  27         1783  
23 27     27   250 use Test2::API::Context qw();
  27         79  
  27         766  
24 27     27   12787 use Test2::Tools::Basic;
  27         29559  
  27         2736  
25 27     27   13505 use Test2::Tools::Explain;
  27         12475  
  27         1963  
26 27     27   12130 use Test2::Tools::Subtest;
  27         18325  
  27         3131  
27              
28 27         16786 use Test::Expander::Constants qw(
29             $DIE $FALSE
30             $FMT_INVALID_COLOR $FMT_INVALID_DIRECTORY $FMT_INVALID_ENV_ENTRY $FMT_INVALID_VALUE $FMT_INVALID_SUBTEST_NUMBER
31             $FMT_MISSING_TDT $FMT_KEEP_ENV_VAR $FMT_NEW_FAILED $FMT_NEW_SUCCEEDED $FMT_REPLACEMENT $FMT_REQUIRE_DESCRIPTION
32             $FMT_REQUIRE_IMPLEMENTATION $FMT_SEARCH_PATTERN $FMT_SET_ENV_VAR $FMT_SET_TO $FMT_SKIP_ENV_VAR $FMT_UNSET_VAR
33             $FMT_UNKNOWN_OPTION $FMT_USE_DESCRIPTION $FMT_USE_IMPLEMENTATION
34             $MSG_BAIL_OUT $MSG_ERROR_WAS $MSG_NO_TABLE_HEADER $MSG_UNEXPECTED_EXCEPTION
35             $NOTE
36             $REGEX_ANY_EXTENSION $REGEX_CLASS_HIERARCHY_LEVEL $REGEX_TABLE_SEPARATOR $REGEX_TOP_DIR_IN_PATH $REGEX_VERSION_NUMBER
37             $TRUE
38             @TEST2_V0_EXPORT %COLORS %MOST_CONSTANTS_TO_EXPORT %OPTION_PARSER %REST_CONSTANTS_TO_EXPORT
39 27     27   13287 );
  27         87  
40              
41             my $ok_orig = \&Test2::API::Context::ok;
42             my ( @subtest_names, @subtest_numbers );
43             my %colors = %COLORS;
44              
45             sub _subtest_selection {
46 33     33   4304 my $error;
47             GetOptions(
48             'subtest_name|subtest=s' => sub {
49 4     4   1013 ( undef, my $opt_value ) = @_;
50 4 100       9 push( @subtest_names, eval { qr/$opt_value/ } ? $opt_value : "\Q$opt_value\E" );
  4         225  
51             },
52             'subtest_number=s' => sub {
53 4     4   1291 ( undef, my $opt_value ) = @_;
54 4 100       27 $error = sprintf( $FMT_INVALID_SUBTEST_NUMBER, $opt_value ) if $opt_value !~ m{^ \d+ (?: / \d+ )* $}x;
55 4         112 push( @subtest_numbers, $opt_value );
56             },
57 33         497 );
58 33 100       18164 die( $error) if $error;
59              
60 32         113 my $subtest_buffered_orig = \&Test2::Tools::Subtest::subtest_buffered;
61 32         186 my $subtest_streamed_orig = \&Test2::Tools::Subtest::subtest_streamed;
62 27     27   226 no warnings qw( redefine );
  27         55  
  27         18005  
63 32     15   207 *Test2::Tools::Subtest::subtest_buffered = sub { _subtest_conditional( $subtest_buffered_orig, @_ ) };
  15         748334  
64 32     2   170 *Test2::Tools::Subtest::subtest_streamed = sub { _subtest_conditional( $subtest_streamed_orig, @_ ) };
  2         209661  
65              
66 32         976 return;
67             }
68              
69 27     27   131 BEGIN { _subtest_selection() }
70              
71 27     27   12708 use Test2::V1;
  27         2869665  
  27         241  
72              
73             readonly_on( $VERSION );
74              
75             our ( $CLASS, $METHOD, $METHOD_REF, $TEMP_DIR, $TEMP_FILE, $TEST_FILE );
76             our @EXPORT = (
77             @{ Const::Fast::EXPORT },
78             @{ Test2::Tools::Explain::EXPORT },
79             @TEST2_V0_EXPORT,
80             qw( tempdir tempfile ),
81             qw( cwd path ),
82             qw( BAIL_OUT bail_on_failure dies_ok is_deeply lives_ok new_ok require_ok restore_failure_handler throws_ok use_ok ),
83             qw( test_table ),
84             );
85              
86             {
87 27     27   12017 no warnings qw( once );
  27         129  
  27         22227  
88             *BAIL_OUT = \&bail_out; # Explicit "sub BAIL_OUT" would be untestable
89             }
90              
91             sub bail_on_failure {
92             _set_failure_handler(
93             sub {
94             # uncoverable subroutine
95 1     1   128 bail_out( $MSG_BAIL_OUT ) # uncoverable statement
96             }
97 2     2 0 21 );
98              
99 2         6 return;
100             }
101              
102             sub dies_ok ( &;$ ) {
103 2     2 0 75824 my ( $coderef, $description ) = @_;
104              
105 2         9 eval { $coderef->() };
  2         7  
106              
107 2         129 return ok( $@, $description );
108             }
109              
110             sub import {
111 38     38   18061 my ( $class, @exports ) = @_;
112              
113 38         223 my $frame_index = 0;
114 38         185 my $test_file;
115 38         426 while( my @current_frame = caller( $frame_index++ ) ) {
116 118         6656 $test_file = path( $current_frame[ 1 ] ) =~ s{^/}{}r;
117             }
118 38         1888 my $options = _parse_options( \@exports, $test_file );
119              
120 27         181 _export_most_symbols( $test_file );
121 27         191 _set_env( $options->{ -target }, $test_file );
122 27 100 100     247 _mock_builtins( $options ) if defined( $CLASS ) && exists( $options->{ -builtins } );
123              
124 27         104 my @options = map { $_, $options->{ $_ } } keys( %$options );
  28         173  
125 27         190 delete_sub( 'CLASS' );
126 27         1262 delete_sub( 'T2' );
127 27 100       5490 Test2::V1->import( '-Pi', grep { ref ne 'ARRAY' || @$_ } @options );
  56         913  
128              
129 27         99744 _export_rest_symbols();
130 27         385 Importer->import_into( $class, scalar( caller ), () );
131              
132 27         2226047 return;
133             }
134              
135             sub is_deeply ( $$;$@ ) {
136 1     1 0 140742 my ( $got, $expected, $title ) = @_;
137              
138 1         10 return is( $got, $expected, $title );
139             }
140              
141             sub lives_ok ( &;$ ) {
142 11     11 0 755986 my ( $coderef, $description ) = @_;
143              
144 11         32 eval { $coderef->() };
  11         61  
145 11 100       102 diag( $MSG_UNEXPECTED_EXCEPTION . $@ ) if $@;
146              
147 11         781 return ok( !$@, $description );
148             }
149              
150             sub new_ok {
151 2     2 0 119229 my ( $class, $args ) = @_;
152              
153 2   100     13 $args ||= [];
154 2         3 my $obj = eval { $class->new( @$args ) };
  2         35  
155 2         99 ok( !$@, _new_test_message( $class ) );
156              
157 2         816 return $obj;
158             }
159              
160             sub require_ok {
161 2     2 0 279401 my ( $module ) = @_;
162              
163 2         8 my $package = caller;
164 2         200 my $require_result = eval( sprintf( $FMT_REQUIRE_IMPLEMENTATION, $package, $module ) );
165 2         2199 ok( $require_result, sprintf( $FMT_REQUIRE_DESCRIPTION, $module, _error() ) );
166              
167 2         554 return $require_result;
168             }
169              
170             sub restore_failure_handler {
171 27     27   242 no warnings qw( redefine );
  27         176  
  27         35388  
172 1     1 0 15 *Test2::API::Context::ok = $ok_orig;
173              
174 1         3 return;
175             }
176              
177             sub test_table {
178 5 100   5 0 1495 my $data = @_ ? shift : _load_tdt();
179 5         28 chomp( my @data = @$data );
180              
181 5         45 my ( $header, $title_inline ) = _test_table_header( \@data );
182              
183 4         11 my %test_table;
184 4         15 while ( my $title = shift( @data ) ) {
185 10 100       113 next if $title =~ /^[+\-$REGEX_TABLE_SEPARATOR]-/;
186 6         12 my @line;
187 6 100       16 if ( $title_inline ) {
188 3         40 ( undef, $title, @line ) = split( $REGEX_TABLE_SEPARATOR, $title );
189 3         8 unshift( @line, undef );
190             }
191             else {
192 3         29 ( undef, $title ) = split( $REGEX_TABLE_SEPARATOR, $title );
193 3         81 @line = split( $REGEX_TABLE_SEPARATOR, shift( @data ) );
194             }
195              
196 6         20 foreach my $index ( 1 .. $#$header ) {
197 18   100     54 my $value = $line[ $index ] // '';
198             ## no critic (ProhibitStringyEval)
199 18         809 $test_table{ $title }->{ $header->[ $index ] } = eval( $value );
200             }
201             }
202              
203 4         40 return %test_table;
204             }
205              
206             sub throws_ok ( &$;$ ) {
207 16     16 0 774385 my ( $coderef, $expecting, $description ) = @_;
208              
209 16         39 eval { $coderef->() };
  16         55  
210 16         58 my $exception = $@;
211 16         40 my $expected_type = ref( $expecting );
212              
213 16 100       107 return $expected_type eq 'Regexp' ? like ( $exception, $expecting, $description )
214             : isa_ok( $exception, [ $expecting ], $description );
215             }
216              
217             sub use_ok ( $;@ ) {
218 1     1 0 243638 my ( $module, @imports ) = @_;
219              
220 1         5 my ( $package, $filename, $line ) = caller( 0 );
221 1         2 $filename =~ y/\n\r/_/; # taken over from Test::More
222              
223 1         6 my $require_result = eval( sprintf( $FMT_USE_IMPLEMENTATION, $package, $module, _use_imports( \@imports ) ) );
224 1         14 ok(
225             $require_result,
226             sprintf(
227             $FMT_USE_DESCRIPTION, $module, _error( $FMT_SEARCH_PATTERN, sprintf( $FMT_REPLACEMENT, $filename, $line ) )
228             )
229             );
230              
231 1         510 return $require_result;
232             }
233              
234             sub _colorize {
235 114     114   137639 my ( $value, $export_type ) = @_;
236              
237 114 100       743 return defined( $colors{ $export_type } ) ? colored( $value, $colors{ $export_type } ) : $value;
238             }
239              
240             sub _determine_testee {
241 29     29   73 my ( $options, $test_file ) = @_;
242              
243 29 100       105 if ( $options->{ -lib } ) {
244 3         5 foreach my $directory ( @{ $options->{ -lib } } ) {
  3         12  
245 3 100       14 $DIE->( $FMT_INVALID_DIRECTORY, $directory, 'invalid type' ) if ref( $directory );
246 2         161 my $inc_entry = eval( $directory );
247 2 100       111 $DIE->( $FMT_INVALID_DIRECTORY, $directory, $@ ) if $@;
248 1         4 unshift( @INC, $inc_entry );
249             }
250 1         3 delete( $options->{ -lib } );
251             }
252              
253 27 100       121 if ( exists( $options->{ -method } ) ) {
254 2         5 delete( $options->{ -method } );
255             }
256             else {
257 25         110 $METHOD = path( $test_file )->basename( $REGEX_ANY_EXTENSION );
258             }
259              
260 27 100       2837 unless ( exists( $options->{ -target } ) ) { # Try to determine class / module autmatically
261 23         139 my ( $test_root ) = $test_file =~ $REGEX_TOP_DIR_IN_PATH;
262 23         231 my $testee = path( $test_file )->relative( $test_root )->parent;
263 23 100       12747 $options->{ -target } = "$testee" =~ s{/}{::}gr if grep { path( $_ )->child( $testee . '.pm' )->is_file } @INC;
  185         15145  
264             }
265 27 100       2245 if ( defined( $options->{ -target } ) ) {
266 25         115 readonly_off( $CLASS );
267 25         53 $CLASS = $options->{ -target };
268 25         73 readonly_on( $CLASS );
269             }
270             else {
271 2         4 delete( $options->{ -target } );
272             }
273              
274 27         100 return $options;
275             }
276              
277             sub _error {
278 7     7   113782 my ( $search_string, $replacement_string ) = @_;
279              
280 7 100       68 return '' if $@ eq '';
281              
282 3         12 my $error = $MSG_ERROR_WAS . $@ =~ s/\n$//mr;
283 3 100       24 $error =~ s/$search_string/$replacement_string/m if defined( $search_string );
284              
285 3         46 return $error;
286             }
287              
288             sub _export_most_symbols {
289 29     29   88 my ( $test_file ) = @_;
290              
291 29 100       147 $TEST_FILE = path( $test_file )->absolute->stringify if path( $test_file )->exists;
292              
293 29         4071 return _export_symbols( %MOST_CONSTANTS_TO_EXPORT );
294             }
295              
296             sub _export_rest_symbols {
297             # Further export if class and method are known
298 27 100 100 27   571 return _export_symbols( %REST_CONSTANTS_TO_EXPORT ) if $CLASS && $METHOD && ( $METHOD_REF = $CLASS->can( $METHOD ) );
      100        
299              
300 4         10 $METHOD = undef;
301              
302 4         9 return;
303             }
304              
305             sub _export_symbols {
306 48     48   277 my %constants = @_;
307              
308 48         298 foreach my $name ( sort keys( %constants ) ) { # Export defined constants
309 27     27   214 no strict qw( refs );
  27         151  
  27         13766  
310 148         59581 my $value = eval( "${ \$name }" );
  148         12234  
311 148 100       888 if ( defined( $value ) ) {
    100          
312 100         173 readonly_on( ${ __PACKAGE__ . '::' . $name =~ s/^.//r } );
  100         834  
313 100         303 push( @EXPORT, $name );
314 100         305 $NOTE->( $FMT_SET_TO, _colorize( $name, 'exported' ), $constants{ $name }->( $value, $CLASS ) );
315             }
316             elsif ( $name =~ /^ \$ (?: CLASS | METHOD | METHOD_REF )$/x ) {
317 2         12 $NOTE->( $FMT_UNSET_VAR, _colorize( $name, 'unexported' ) );
318             }
319             }
320              
321 48         28877 return;
322             }
323              
324             sub _load_tdt {
325 2     2   222300 my $tdt_file = $TEST_FILE =~ s/$REGEX_ANY_EXTENSION/.tdt/r;
326 2         3 my $test_table = eval { path( $tdt_file)->slurp };
  2         10  
327 2 100       98 $DIE->( $FMT_MISSING_TDT, $tdt_file, $@ =~ s/\n//gr =~ s/ at .+//ir ) if $@;
328              
329 1         20 return [ split( m{$/}, $test_table ) ];
330             }
331              
332             sub _mock_builtins {
333 1     1   3 my ( $options ) = @_;
334              
335 1         2 while ( my ( $sub_name, $sub_ref ) = each( %{ $options->{ -builtins } } ) ) {
  2         32  
336 1         3 my $sub_full_name = $CLASS . '::' . $sub_name;
337 27     27   209 no strict qw( refs );
  27         172  
  27         26587  
338 1         18 *${ sub_full_name } = $sub_ref;
339             }
340 1         3 delete( $options->{ -builtins } );
341              
342 1         16 return;
343             }
344              
345             sub _new_test_message {
346 4     4   114145 my ( $class ) = @_;
347              
348 4 100       67 return $@ ? sprintf( $FMT_NEW_FAILED, $class, _error() ) : sprintf( $FMT_NEW_SUCCEEDED, $class, $class );
349             }
350              
351             sub _parse_bail {
352 1     1   4 my ( $options, $option_name, @option_value ) = @_;
353              
354             _set_failure_handler(
355             sub {
356             # uncoverable subroutine
357 0     0   0 bail_out( $MSG_BAIL_OUT ) # uncoverable statement
358             }
359 1         25 );
360              
361 1         5 return;
362             }
363              
364             sub _parse_builtins {
365 3     3   11 my ( $options, $option_name, @option_value ) = @_;
366              
367 3         8 my ( $option_value ) = @option_value;
368 3 100       24 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
369 2         12 while ( my ( $sub_name, $sub_ref ) = each( %$option_value ) ) {
370 2 100       16 $DIE->( $FMT_INVALID_VALUE, $option_name . "->{ $sub_name }", $sub_ref ) if ref( $sub_ref ) ne 'CODE';
371             }
372 1         3 $options->{ $option_name } = $option_value;
373              
374 1         6 return;
375             }
376              
377             sub _parse_color {
378 4     4   24 my ( $options, $option_name, @option_value ) = @_;
379              
380 4         10 my ( $option_value ) = @option_value;
381 4         9 keys( %colors );
382 4         20 while ( my ( $color_name, $color_value ) = each( %colors ) ) {
383 7 100       31 if ( exists( $option_value->{ $color_name } ) ) {
384 3         8 my $requested_color = $option_value->{ $color_name };
385 3 100       11 if ( defined( $requested_color ) ) {
386 2         4 eval { color( $requested_color ) };
  2         10  
387 2 100       331 $DIE->( $FMT_INVALID_COLOR, $requested_color, $color_name ) if $@;
388             }
389 2         27 $colors{ $color_name } = $requested_color;
390             }
391             }
392 3         12 foreach my $color_name ( keys( %$option_value ) ) {
393 3 100       14 $DIE->( $FMT_UNKNOWN_OPTION, $option_name, $color_name ) unless exists( $colors{ $color_name } );
394             }
395              
396 2         7 return;
397             }
398              
399             sub _parse_lib {
400 4     4   11 my ( $options, $option_name, @option_value ) = @_;
401              
402 4         107 my ( $option_value ) = @option_value;
403 4 100       25 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'ARRAY';
404 3         7 $options->{ $option_name } = $option_value;
405              
406 3         16 return;
407             }
408              
409             sub _parse_method {
410 3     3   10 my ( $options, $option_name, @option_value ) = @_;
411              
412 3         6 my ( $option_value ) = @option_value;
413 3 100       17 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value );
414 2         5 $METHOD = $options->{ $option_name } = $option_value;
415              
416 2         9 return;
417             }
418              
419             sub _parse_options {
420 37     37   191 my ( $exports, $test_file ) = @_;
421              
422 37         135 my $options = {};
423 37         167 while ( my $option_name = shift( @$exports ) ) {
424 26 100 100     130 $DIE->( $FMT_UNKNOWN_OPTION, $option_name, shift( @$exports ) // '' ) if $option_name !~ /^-[\-\w]/;
425              
426 24   100     77 my $option_value = $exports->[ 0 ] // '';
427 24 100 100     193 my @option_value = @$exports && $option_value !~ /^-/ ? shift( @$exports ) : ();
428 24 100       99 my $parser = exists( $OPTION_PARSER{ $option_name } ) ? '_parse_' . substr( $option_name, 1 ) : '_take_over';
429 27     27   216 { no strict qw( refs ); $parser->( $options, $option_name, @option_value ) }
  27         121  
  27         32069  
  24         36  
  24         118  
430             }
431              
432 29         138 return _determine_testee( $options, $test_file );
433             }
434              
435             sub _parse_target {
436 4     4   11 my ( $options, $option_name, @option_value ) = @_;
437              
438 4         10 ( $options->{ $option_name } ) = @option_value;
439              
440 4         15 return;
441             }
442              
443             sub _parse_tempdir {
444 3     3   8 my ( $options, $option_name, @option_value ) = @_;
445              
446 3         5 my ( $option_value ) = @option_value;
447 3 100       15 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
448 2         10 $TEMP_DIR = tempdir( CLEANUP => 1, %$option_value );
449              
450 2         1436 return;
451             }
452              
453             sub _parse_tempfile {
454 3     3   7 my ( $options, $option_name, @option_value ) = @_;
455              
456 3         6 my ( $option_value ) = @option_value;
457 3 100       13 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
458 2         3 my $file_handle;
459 2         9 ( $file_handle, $TEMP_FILE ) = tempfile( UNLINK => 1, %$option_value );
460              
461 2         1134 return;
462             }
463              
464             sub _read_env_file {
465 9     9   23 my ( $env_file ) = @_;
466              
467 9         29 my @lines = path( $env_file )->lines( { chomp => 1 } );
468 9         2807 my %env;
469 9         59 while ( my ( $index, $line ) = each( @lines ) ) {
470             ## no critic (ProhibitUnusedCapture)
471 14 100       717 next unless $line =~ /^ (? \w+) \s* (?: = \s* (? \S .*) | $ )/x;
472 13         161 my ( $name, $value ) = @+{ qw( name value ) };
473 13 100       109 if ( exists( $+{ value } ) ) {
    100          
474 11         29 my $stricture = q{
475             use strict;
476             use warnings
477             FATAL => qw( all ),
478             NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );
479             };
480 11         26 $value = eval {
481 11     3   1181 eval( $stricture . '$value' );
  3     2   484  
  3         2053  
  3         196  
  2         13  
  2         5  
  2         242  
482 11 50       42 die( $@ ) if $@; # uncoverable branch true
483 11     2   989 $value = eval( $stricture . $value );
  2     2   13  
  2         3  
  2         89  
  2         9  
  2         5  
  2         214  
484 11 100       620 die( $@ ) if $@;
485 9         26 $value;
486             };
487 11 100       86 $DIE->( $FMT_INVALID_ENV_ENTRY, $index, $env_file, $line, $@ =~ s/\n//gr =~ s/ at \(eval .+//ir ) if $@;
488 9 100       25 if ( defined( $value ) ) {
489 8         31 $NOTE->( $FMT_SET_ENV_VAR, _colorize( $name, 'exported' ), $value, $env_file );
490 8         5150 $ENV{ $name } = $env{ $name } = $value;
491             }
492             else {
493 1         5 $NOTE->( $FMT_SKIP_ENV_VAR, _colorize( $name, 'unexported' ), $env_file );
494             }
495             }
496             elsif ( exists( $ENV{ $+{ name } } ) ) {
497 1         4 $env{ $name } = $ENV{ $name };
498 1         5 $NOTE->( $FMT_KEEP_ENV_VAR, _colorize( $name, 'exported' ), $ENV{ $name }, $env_file );
499             }
500             }
501              
502 7         451 return \%env;
503             }
504              
505             sub _set_env {
506 36     36   295904 my ( $class, $test_file ) = @_;
507              
508 36 100       168 return unless path( $test_file )->exists;
509              
510 35         2028 my $env_found = $FALSE;
511 35         86 my $new_env = {};
512             {
513 35         73 local $CWD = $test_file =~ s{/.*}{}r; ## no critic (ProhibitLocalVars)
  35         200  
514 35         2390 ( $env_found, $new_env ) = _set_env_hierarchically( $class, $env_found, $new_env );
515             }
516              
517 35         2132 my $env_file = $test_file =~ s/$REGEX_ANY_EXTENSION/.env/r;
518              
519 35 100       430 if ( path( $env_file )->is_file ) {
520 7 100       441 $env_found = $TRUE unless $env_found;
521 7         24 my $method_env = _read_env_file( $env_file );
522 5         28 @$new_env{ keys( %$method_env ) } = values( %$method_env );
523             }
524              
525 33 100       2666 %ENV = %$new_env if $env_found;
526              
527 33         135 return;
528             }
529              
530             sub _set_env_hierarchically {
531 97     97   271 my ( $class, $env_found, $new_env ) = @_;
532              
533 97 100       358 return ( $env_found, $new_env ) unless $class;
534              
535 64         153 my $class_top_level;
536 64         590 ( $class_top_level, $class ) = $class =~ $REGEX_CLASS_HIERARCHY_LEVEL;
537              
538 64 100       275 return ( $FALSE, {} ) unless path( $class_top_level )->is_dir;
539              
540 62         3962 my $env_file = $class_top_level . '.env';
541 62 100       228 if ( path( $env_file )->is_file ) {
542 2 100       113 $env_found = $TRUE unless $env_found;
543 2         9 $new_env = { %$new_env, %{ _read_env_file( $env_file ) } };
  2         9  
544             }
545              
546 62         3036 local $CWD = $class_top_level; ## no critic (ProhibitLocalVars)
547              
548 62         2380 return _set_env_hierarchically( $class, $env_found, $new_env );
549             }
550              
551             sub _set_failure_handler {
552 2     2   9 my $action = shift;
553 27     27   238 no warnings qw( redefine );
  27         169  
  27         20187  
554             *Test2::API::Context::ok = sub {
555 8     8   2259 my ( undef, $pass ) = @_;
556 8         39 my $result = $ok_orig->( @_ );
557 8 50       1780 $action->() unless $pass; # uncoverable branch true
558              
559 8         67 return $result;
560 2         19 };
561              
562 2         6 return;
563             }
564              
565             sub _subtest_conditional {
566 18     18   82 my ( $orig_subtest, $name, @rest ) = @_;
567              
568 18         65 my $ctx = context();
569 18         2325 my $number = join( '/', map { $_->count } @{ $ctx->stack } );
  21         173  
  18         80  
570 18 100 100     247 if (
      100        
      100        
571             !@subtest_names && !@subtest_numbers ||
572 3         48 ( grep { $name =~ /$_/ } @subtest_names ) ||
573 3         71 ( grep { /^$number/ } @subtest_numbers )
574             ) {
575 17         83 $orig_subtest->( $name, @rest );
576 17         38536 $ctx->release;
577             }
578             else {
579 1         8 $ctx->skip( 'forced by ' . __PACKAGE__ );
580 1         434 $ctx->release;
581             }
582              
583 18         834 return;
584             }
585              
586             sub _take_over {
587 3     3   8 my ( $options, $option_name, @option_value ) = @_;
588              
589 3 100       12 $options->{ $option_name } = @option_value ? $option_value[ 0 ] : [];
590              
591 3         13 return;
592             }
593              
594             sub _test_table_header {
595 5     5   12 my ( $data ) = @_;
596              
597 5         11 my $header = [];
598 5         18 while ( my $titles = shift( @$data ) ) {
599 18 100       216 @$header ? last : next if $titles =~ /^[+\-$REGEX_TABLE_SEPARATOR]-/;
    100          
600 8         89 my @line = split( $REGEX_TABLE_SEPARATOR, $titles );
601 8         28 foreach my $index ( 1 .. $#line ) {
602 30 100       71 $line [ $index ] = ' ' if $line[ $index ] eq '';
603 30 100       129 $header->[ $index ] = defined( $header->[ $index ] ) ? $header->[ $index ] . $line[ $index ] : $line[ $index ];
604             }
605             }
606 5 100       24 die( $MSG_NO_TABLE_HEADER ) unless @$header;
607              
608 4         10 $header->[ 0 ] = '';
609 4         82 s/^\s+|\s+$//g foreach @$header;
610 4         13 my $title_in_line = $header->[ 1 ] eq '';
611 4 100       13 shift( @$header ) if $title_in_line;
612              
613 4         32 return ( $header, $title_in_line );
614             }
615              
616             sub _use_imports {
617 4     4   135707 my ( $imports ) = @_;
618              
619 4 100 100     115 return @$imports == 1 && $imports->[ 0 ] =~ $REGEX_VERSION_NUMBER ? ' ' . $imports->[ 0 ] : '';
620             }
621              
622             1;