Skip to content

Commit

Permalink
skip assignment of environment variables in case of undefined value
Browse files Browse the repository at this point in the history
  • Loading branch information
jsf116 committed Jul 28, 2023
1 parent d323022 commit 2ceb12a
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 55 deletions.
6 changes: 6 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
Changelog for Test-Expander

2.1.4 2023-07-28
- Skip assignment of environment variables in case of undefined value.

2.1.3 2023-07-27
- Fix implementation of '-target => undef'.

2.1.2 2023-07-24
- Improve documentation by introduction of CAVEATS topic.

Expand Down
3 changes: 3 additions & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ Readme.md
t/.perlcriticrc
t/.proverc
t/.proverc-cover
t/Test/Expander/_determine_testee.env
t/Test/Expander/_determine_testee.t
t/Test/Expander/_error.t
t/Test/Expander/_new_test_message.t
t/Test/Expander/_parse_options.t
t/Test/Expander/_set_env.t
t/Test/Expander/_use_imports.t
t/Test/Expander/dies_ok.t
Expand Down
6 changes: 3 additions & 3 deletions META.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@
"provides" : {
"Test::Expander" : {
"file" : "lib/Test/Expander.pm",
"version" : "2.1.2"
"version" : "2.1.4"
},
"Test::Expander::Constants" : {
"file" : "lib/Test/Expander/Constants.pm",
"version" : "2.1.2"
"version" : "2.1.4"
}
},
"release_status" : "stable",
Expand All @@ -76,5 +76,5 @@
"web" : "https://github.com/jsf116/Test-Expander"
}
},
"version" : "2.1.2"
"version" : "2.1.4"
}
6 changes: 3 additions & 3 deletions META.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ no_index:
provides:
Test::Expander:
file: lib/Test/Expander.pm
version: '2.1.2'
version: '2.1.4'
Test::Expander::Constants:
file: lib/Test/Expander/Constants.pm
version: '2.1.2'
version: '2.1.4'
requires:
B: '0'
Const::Fast: '0'
Expand All @@ -39,4 +39,4 @@ requires:
resources:
bugtracker: https://github.com/jsf116/Test-Expander
repository: git://github.com/jsf116/Test-Expander.git
version: '2.1.2'
version: '2.1.4'
2 changes: 1 addition & 1 deletion Makefile.PL
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use v5.14;
use strict;
use warnings
FATAL => qw(all),
NONFATAL => qw(deprecated exec internal malloc newline portable recursion);
Expand Down
76 changes: 43 additions & 33 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
# does not create temporary file, creates a temporary directory and
# adds directories 'dir0' and 'dir1' located therein on top of the directory list used by the Perl interpreter
# for search of modules to be loaded. In other words, "unshifts" these directories to the @INC array:
# PLEASE CONSIDER THE SINGLE QUOTES APPLIED BELOW!
use Test::Expander
-lib => [
'path( $TEMP_DIR )->child( qw( dir0 ) )->stringify',
Expand Down Expand Up @@ -65,13 +66,13 @@ This, of course, can be stored in additional variables declared somewhere at the
a single change of path and / or base name of the corresponding test file.

An additional benefit of suggested approach is a better readability of tests, where chunks like
```perl
```perl
Foo::Bar->baz( $arg0, $arg1 )
```
```
now look like
```perl
```perl
$CLASS->$METHOD( $arg0, $arg1 )
```
```
and hence clearly manifest that this chunk is about the testee.

- The frequent necessity of introduction of temporary directory and / or temporary file usually leads to the usage of
Expand All @@ -80,9 +81,9 @@ providing the methods / funtions **tempdir** and **tempfile**.

This, however, can significantly be simplified (and the size of test file can be reduced) requesting such introduction
via the options supported by **Test::Expander**:
```perl
```perl
use Test::Expander -tempdir => {}, -tempfile => {};
```
```
- Another fuctionality frequently used in tests relates to the work with files and directories:
reading, writing, creation, etc. Because almost all features required in such cases are provided by
[Path::Tiny](https://metacpan.org/pod/Path::Tiny), some functions of this module is also exported from
Expand Down Expand Up @@ -126,7 +127,8 @@ The following options are accepted:
- **-lib** - prepend directory list used by the Perl interpreter for search of modules to be loaded
(i.e. the **@INC** array) with values supplied in form of array reference.
Each element of this array is evaluated using [string eval](https://perldoc.perl.org/functions/eval) so that
expression evaluated to strings are supported.
any valid expression evaluated to string is supported if it is based on modules used by **Test::Expander** or
any module loaded before.

Among other things this provides a possibility to temporary expand the module search path by directories located
in the temporary directory if the latter is defined with the option **-tempdir** (see below).
Expand Down Expand Up @@ -325,32 +327,40 @@ In this case they are logged to STDOUT using [note](https://metacpan.org/pod/Tes

# CAVEATS

**Test::Expander** is recommended to be the very first module in your test file.

The known exceptions are:

- When another module is used, which in turn is based on [Test::Builder](https://metacpan.org/pod/Test::Builder) e.g.
[Test::Output](https://metacpan.org/pod/Test::Output):
```perl
use Test::Output;
use Test::Expander;
```
- When some actions performed on the module level (e.g. determination of constants)
rely upon results of other actions (e.g. mocking of built-ins).

To explain this let us assume that your test file should globally mock the built-in **close**
(if this is only required in the name space of class / module to be tested,
the option \*\*builtin\*\* should be used instead!)
to verify if the testee properly reacts both on its success and failure.
For this purpose a reasonable implementation might look as follows:
```perl
my $close_success;
BEGIN {
*CORE::GLOBAL::close = sub (*) { $close_success ? CORE::close( shift ) : 0 }
}

use Test::Expander;
```
- **Test::Expander** is recommended to be the very first module in your test file.

The known exceptions are:

1. When another module is used, which in turn is based on [Test::Builder](https://metacpan.org/pod/Test::Builder) e.g.
[Test::Output](https://metacpan.org/pod/Test::Output):
```perl
use Test::Output;
use Test::Expander;
```
2. When some actions performed on the module level (e.g. determination of constants)
rely upon results of other actions (e.g. mocking of built-ins).

To explain this let us assume that your test file should globally mock the built-in **close**
(if this is only required in the name space of class / module to be tested,
the option **-builtin** should be used instead!)
to verify if the testee properly reacts both on its success and failure.
For this purpose a reasonable implementation might look as follows:
```perl
my $close_success;
BEGIN {
*CORE::GLOBAL::close = sub (*) { $close_success ? CORE::close( shift ) : 0 }
}

use Test::Expander;
```
- Array elements of the value supplied along with the option **-lib** are evaluated using
[string eval](https://perldoc.perl.org/functions/eval) so that constant strings would need duplicated quotes e.g.
```perl
use Test::Expander -lib => [ q('my_test_lib') ];
```
- If the value to be assigned to an environment variable after evaluation of an **.env** file is undefined,
such assignment is skipped.

# AUTHOR

Jurij Fajnberg, <fajnbergj at gmail.com>
Expand Down
28 changes: 19 additions & 9 deletions lib/Test/Expander.pm
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
package Test::Expander;

# The versioning is conform with https://semver.org
our $VERSION = '2.1.2'; ## no critic (RequireUseStrict, RequireUseWarnings)
our $VERSION = '2.1.4'; ## no critic (RequireUseStrict, RequireUseWarnings)

use strict;
use warnings
Expand All @@ -23,7 +23,7 @@ use Test::Expander::Constants qw(
$DIE $FALSE
$FMT_INVALID_DIRECTORY $FMT_INVALID_ENV_ENTRY $FMT_INVALID_VALUE $FMT_KEEP_ENV_VAR $FMT_NEW_FAILED
$FMT_NEW_SUCCEEDED $FMT_REPLACEMENT $FMT_REQUIRE_DESCRIPTION $FMT_REQUIRE_IMPLEMENTATION $FMT_SEARCH_PATTERN
$FMT_SET_ENV_VAR $FMT_SET_TO $FMT_UNKNOWN_OPTION $FMT_USE_DESCRIPTION $FMT_USE_IMPLEMENTATION
$FMT_SET_ENV_VAR $FMT_SET_TO $FMT_SKIP_ENV_VAR $FMT_UNKNOWN_OPTION $FMT_USE_DESCRIPTION $FMT_USE_IMPLEMENTATION
$MSG_ERROR_WAS $MSG_UNEXPECTED_EXCEPTION
$NOTE
$REGEX_ANY_EXTENSION $REGEX_CLASS_HIERARCHY_LEVEL $REGEX_TOP_DIR_IN_PATH $REGEX_VERSION_NUMBER
Expand Down Expand Up @@ -163,7 +163,12 @@ sub _determine_testee {
my $testee = path( $test_file )->relative( $test_root )->parent;
$options->{ -target } = $testee =~ s{/}{::}gr if grep { path( $_ )->child( $testee . '.pm' )->is_file } @INC;
}
$CLASS = $options->{ -target } if exists( $options->{ -target } );
if ( defined( $options->{ -target } ) ) {
$CLASS = $options->{ -target };
}
else {
delete( $options->{ -target } );
}

return $options;
}
Expand Down Expand Up @@ -250,8 +255,8 @@ sub _parse_options {
$DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value );
$METHOD = $options->{ $option_name } = $option_value;
}
elsif ( $option_name eq '-target' ) { # Do not load module only if its name is undef
$options->{ $option_name } = $option_value if defined( $option_value );
elsif ( $option_name eq '-target' ) {
$options->{ $option_name } = $option_value;
}
elsif ( $option_name eq '-tempdir' ) {
$DIE->( $FMT_INVALID_VALUE, $option_name, $option_value ) if ref( $option_value ) ne 'HASH';
Expand Down Expand Up @@ -286,20 +291,25 @@ sub _read_env_file {
FATAL => qw( all ),
NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );
};
$env{ $name } = eval {
$value = eval {
eval( $stricture . '$value' );
die( $@ ) if $@; # uncoverable branch true
$value = eval( $stricture . $value );
die( $@ ) if $@;
$value;
};
$DIE->( $FMT_INVALID_ENV_ENTRY, $index, $env_file, $line, $@ =~ s/\n//gr =~ s/ at \(eval .+//ir ) if $@;
$NOTE->( $FMT_SET_ENV_VAR, $name, $env{ $name }, $env_file );
$ENV{ $name } = $env{ $name };
if ( defined( $value ) ) {
$NOTE->( $FMT_SET_ENV_VAR, $name, $value, $env_file );
$ENV{ $name } = $env{ $name } = $value;
}
else {
$NOTE->( $FMT_SKIP_ENV_VAR, $name, $env_file );
}
}
elsif ( exists( $ENV{ $+{ name } } ) ) {
$env{ $name } = $ENV{ $name };
$NOTE->( $FMT_KEEP_ENV_VAR, $name, $ENV{ $name } );
$NOTE->( $FMT_KEEP_ENV_VAR, $name, $ENV{ $name }, $env_file );
}
}

Expand Down
28 changes: 24 additions & 4 deletions lib/Test/Expander.pod
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ B<Test::Expander> - Expansion of test functionalities that appear to be frequent
# does not create temporary file, creates a temporary directory and
# adds directories 'dir0' and 'dir1' located therein on top of the directory list used by the Perl interpreter
# for search of modules to be loaded. In other words, "unshifts" these directories to the @INC array:
# PLEASE CONSIDER THE SINGLE QUOTES APPLIED BELOW!
use Test::Expander
-lib => [
'path( $TEMP_DIR )->child( qw( dir0 ) )->stringify',
Expand Down Expand Up @@ -153,7 +154,8 @@ the values are code references overriding default behavior.
B<-lib> - prepend directory list used by the Perl interpreter for search of modules to be loaded
(i.e. the B<@INC> array) with values supplied in form of array reference.
Each element of this array is evaluated using L<string eval|https://perldoc.perl.org/functions/eval> so that
expression evaluated to strings are supported.
any valid expression evaluated to string is supported if it is based on modules used by B<Test::Expander> or
any module loaded before.

Among other things this provides a possibility to temporary expand the module search path by directories located
in the temporary directory if the latter is defined with the option B<-tempdir> (see below).
Expand Down Expand Up @@ -539,28 +541,32 @@ In this case they are logged to STDOUT using L<note|https://metacpan.org/pod/Tes

=head1 CAVEATS

=over 2

=item

B<Test::Expander> is recommended to be the very first module in your test file.

The known exceptions are:

=over 2

=item
=item 1

When another module is used, which in turn is based on L<Test::Builder|https://metacpan.org/pod/Test::Builder> e.g.
L<Test::Output|https://metacpan.org/pod/Test::Output>:

use Test::Output;
use Test::Expander;

=item
=item 2

When some actions performed on the module level (e.g. determination of constants)
rely upon results of other actions (e.g. mocking of built-ins).

To explain this let us assume that your test file should globally mock the built-in B<close>
(if this is only required in the name space of class / module to be tested,
the option **builtin** should be used instead!)
the option B<-builtin> should be used instead!)
to verify if the testee properly reacts both on its success and failure.
For this purpose a reasonable implementation might look as follows:

Expand All @@ -573,6 +579,20 @@ For this purpose a reasonable implementation might look as follows:

=back

=item

Array elements of the value supplied along with the option B<-lib> are evaluated using
L<string eval|https://perldoc.perl.org/functions/eval> so that constant strings would need duplicated quotes e.g.

use Test::Expander -lib => [ q('my_test_lib') ];

=item

If the value to be assigned to an environment variable after evaluation of an B<.env> file is undefined,
such assignment is skipped.

=back

=head1 AUTHOR

Jurij Fajnberg, <fajnbergj at gmail.com>
Expand Down
5 changes: 3 additions & 2 deletions lib/Test/Expander/Constants.pm
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package Test::Expander::Constants;

our $VERSION = '2.1.2'; ## no critic (RequireUseStrict, RequireUseWarnings)
our $VERSION = '2.1.4'; ## no critic (RequireUseStrict, RequireUseWarnings)

use strict;
use warnings
Expand All @@ -25,7 +25,7 @@ const our $FALSE => 0;
const our $FMT_INVALID_DIRECTORY => "Invalid directory name / expression '%s' supplied with option '-lib'%s\n";
const our $FMT_INVALID_ENV_ENTRY => "Erroneous line %d of '%s' containing '%s': %s\n";
const our $FMT_INVALID_VALUE => "Option '%s' passed along with invalid value '%s'\n";
const our $FMT_KEEP_ENV_VAR => "Keep environment variable '%s' containing '%s'";
const our $FMT_KEEP_ENV_VAR => "Keep environment variable '%s' containing '%s' because it is not reassigned in file '%s'";
const our $FMT_NEW_FAILED => '%s->new died.%s';
const our $FMT_NEW_SUCCEEDED => "An object of class '%s' isa '%s'";
const our $FMT_REPLACEMENT => $EXCEPTION_PREFIX . '%s line %s.';
Expand All @@ -34,6 +34,7 @@ const our $FMT_REQUIRE_IMPLEMENTATION => 'package %s; require %s';
const our $FMT_SEARCH_PATTERN => $EXCEPTION_PREFIX . '.*$';
const our $FMT_SET_ENV_VAR => "Set environment variable '%s' to '%s' from file '%s'";
const our $FMT_SET_TO => "Set %s to '%s'";
const our $FMT_SKIP_ENV_VAR => "Skip environment variable '%s' because its value from file '%s' is undefined";
const our $FMT_UNKNOWN_OPTION => "Unknown option '%s' => '%s' supplied.\n";
const our $FMT_USE_DESCRIPTION => 'use %s;%s';
const our $FMT_USE_IMPLEMENTATION => 'package %s; use %s%s; 1';
Expand Down
1 change: 1 addition & 0 deletions t/Test/Expander/_determine_testee.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
NEW_ENV_VAR = $TEMP_DIR
12 changes: 12 additions & 0 deletions t/Test/Expander/_determine_testee.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use strict;
use warnings
FATAL => qw( all ),
NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );

use Test::Expander -method => undef;

ok( exists( $main::{ CLASS } ), 'class determined' );
ok( !exists( $main::{ METHOD } ), 'no method determined' );
ok( !exists( $ENV{ NEW_ENV_VAR } ), 'environment variable unset due to undefined value from .env file' );

done_testing();
10 changes: 10 additions & 0 deletions t/Test/Expander/_parse_options.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use strict;
use warnings
FATAL => qw( all ),
NONFATAL => qw( deprecated exec internal malloc newline once portable redefine recursion uninitialized );

use Test::Expander -target => undef;

ok( !exists( $main::{ CLASS } ), 'no class determined' );

done_testing();

0 comments on commit 2ceb12a

Please sign in to comment.