File Coverage

blib/lib/Test/Expander.pm
Criterion Covered Total %
statement 369 370 99.7
branch 120 122 100.0
condition 27 27 100.0
subroutine 74 75 98.6
pod 0 10 0.0
total 590 604 98.0


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.0'; ## no critic (RequireUseStrict, RequireUseWarnings)
6              
7 27     27   1857845 use strict;
  27         78  
  27         1628  
8             use warnings
9 27         3674 FATAL => qw( all ),
10 27     27   148 NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );
  27         108  
11              
12 27     27   13717 use Const::Fast;
  27         75692  
  27         268  
13 27     27   14585 use File::chdir;
  27         93721  
  27         3699  
14 27     27   23078 use File::Temp qw( tempdir tempfile );
  27         696834  
  27         2731  
15 27     27   20688 use Getopt::Long qw( GetOptions :config posix_default );
  27         380529  
  27         305  
16 27     27   27119 use Importer;
  27         183795  
  27         343  
17 27     27   26221 use Path::Tiny qw( cwd path );
  27         418691  
  27         2924  
18 27     27   13047 use Scalar::Readonly qw( readonly_on );
  27         16689  
  27         2234  
19 27     27   17251 use Term::ANSIColor qw( color colored );
  27         285500  
  27         22942  
20 27     27   11156 use Test::Builder;
  27         1243950  
  27         1322  
21 27     27   223 use Test2::API qw( context );
  27         66  
  27         2003  
22 27     27   248 use Test2::API::Context qw();
  27         72  
  27         872  
23 27     27   12529 use Test2::Tools::Basic;
  27         29050  
  27         2784  
24 27     27   12929 use Test2::Tools::Explain;
  27         12998  
  27         1897  
25 27     27   11704 use Test2::Tools::Subtest;
  27         16789  
  27         3127  
26              
27 27         17643 use Test::Expander::Constants qw(
28             $DIE $FALSE
29             $FMT_INVALID_COLOR $FMT_INVALID_DIRECTORY $FMT_INVALID_ENV_ENTRY $FMT_INVALID_VALUE $FMT_INVALID_SUBTEST_NUMBER
30             $FMT_MISSING_TDT $FMT_KEEP_ENV_VAR $FMT_NEW_FAILED $FMT_NEW_SUCCEEDED $FMT_REPLACEMENT $FMT_REQUIRE_DESCRIPTION
31             $FMT_REQUIRE_IMPLEMENTATION $FMT_SEARCH_PATTERN $FMT_SET_ENV_VAR $FMT_SET_TO $FMT_SKIP_ENV_VAR $FMT_UNSET_VAR
32             $FMT_UNKNOWN_OPTION $FMT_USE_DESCRIPTION $FMT_USE_IMPLEMENTATION
33             $MSG_BAIL_OUT $MSG_ERROR_WAS $MSG_NO_TABLE_HEADER $MSG_UNEXPECTED_EXCEPTION
34             $NOTE
35             $REGEX_ANY_EXTENSION $REGEX_CLASS_HIERARCHY_LEVEL $REGEX_TABLE_SEPARATOR $REGEX_TOP_DIR_IN_PATH $REGEX_VERSION_NUMBER
36             $TRUE
37             %COLORS %MOST_CONSTANTS_TO_EXPORT %OPTION_PARSER %REST_CONSTANTS_TO_EXPORT
38 27     27   13517 );
  27         93  
39              
40             my $ok_orig = \&Test2::API::Context::ok;
41             my ( @subtest_names, @subtest_numbers );
42             my %colors = %COLORS;
43              
44             sub _subtest_selection {
45 33     33   5059 my $error;
46             GetOptions(
47             'subtest_name|subtest=s' => sub {
48 4     4   1245 ( undef, my $opt_value ) = @_;
49 4 100       13 push( @subtest_names, eval { qr/$opt_value/ } ? $opt_value : "\Q$opt_value\E" );
  4         203  
50             },
51             'subtest_number=s' => sub {
52 4     4   1491 ( undef, my $opt_value ) = @_;
53 4 100       46 $error = sprintf( $FMT_INVALID_SUBTEST_NUMBER, $opt_value ) if $opt_value !~ m{^ \d+ (?: / \d+ )* $}x;
54 4         155 push( @subtest_numbers, $opt_value );
55             },
56 33         522 );
57 33 100       20354 die( $error) if $error;
58              
59 32         119 my $subtest_buffered_orig = \&Test2::Tools::Subtest::subtest_buffered;
60 32         123 my $subtest_streamed_orig = \&Test2::Tools::Subtest::subtest_streamed;
61 27     27   250 no warnings qw( redefine );
  27         56  
  27         4385  
62 32     15   230 *Test2::Tools::Subtest::subtest_buffered = sub { _subtest_conditional( $subtest_buffered_orig, @_ ) };
  15         637296  
63 32     2   268 *Test2::Tools::Subtest::subtest_streamed = sub { _subtest_conditional( $subtest_streamed_orig, @_ ) };
  2         313569  
64              
65 32         959 return;
66             }
67              
68 27     27   129 BEGIN { _subtest_selection() }
69              
70 27     27   11664 use Test2::V0 qw();
  27         2960886  
  27         3502  
71              
72             readonly_on( $VERSION );
73              
74             our ( $CLASS, $METHOD, $METHOD_REF, $TEMP_DIR, $TEMP_FILE, $TEST_FILE );
75             our @EXPORT = (
76             @{ Const::Fast::EXPORT },
77             @{ Test2::Tools::Explain::EXPORT },
78             @{ Test2::V0::EXPORT },
79             qw( tempdir tempfile ),
80             qw( cwd path ),
81             qw( BAIL_OUT bail_on_failure dies_ok is_deeply lives_ok new_ok require_ok restore_failure_handler throws_ok use_ok ),
82             qw( test_table ),
83             );
84              
85             {
86 27     27   257 no warnings qw( once );
  27         172  
  27         20470  
87             *BAIL_OUT = \&bail_out; # Explicit "sub BAIL_OUT" would be untestable
88             }
89              
90             sub bail_on_failure {
91             _set_failure_handler(
92             sub {
93             # uncoverable subroutine
94 1     1   64 bail_out( $MSG_BAIL_OUT ) # uncoverable statement
95             }
96 2     2 0 19 );
97              
98 2         4 return;
99             }
100              
101             sub dies_ok ( &;$ ) {
102 2     2 0 95208 my ( $coderef, $description ) = @_;
103              
104 2         13 eval { $coderef->() };
  2         7  
105              
106 2         74 return ok( $@, $description );
107             }
108              
109             sub import {
110 37     37   14390 my ( $class, @exports ) = @_;
111              
112 37         152 my $frame_index = 0;
113 37         247 my $test_file;
114 37         377 while( my @current_frame = caller( $frame_index++ ) ) {
115 117         6485 $test_file = path( $current_frame[ 1 ] ) =~ s{^/}{}r;
116             }
117 37         1564 my $options = _parse_options( \@exports, $test_file );
118              
119 27         117 _export_most_symbols( $test_file );
120 27         163 _set_env( $options->{ -target }, $test_file );
121 27 100 100     425 _mock_builtins( $options ) if defined( $CLASS ) && exists( $options->{ -builtins } );
122              
123 26         331 Test2::V0->import( %$options );
124              
125 26         69185 _export_rest_symbols();
126 26         344 Importer->import_into( $class, scalar( caller ), () );
127              
128 26         2377408 return;
129             }
130              
131             sub is_deeply ( $$;$@ ) {
132 1     2 0 89510 my ( $got, $expected, $title ) = @_;
133              
134 1         9 return is( $got, $expected, $title );
135             }
136              
137             sub lives_ok ( &;$ ) {
138 11     11 0 797072 my ( $coderef, $description ) = @_;
139              
140 11         26 eval { $coderef->() };
  11         35  
141 11 100       93 diag( $MSG_UNEXPECTED_EXCEPTION . $@ ) if $@;
142              
143 11         788 return ok( !$@, $description );
144             }
145              
146             sub new_ok {
147 2     2 0 111219 my ( $class, $args ) = @_;
148              
149 2   100     15 $args ||= [];
150 2         5 my $obj = eval { $class->new( @$args ) };
  2         13  
151 2         110 ok( !$@, _new_test_message( $class ) );
152              
153 2         884 return $obj;
154             }
155              
156             sub require_ok {
157 2     2 0 289023 my ( $module ) = @_;
158              
159 2         7 my $package = caller;
160 2         217 my $require_result = eval( sprintf( $FMT_REQUIRE_IMPLEMENTATION, $package, $module ) );
161 2         2119 ok( $require_result, sprintf( $FMT_REQUIRE_DESCRIPTION, $module, _error() ) );
162              
163 2         559 return $require_result;
164             }
165              
166             sub restore_failure_handler {
167 27     27   220 no warnings qw( redefine );
  27         123  
  27         35699  
168 1     1 0 15 *Test2::API::Context::ok = $ok_orig;
169              
170 1         3 return;
171             }
172              
173             sub test_table {
174 5 100   5 0 1350 my $data = @_ ? shift : _load_tdt();
175 5         22 chomp( my @data = @$data );
176              
177 5         11 my ( $header, $title_inline ) = _test_table_header( \@data );
178              
179 4         6 my %test_table;
180 4         9 while ( my $title = shift( @data ) ) {
181 10 100       64 next if $title =~ /^[+\-$REGEX_TABLE_SEPARATOR]-/;
182 6         9 my @line;
183 6 100       10 if ( $title_inline ) {
184 3         33 ( undef, $title, @line ) = split( $REGEX_TABLE_SEPARATOR, $title );
185 3         6 unshift( @line, undef );
186             }
187             else {
188 3         16 ( undef, $title ) = split( $REGEX_TABLE_SEPARATOR, $title );
189 3         13 @line = split( $REGEX_TABLE_SEPARATOR, shift( @data ) );
190             }
191              
192 6         13 foreach my $index ( 1 .. $#$header ) {
193 18   100     39 my $value = $line[ $index ] // '';
194             ## no critic (ProhibitStringyEval)
195 18         499 $test_table{ $title }->{ $header->[ $index ] } = eval( $value );
196             }
197             }
198              
199 4         27 return %test_table;
200             }
201              
202             sub throws_ok ( &$;$ ) {
203 16     16 0 1032078 my ( $coderef, $expecting, $description ) = @_;
204              
205 16         39 eval { $coderef->() };
  16         53  
206 16         52 my $exception = $@;
207 16         39 my $expected_type = ref( $expecting );
208              
209 16 100       103 return $expected_type eq 'Regexp' ? like ( $exception, $expecting, $description )
210             : isa_ok( $exception, [ $expecting ], $description );
211             }
212              
213             sub use_ok ( $;@ ) {
214 1     1 0 202150 my ( $module, @imports ) = @_;
215              
216 1         7 my ( $package, $filename, $line ) = caller( 0 );
217 1         4 $filename =~ y/\n\r/_/; # taken over from Test::More
218              
219 1         7 my $require_result = eval( sprintf( $FMT_USE_IMPLEMENTATION, $package, $module, _use_imports( \@imports ) ) );
220 1         12 ok(
221             $require_result,
222             sprintf(
223             $FMT_USE_DESCRIPTION, $module, _error( $FMT_SEARCH_PATTERN, sprintf( $FMT_REPLACEMENT, $filename, $line ) )
224             )
225             );
226              
227 1         355 return $require_result;
228             }
229              
230             sub _colorize {
231 108     108   78563 my ( $value, $export_type ) = @_;
232              
233 108 100       768 return defined( $colors{ $export_type } ) ? colored( $value, $colors{ $export_type } ) : $value;
234             }
235              
236             sub _determine_testee {
237 28     28   88 my ( $options, $test_file ) = @_;
238              
239 28 100       125 if ( $options->{ -lib } ) {
240 3         6 foreach my $directory ( @{ $options->{ -lib } } ) {
  3         11  
241 3 100       15 $DIE->( $FMT_INVALID_DIRECTORY, $directory, 'invalid type' ) if ref( $directory );
242 2         162 my $inc_entry = eval( $directory );
243 2 100       122 $DIE->( $FMT_INVALID_DIRECTORY, $directory, $@ ) if $@;
244 1         5 unshift( @INC, $inc_entry );
245             }
246 1         3 delete( $options->{ -lib } );
247             }
248              
249 26 100       118 if ( exists( $options->{ -method } ) ) {
250 2         4 delete( $options->{ -method } );
251             }
252             else {
253 24         79 $METHOD = path( $test_file )->basename( $REGEX_ANY_EXTENSION );
254             }
255              
256 26 100       2715 unless ( exists( $options->{ -target } ) ) { # Try to determine class / module autmatically
257 22         169 my ( $test_root ) = $test_file =~ $REGEX_TOP_DIR_IN_PATH;
258 22         262 my $testee = path( $test_file )->relative( $test_root )->parent;
259 22 100       14349 $options->{ -target } = "$testee" =~ s{/}{::}gr if grep { path( $_ )->child( $testee . '.pm' )->is_file } @INC;
  177         13807  
260             }
261 26 100       1899 if ( defined( $options->{ -target } ) ) {
262 24         66 $CLASS = $options->{ -target };
263             }
264             else {
265 2         4 delete( $options->{ -target } );
266             }
267              
268 26         96 return $options;
269             }
270              
271             sub _error {
272 7     7   129149 my ( $search_string, $replacement_string ) = @_;
273              
274 7 100       81 return '' if $@ eq '';
275              
276 3         34 my $error = $MSG_ERROR_WAS . $@ =~ s/\n$//mr;
277 3 100       31 $error =~ s/$search_string/$replacement_string/m if defined( $search_string );
278              
279 3         51 return $error;
280             }
281              
282             sub _export_most_symbols {
283 28     28   73 my ( $test_file ) = @_;
284              
285 28 100       99 $TEST_FILE = path( $test_file )->absolute->stringify if path( $test_file )->exists;
286              
287 28         3505 return _export_symbols( %MOST_CONSTANTS_TO_EXPORT );
288             }
289              
290             sub _export_rest_symbols {
291             # Further export if class and method are known
292 26 100 100 26   638 return _export_symbols( %REST_CONSTANTS_TO_EXPORT ) if $CLASS && $METHOD && ( $METHOD_REF = $CLASS->can( $METHOD ) );
      100        
293              
294 4         11 $METHOD = undef;
295              
296 4         9 return;
297             }
298              
299             sub _export_symbols {
300 46     46   205 my %constants = @_;
301              
302 46         283 foreach my $name ( sort keys( %constants ) ) { # Export defined constants
303 27     27   241 no strict qw( refs );
  27         175  
  27         13183  
304 142         56905 my $value = eval( "${ \$name }" );
  142         9960  
305 142 100       831 if ( defined( $value ) ) {
    100          
306 94         149 readonly_on( ${ __PACKAGE__ . '::' . $name =~ s/^.//r } );
  94         747  
307 94         235 push( @EXPORT, $name );
308 94         422 $NOTE->( $FMT_SET_TO, _colorize( $name, 'exported' ), $constants{ $name }->( $value, $CLASS ) );
309             }
310             elsif ( $name =~ /^ \$ (?: CLASS | METHOD | METHOD_REF )$/x ) {
311 2         8 $NOTE->( $FMT_UNSET_VAR, _colorize( $name, 'unexported' ) );
312             }
313             }
314              
315 46         28597 return;
316             }
317              
318             sub _load_tdt {
319 2     2   249509 my $tdt_file = $TEST_FILE =~ s/$REGEX_ANY_EXTENSION/.tdt/r;
320 2         6 my $test_table = eval { path( $tdt_file)->slurp };
  2         13  
321 2 100       159 $DIE->( $FMT_MISSING_TDT, $tdt_file, $@ =~ s/\n//gr =~ s/ at .+//ir ) if $@;
322              
323 1         38 return [ split( m{$/}, $test_table ) ];
324             }
325              
326             sub _mock_builtins {
327 1     1   3 my ( $options ) = @_;
328              
329 1         2 while ( my ( $sub_name, $sub_ref ) = each( %{ $options->{ -builtins } } ) ) {
  2         11  
330 1         3 my $sub_full_name = $CLASS . '::' . $sub_name;
331 27     27   197 no strict qw( refs );
  27         116  
  27         22569  
332 1         19 *${ sub_full_name } = $sub_ref;
333             }
334 1         3 delete( $options->{ -builtins } );
335              
336 1         2 return;
337             }
338              
339             sub _new_test_message {
340 4     4   147695 my ( $class ) = @_;
341              
342 4 100       70 return $@ ? sprintf( $FMT_NEW_FAILED, $class, _error() ) : sprintf( $FMT_NEW_SUCCEEDED, $class, $class );
343             }
344              
345             sub _parse_bail {
346 1     1   4 my ( $options, $option_name, $option_value ) = @_;
347              
348             _set_failure_handler(
349             sub {
350             # uncoverable subroutine
351 0     0   0 bail_out( $MSG_BAIL_OUT ) # uncoverable statement
352             }
353 1         9 );
354              
355 1         4 return;
356             }
357              
358             sub _parse_builtins {
359 3     3   9 my ( $options, $option_name, $option_value ) = @_;
360              
361 3 100       20 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
362 2         17 while ( my ( $sub_name, $sub_ref ) = each( %$option_value ) ) {
363 2 100       20 $DIE->( $FMT_INVALID_VALUE, $option_name . "->{ $sub_name }", $sub_ref ) if ref( $sub_ref ) ne 'CODE';
364             }
365 1         3 $options->{ $option_name } = $option_value;
366              
367 1         5 return;
368             }
369              
370             sub _parse_color {
371 4     4   22 my ( $options, $option_name, $option_value ) = @_;
372              
373 4         11 keys( %colors );
374 4         24 while ( my ( $color_name, $color_value ) = each( %colors ) ) {
375 7 100       29 if ( exists( $option_value->{ $color_name } ) ) {
376 3         7 my $requested_color = $option_value->{ $color_name };
377 3 100       10 if ( defined( $requested_color ) ) {
378 2         4 eval { color( $requested_color ) };
  2         10  
379 2 100       371 $DIE->( $FMT_INVALID_COLOR, $requested_color, $color_name ) if $@;
380             }
381 2         9 $colors{ $color_name } = $requested_color;
382             }
383             }
384 3         12 foreach my $color_name ( keys( %$option_value ) ) {
385 2 100       12 $DIE->( $FMT_UNKNOWN_OPTION, $option_name, $color_name ) unless exists( $colors{ $color_name } );
386             }
387              
388 2         6 return;
389             }
390              
391             sub _parse_lib {
392 4     4   12 my ( $options, $option_name, $option_value ) = @_;
393              
394 4 100       18 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'ARRAY';
395 3         12 $options->{ $option_name } = $option_value;
396              
397 3         15 return;
398             }
399              
400             sub _parse_method {
401 3     3   11 my ( $options, $option_name, $option_value ) = @_;
402              
403 3 100       16 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value );
404 2         6 $METHOD = $options->{ $option_name } = $option_value;
405              
406 2         9 return;
407             }
408              
409             sub _parse_options {
410 36     36   220 my ( $exports, $test_file ) = @_;
411              
412 36         130 my $options = {};
413 36         197 while ( my $option_name = shift( @$exports ) ) {
414 24 100 100     158 $DIE->( $FMT_UNKNOWN_OPTION, $option_name, shift( @$exports ) // '' ) if $option_name !~ /^-\w/;
415              
416 22         61 my $option_value = shift( @$exports );
417 22 100       123 my $parser = exists( $OPTION_PARSER{ $option_name } ) ? '_parse_' . substr( $option_name, 1 ) : '_take_over';
418 27     27   217 { no strict qw( refs ); $parser->( $options, $option_name, $option_value ) }
  27         184  
  27         29153  
  22         64  
  22         95  
419             }
420              
421 28         215 return _determine_testee( $options, $test_file );
422             }
423              
424             sub _parse_target {
425 4     4   11 my ( $options, $option_name, $option_value ) = @_;
426              
427 4         11 $options->{ $option_name } = $option_value;
428              
429 4         26 return;
430             }
431              
432             sub _parse_tempdir {
433 3     3   8 my ( $options, $option_name, $option_value ) = @_;
434              
435 3 100       16 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
436 2         15 $TEMP_DIR = tempdir( CLEANUP => 1, %$option_value );
437              
438 2         1970 return;
439             }
440              
441             sub _parse_tempfile {
442 3     3   8 my ( $options, $option_name, $option_value ) = @_;
443              
444 3 100       17 $DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
445 2         4 my $file_handle;
446 2         10 ( $file_handle, $TEMP_FILE ) = tempfile( UNLINK => 1, %$option_value );
447              
448 2         1358 return;
449             }
450              
451             sub _read_env_file {
452 9     9   28 my ( $env_file ) = @_;
453              
454 9         31 my @lines = path( $env_file )->lines( { chomp => 1 } );
455 9         3111 my %env;
456 9         65 while ( my ( $index, $line ) = each( @lines ) ) {
457             ## no critic (ProhibitUnusedCapture)
458 14 100       697 next unless $line =~ /^ (? \w+) \s* (?: = \s* (? \S .*) | $ )/x;
459 13         180 my ( $name, $value ) = @+{ qw( name value ) };
460 13 100       108 if ( exists( $+{ value } ) ) {
    100          
461 11         34 my $stricture = q{
462             use strict;
463             use warnings
464             FATAL => qw( all ),
465             NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );
466             };
467 11         28 $value = eval {
468 11     3   1281 eval( $stricture . '$value' );
  3     2   654  
  3         1648  
  3         231  
  2         14  
  2         5  
  2         264  
469 11 50       52 die( $@ ) if $@; # uncoverable branch true
470 11     2   1034 $value = eval( $stricture . $value );
  2     2   16  
  2         4  
  2         112  
  2         12  
  2         4  
  2         237  
471 11 100       663 die( $@ ) if $@;
472 9         28 $value;
473             };
474 11 100       133 $DIE->( $FMT_INVALID_ENV_ENTRY, $index, $env_file, $line, $@ =~ s/\n//gr =~ s/ at \(eval .+//ir ) if $@;
475 9 100       31 if ( defined( $value ) ) {
476 8         34 $NOTE->( $FMT_SET_ENV_VAR, _colorize( $name, 'exported' ), $value, $env_file );
477 8         6058 $ENV{ $name } = $env{ $name } = $value;
478             }
479             else {
480 1         4 $NOTE->( $FMT_SKIP_ENV_VAR, _colorize( $name, 'unexported' ), $env_file );
481             }
482             }
483             elsif ( exists( $ENV{ $+{ name } } ) ) {
484 1         5 $env{ $name } = $ENV{ $name };
485 1         5 $NOTE->( $FMT_KEEP_ENV_VAR, _colorize( $name, 'exported' ), $ENV{ $name }, $env_file );
486             }
487             }
488              
489 7         780 return \%env;
490             }
491              
492             sub _set_env {
493 35     35   356977 my ( $class, $test_file ) = @_;
494              
495 35 100       182 return unless path( $test_file )->exists;
496              
497 34         2198 my $env_found = $FALSE;
498 34         84 my $new_env = {};
499             {
500 34         71 local $CWD = $test_file =~ s{/.*}{}r; ## no critic (ProhibitLocalVars)
  34         222  
501 34         2348 ( $env_found, $new_env ) = _set_env_hierarchically( $class, $env_found, $new_env );
502             }
503              
504 34         2103 my $env_file = $test_file =~ s/$REGEX_ANY_EXTENSION/.env/r;
505              
506 34 100       460 if ( path( $env_file )->is_file ) {
507 7 100       2730 $env_found = $TRUE unless $env_found;
508 7         29 my $method_env = _read_env_file( $env_file );
509 5         32 @$new_env{ keys( %$method_env ) } = values( %$method_env );
510             }
511              
512 32 100       2693 %ENV = %$new_env if $env_found;
513              
514 32         133 return;
515             }
516              
517             sub _set_env_hierarchically {
518 94     94   292 my ( $class, $env_found, $new_env ) = @_;
519              
520 94 100       386 return ( $env_found, $new_env ) unless $class;
521              
522 62         106 my $class_top_level;
523 62         627 ( $class_top_level, $class ) = $class =~ $REGEX_CLASS_HIERARCHY_LEVEL;
524              
525 62 100       221 return ( $FALSE, {} ) unless path( $class_top_level )->is_dir;
526              
527 60         3653 my $env_file = $class_top_level . '.env';
528 60 100       174 if ( path( $env_file )->is_file ) {
529 2 100       137 $env_found = $TRUE unless $env_found;
530 2         7 $new_env = { %$new_env, %{ _read_env_file( $env_file ) } };
  2         7  
531             }
532              
533 60         2919 local $CWD = $class_top_level; ## no critic (ProhibitLocalVars)
534              
535 60         2349 return _set_env_hierarchically( $class, $env_found, $new_env );
536             }
537              
538             sub _set_failure_handler {
539 2     2   7 my $action = shift;
540 27     27   217 no warnings qw( redefine );
  27         118  
  27         19583  
541             *Test2::API::Context::ok = sub {
542 8     8   2281 my ( undef, $pass ) = @_;
543 8         33 my $result = $ok_orig->( @_ );
544 8 50       1850 $action->() unless $pass; # uncoverable branch true
545              
546 8         119 return $result;
547 2         18 };
548              
549 2         6 return;
550             }
551              
552             sub _subtest_conditional {
553 18     18   79 my ( $orig_subtest, $name, @rest ) = @_;
554              
555 18         83 my $ctx = context();
556 18         2300 my $number = join( '/', map { $_->count } @{ $ctx->stack } );
  21         190  
  18         88  
557 18 100 100     300 if (
      100        
      100        
558             !@subtest_names && !@subtest_numbers ||
559 3         47 ( grep { $name =~ /$_/ } @subtest_names ) ||
560 3         66 ( grep { /^$number/ } @subtest_numbers )
561             ) {
562 17         81 $orig_subtest->( $name, @rest );
563 17         80128 $ctx->release;
564             }
565             else {
566 1         7 $ctx->skip( 'forced by ' . __PACKAGE__ );
567 1         385 $ctx->release;
568             }
569              
570 18         858 return;
571             }
572              
573             sub _take_over {
574 1     1   3 my ( $options, $option_name, $option_value ) = @_;
575              
576 1         4 $options->{ $option_name } = $option_value;
577              
578 1         4 return;
579             }
580              
581             sub _test_table_header {
582 5     5   7 my ( $data ) = @_;
583              
584 5         8 my $header = [];
585 5         25 while ( my $titles = shift( @$data ) ) {
586 18 100       160 @$header ? last : next if $titles =~ /^[+\-$REGEX_TABLE_SEPARATOR]-/;
    100          
587 8         53 my @line = split( $REGEX_TABLE_SEPARATOR, $titles );
588 8         17 foreach my $index ( 1 .. $#line ) {
589 30 100       73 $line [ $index ] = ' ' if $line[ $index ] eq '';
590 30 100       62 $header->[ $index ] = defined( $header->[ $index ] ) ? $header->[ $index ] . $line[ $index ] : $line[ $index ];
591             }
592             }
593 5 100       14 die( $MSG_NO_TABLE_HEADER ) unless @$header;
594              
595 4         8 $header->[ 0 ] = '';
596 4         43 s/^\s+|\s+$//g foreach @$header;
597 4         8 my $title_in_line = $header->[ 1 ] eq '';
598 4 100       6 shift( @$header ) if $title_in_line;
599              
600 4         10 return ( $header, $title_in_line );
601             }
602              
603             sub _use_imports {
604 4     4   108856 my ( $imports ) = @_;
605              
606 4 100 100     193 return @$imports == 1 && $imports->[ 0 ] =~ $REGEX_VERSION_NUMBER ? ' ' . $imports->[ 0 ] : '';
607             }
608              
609             1;