first commit
This commit is contained in:
commit
717e9581ea
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
Makefile
|
||||
build
|
196
Readme.md
Normal file
196
Readme.md
Normal file
@ -0,0 +1,196 @@
|
||||
# NAME
|
||||
|
||||
**Test::Expander** - Expansion of test functionalities that appear to be frequently used while testing.
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
```perl
|
||||
# Tries to determine both class and method to be tested automatically,
|
||||
# does not create any temporary directory:
|
||||
use Test::Expander;
|
||||
|
||||
# Tries to determine both class and method to be tested automatically,
|
||||
# does not create any temporary directory,
|
||||
# passes the option '-srand' to Test::V0 changing the random seed to the current time in seconds:
|
||||
use Test::Expander -srand => time;
|
||||
|
||||
# Tries to determine only the method to be tested automatically, class is explicitly supplied,
|
||||
# a temporary directory is created with name corresponing to the template supplied:
|
||||
use Test::Expander -target => 'My::Class', -tempdir => { TEMPLATE => 'my_dir.XXXXXXXX' };
|
||||
```
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
**Test::Expander** combines all advanced possibilities provided by [Test2::V0](https://metacpan.org/pod/Test2::V0)
|
||||
with some specific functions available in the older module [Test::More](https://metacpan.org/pod/Test::More) only
|
||||
(which allows a smooth migration from [Test::More](https://metacpan.org/pod/Test::More)-based tests to
|
||||
[Test2::V0](https://metacpan.org/pod/Test2::V0)-based ones) and handy functions from some other modules
|
||||
often used in test suites.
|
||||
|
||||
Furthermore, this module provides a recognition of class to be tested (see variable **$CLASS** below) so that
|
||||
in contrast to [Test2::V0](https://metacpan.org/pod/Test2::V0) you do not need to specify this explicitly
|
||||
if the path to the test file is in accordance with the name of class to be tested.
|
||||
|
||||
A similar recognition is provided in regard to the method / subroutine to be tested
|
||||
(see variables **$METHOD** and **METHOD\_REF** below) if the base name (without extension) of test file is
|
||||
identical with the name of this method / subroutine.
|
||||
|
||||
Finally, a configurable setting of specific environment variables is provided so that
|
||||
there is no need to hard-code this in the test itself.
|
||||
|
||||
For the time being the following options are accepted by **Test::Expander**:
|
||||
|
||||
- Options specific for this module only:
|
||||
- **-target** - identical with the same-named option of [Test2::V0](https://metacpan.org/pod/Test2::V0) and
|
||||
has the same purpose namely the explicit definition of class to be tested as a value of this option;
|
||||
- **-tempdir** - activates creation of a temporary directory by the function **tempdir** provided by
|
||||
[File::Temp::tempdir](https://metacpan.org/pod/File::Temp).
|
||||
- **-tempfile** - activates creation of a temporary file by the method **tempfile** provided by
|
||||
[File::Temp::tempfile](https://metacpan.org/pod/File::Temp).
|
||||
- All other valid options (i.e. arguments starting with the dash sign **-**) are forwarded to
|
||||
[Test2::V0](https://metacpan.org/pod/Test2::V0) along with their values.
|
||||
- If an argument cannot be recognized as an option, an exception is raised.
|
||||
|
||||
The proper application of **Test::Expander** implies that is is used as the very first in your unit test.
|
||||
|
||||
The only exception currently known is the case, when some actions performed on the module level
|
||||
(e.g. determination of constants) base on results of other actions (e.g. mocking of built-ins).
|
||||
|
||||
To explain this let us assume that your unit test file should mock the built-in **close**
|
||||
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 $closeSuccess = 1;
|
||||
BEGIN {
|
||||
*CORE::GLOBAL::close = sub (*) { return $closeSuccess ? CORE::close($_[0]) : 0 };
|
||||
}
|
||||
|
||||
use Test::Expander;
|
||||
```
|
||||
|
||||
Furthermore, the automated recognition of name of class to be tested can only work properly
|
||||
if the test file is located in the corresponding subdirectory of **t**, or **xt**, or any other folder
|
||||
containing a bunch of test files.
|
||||
For instance, if the class to be tested is _Foo::Bar::Baz_, then the folder with test files
|
||||
related to this class should be **t/**_Foo_**/**_Bar_**/**_Baz_ or **xt/**_Foo_**/**_Bar_**/**_Baz_
|
||||
(the name of the top-level directory in this relative name - **t**, or **xt**, or **my\_test** is not important) -
|
||||
otherwise the module name cannot be put into the exported variable **$CLASS** and, if you want to use this variable,
|
||||
should be supplied as a value of the option **-target**:
|
||||
|
||||
```perl
|
||||
use Test::Expander -target => 'Foo::Bar::Baz';
|
||||
```
|
||||
|
||||
What is more, the automated recognition of name of method / subroutine to be tested can only work properly
|
||||
if the base name of the test file without extension (usually **.t**) is equal to the method / subroutine
|
||||
name. In other words, this recognition only works if the file containing the class mentioned above exists and
|
||||
if this class has the method / subroutine with the same name as the test file base name without extension.
|
||||
If this is the case, the exported variables **$METHOD** and **$METHOD\_REF** contain the name of method / subroutine
|
||||
to be tested and its reference, correspondingly, otherwise both variables are undefined.
|
||||
|
||||
Finally, **Test::Expander** supports testing inside of a clean environment containing only some clearly
|
||||
specified environment variables required for the particular test.
|
||||
Names and values of these environment variables should be configured in files,
|
||||
which names are identical with pathes to single class levels or method to be tested,
|
||||
and the extension is always **.env**.
|
||||
For instance, if the test file name is **t/Foo/Bar/Baz/myMethod.t**, the following approach is applied:
|
||||
|
||||
- if the file **t/Foo.env** exists, its content is used for the initialization of test environment,
|
||||
- if the file **t/Foo/Bar.env** exists, its content is used either for extension of test environment
|
||||
initialized in the previous step or for its initialization if the file **t/Foo.env** does not exist,
|
||||
- if the file **t/Foo/Bar/Baz.env** exists, its content is used either for extension of test
|
||||
environment initialized in one of the previous steps or for its initialization if neither the file **t/Foo.env** nor
|
||||
the file **t/Foo/Bar.env** exists,
|
||||
- if the file **t/Foo/Bar/Baz/myMethod.env** exists, its content will be used either for extension of test environment
|
||||
initialized in one of the previous steps or for its initialization if no one of **.env** files mentioned above exists.
|
||||
|
||||
If the **.env** files existing on different levels have identical names of environment variables,
|
||||
the priority is the higher the later they have been detected.
|
||||
I.e. **VAR = 'VALUE0'** in **t/Foo/Bar/Baz/myMethod.env** overwrites **VAR = 'VALUE1'** in **t/Foo/Bar/Baz.env**.
|
||||
|
||||
If no one of these **.env** files exists, the environment will not be changed by **Test::Expander**
|
||||
during the execution of **t/Foo/Bar/Baz/myMethod.t**.
|
||||
|
||||
An environment configuration file (**.env** file) is a line-based text file,
|
||||
which content is interpreted as follows:
|
||||
|
||||
- if such files don't exist, the **%ENV** hash remains unchanged;
|
||||
- otherwise, if at least one of such files exists, the **%ENV** gets emptied (without localization) and
|
||||
- lines not matching the RegEx **/^\\w+\\s\\\*=\\s\\\*\\S/** (some alphanumeric characters representing a name of
|
||||
environment variable, optional blanks, the equal sign, again optional blanks and at least one non-blank
|
||||
character representing the first sign of environment variable value) are skipped;
|
||||
- in all other lines the value of the environment variable is everything from the first non-blank
|
||||
character after the equal sign until end of the line;
|
||||
- the value of the environment variable is evaluated by the [string eval](https://perldoc.perl.org/functions/eval)
|
||||
so that
|
||||
- constant values must be quoted;
|
||||
- variables and subroutines must not be quoted:
|
||||
|
||||
NAME_CONST = 'VALUE'
|
||||
NAME_VAR = $KNIB::App::MyApp::Constants::ABC
|
||||
NAME_FUNC = join(' ', $KNIB::App::MyApp::Constants::DEF)
|
||||
|
||||
Another feature frequently applied inside of test suites is creation of a temporary directory / file used as an
|
||||
isolated container for some testing actions.
|
||||
The module options **-tempdir** and **-tempfile** fully synactically compatible with
|
||||
[File::Temp::tempdir](https://metacpan.org/pod/File::Temp#FUNCTIONS) /
|
||||
[File::Temp::tempfile](https://metacpan.org/pod/File::Temp#FUNCTIONS) make sure that such temporary
|
||||
directory / file are created after **use Test::Expander** and their names are stored in the variables
|
||||
**$TEMP\_DIR** / **$TEMP\_FILE**, correspondingly.
|
||||
Both temporary directory and file are removed by default after execution.
|
||||
|
||||
All functions provided by this module are exported by default. These and the exported variables are:
|
||||
|
||||
- all functions exported by default from [Test2::V0](https://metacpan.org/pod/Test2::V0),
|
||||
- all functions exported by default from [Test::Files](https://metacpan.org/pod/Test::Files),
|
||||
- all functions exported by default from [Test::Output](https://metacpan.org/pod/Test::Output),
|
||||
- all functions exported by default from [Test::Warn](https://metacpan.org/pod/Test::Warn),
|
||||
- some functions exported by default from [Test::More](https://metacpan.org/pod/Test::More)
|
||||
and often used in older tests but not supported by [Test2::V0](https://metacpan.org/pod/Test2::V0):
|
||||
- BAIL\_OUT,
|
||||
- is\_deeply,
|
||||
- new\_ok,
|
||||
- require\_ok,
|
||||
- use\_ok,
|
||||
- some functions exported by default from [Test::Exception](https://metacpan.org/pod/Test::Exception)
|
||||
and often used in older tests but not supported by [Test2::V0](https://metacpan.org/pod/Test2::V0):
|
||||
- dies\_ok,
|
||||
- explain,
|
||||
- lives\_ok,
|
||||
- throws\_ok,
|
||||
- function exported by default from [Const::Fast](https://metacpan.org/pod/Const::Fast):
|
||||
- const,
|
||||
- some functions exported by request from [File::Temp](https://metacpan.org/pod/File::Temp):
|
||||
- tempdir,
|
||||
- tempfile,
|
||||
- some functions exported by request from [Path::Tiny](https://metacpan.org/pod/Path::Tiny):
|
||||
- cwd,
|
||||
- path,
|
||||
- variable **$CLASS** containing the name of class to be tested,
|
||||
- variable **$METHOD** containing the name of method to be tested,
|
||||
- variable **$METHOD\_REF** containing the reference to subroutine to be tested.
|
||||
- variable **$TEMP\_DIR** containing the name of a temporary directory created at compile time
|
||||
if the option **-tempdir** was supplied.
|
||||
- variable **$TEMP\_FILE** containing the name of a temporary file created at compile time
|
||||
if the option **-tempfile** was supplied.
|
||||
|
||||
All variables mentioned above are read-only if they are defined after **use Test::Expander ...**.
|
||||
|
||||
# AUTHOR
|
||||
|
||||
Jurij Fajnberg, <fajnbergj at gmail.com>
|
||||
|
||||
# BUGS
|
||||
|
||||
Please report any bugs or feature requests through the web interface at
|
||||
[https://github.com/jsf116/Test-Expander/issues](https://github.com/jsf116/Test-Expander/issues).
|
||||
|
||||
# COPYRIGHT AND LICENSE
|
||||
|
||||
## LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (c) 2021 Jurij Fajnberg
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the same terms
|
||||
as the Perl 5 programming language system itself.
|
379
lib/.perlcriticrc
Normal file
379
lib/.perlcriticrc
Normal file
@ -0,0 +1,379 @@
|
||||
# You may disable specific policies appending the following annotation
|
||||
#
|
||||
# ## no critic (..., ...)
|
||||
#
|
||||
# to the corresponding code line. To direct perlcritic to ignore the
|
||||
# "## no critic" annotations, use the --force option.
|
||||
|
||||
# Policies shipped with Perl::Critic 1.125 were considered for the below
|
||||
# defintion of the new policy theme "knib".
|
||||
|
||||
severity = brutal
|
||||
theme = knib
|
||||
verbose = %f: %m at line %l, column %c. (Policy: %p)\n
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitLvalueSubstr]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval]
|
||||
add_themes = knib
|
||||
allow_includes = 1
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalCan]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalIsa]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic]
|
||||
# KNOWN BUGS: This policy flags a false positive on reverse() called in list
|
||||
# context, since reverse() in list context does not assume $_.
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrep]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireBlockMap]
|
||||
add_themes = knib
|
||||
|
||||
# 14.01.2016 policy disabled after a discussion with the team
|
||||
#[BuiltinFunctions::RequireGlobFunction]
|
||||
#add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireSimpleSortBlock]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA]
|
||||
# Note: Some people prefer parent over base.
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::ProhibitHardTabs]
|
||||
add_themes = knib
|
||||
allow_leading_tabs = 0
|
||||
|
||||
# 14.01.2016 policy disabled after a discussion with the team
|
||||
#[Perl::Critic::Policy::CodeLayout::ProhibitQuotedWordLists]
|
||||
#add_themes = knib
|
||||
#min_elements = 1
|
||||
#strict = 1
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines]
|
||||
add_themes = knib
|
||||
|
||||
# 14.01.2016 policy disabled after a discussion with the team
|
||||
#[Perl::Critic::Policy::CodeLayout::RequireTrailingCommas]
|
||||
#add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitCStyleForLoops]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitCascadingIfElse]
|
||||
add_themes = knib
|
||||
max_elsif = 1
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitDeepNests]
|
||||
# Martin Fowler's book "Refactoring: Improving The Design of Existing Code".
|
||||
add_themes = knib
|
||||
max_nests = 5
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions]
|
||||
# Read the LIMITATIONS that this policy has.
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode]
|
||||
add_themes = knib
|
||||
|
||||
# Available With Perl::Critic v1.126
|
||||
#[Perl::Critic::Policy::ControlStructures::ProhibitYadaOperator]
|
||||
#add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Documentation::PodSpelling]
|
||||
add_themes =
|
||||
# "spell" is the spell checker avalable on our AIX system. The default spell
|
||||
# checker "aspell" was not available.
|
||||
spell_command = spell
|
||||
#stop_words = ...
|
||||
stop_words_file = PodSpelling_stop_words.txt
|
||||
|
||||
[Perl::Critic::Policy::Documentation::RequirePackageMatchesPodName]
|
||||
add_themes = knib
|
||||
|
||||
#[Perl::Critic::Policy::Documentation::RequirePodSections]
|
||||
#add_themes = knib
|
||||
|
||||
# 14.01.2016 policy disabled after a discussion with the team
|
||||
#[Perl::Critic::Policy::ErrorHandling::RequireCheckingReturnValueOfEval]
|
||||
#add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitBacktickOperators]
|
||||
add_themes = knib
|
||||
# 14.01.2016 policy configuration changed after a discussion with the team
|
||||
only_in_void_context = 1
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitExplicitStdin]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitInteractiveTest]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitReadlineInForLoop]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireBriefOpen]
|
||||
# http://www.perlmonks.org/?node_id=1134785
|
||||
add_themes = knib
|
||||
lines = 9
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireCheckedSyscalls]
|
||||
# Covers the policies
|
||||
# Perl::Critic::Policy::InputOutput::RequireCheckedClose and
|
||||
# Perl::Critic::Policy::InputOutput::RequireCheckedOpen
|
||||
add_themes = knib
|
||||
exclude_functions = print say
|
||||
functions = :builtins
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireEncodingWithUTF8Layer]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUnrestrictedNoCritic]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitAutomaticExportation]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitEvilModules]
|
||||
add_themes = knib
|
||||
modules = Class::ISA Error Pod::Plainer Shell Switch
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitExcessMainComplexity]
|
||||
# http://en.wikipedia.org/wiki/Cyclomatic_complexity
|
||||
add_themes = knib
|
||||
max_mccabe = 20
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitMultiplePackages]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireBarewordIncludes]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireEndWithOne]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireExplicitPackage]
|
||||
add_themes = knib
|
||||
allow_import_of = utf8
|
||||
exempt_scripts = 0
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireVersionVar]
|
||||
# Read the TO DO section of this policy and think about its implication.
|
||||
add_themes = knib
|
||||
|
||||
# Perl::Critic::Policy::NamingConventions::Capitalization
|
||||
# It takes some time to configure this policy!
|
||||
|
||||
[Perl::Critic::Policy::Objects::ProhibitIndirectSyntax]
|
||||
add_themes = knib
|
||||
# The new() subroutine is configured by default; any additional forbid values
|
||||
# are in addition to new().
|
||||
forbid = create destroy
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitSingleCharAlternation]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUnusedCapture]
|
||||
add_themes = knib
|
||||
|
||||
# 14.01.2016 policy still enabled after a discussion with the team
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUnusualDelimiters]
|
||||
add_themes = knib
|
||||
allow_all_brackets = 0
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::RequireBracesForMultiline]
|
||||
add_themes = knib
|
||||
allow_all_brackets = 0
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitAmpersandSigils]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms]
|
||||
# Read the CAVEATS.
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExcessComplexity]
|
||||
# http://en.wikipedia.org/wiki/Cyclomatic_complexity
|
||||
add_themes = knib
|
||||
max_mccabe = 20
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef]
|
||||
# http://perlmonks.org/index.pl?node_id=741847
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitManyArgs]
|
||||
add_themes = knib
|
||||
max_arguments = 5
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitNestedSubs]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitReturnSort]
|
||||
# KNOWN BUGS: This Policy is not sensitive to the wantarray() function.
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitSubroutinePrototypes]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProtectPrivateSubs]
|
||||
add_themes = knib
|
||||
|
||||
#[Perl::Critic::Policy::Subroutines::RequireArgUnpacking]
|
||||
#add_themes = knib
|
||||
#allow_delegation_to = SUPER:: NEXT::
|
||||
#allow_subscripts = 0
|
||||
#short_subroutine_statements = 0
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::RequireFinalReturn]
|
||||
add_themes = knib
|
||||
terminal_funcs = return carp croak die exec exit goto throw
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict]
|
||||
add_themes = knib
|
||||
allow = refs
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoWarnings]
|
||||
add_themes = knib
|
||||
allow_with_category_restriction = 1
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitProlongedStrictureOverride]
|
||||
add_themes = knib
|
||||
statements = 3
|
||||
|
||||
# The following policy seems to have a bug for the ok() test.
|
||||
#[Perl::Critic::Policy::TestingAndDebugging::RequireTestLabels]
|
||||
#add_themes = knib
|
||||
#modules = Test::Exception Test::More
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitInterpolationOfLiterals]
|
||||
add_themes = knib
|
||||
allow_if_string_contains_single_quote = 1
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitLongChainsOfMethodCalls]
|
||||
add_themes = knib
|
||||
max_chain_length = 3
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers]
|
||||
# Not yet configured completely.
|
||||
add_themes = knib
|
||||
# 14.01.2016 2 is considered a magic number as well after a discussion with the team
|
||||
allowed_values = -1 0 1
|
||||
|
||||
# 11.02.2016 policy disabled after a discussion with the team
|
||||
#[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMismatchedOperators]
|
||||
#add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitQuotesAsQuotelikeOperatorDelimiters]
|
||||
add_themes = knib
|
||||
back_quote_allowed_operators =
|
||||
double_quote_allowed_operators =
|
||||
single_quote_allowed_operators =
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireConstantVersion]
|
||||
add_themes =
|
||||
allow_version_without_use_on_same_line = 1
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators]
|
||||
add_themes = knib
|
||||
min_value = 10000
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireQuotedHeredocTerminator]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitConditionalDeclarations]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitLocalVars]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitMatchVars]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitUnusedVariables]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireInitializationForLocalVars]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLexicalLoopIterators]
|
||||
add_themes = knib
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars]
|
||||
add_themes = knib
|
||||
allow =
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireNegativeIndices]
|
||||
add_themes = knib
|
258
lib/Test/Expander.pm
Normal file
258
lib/Test/Expander.pm
Normal file
@ -0,0 +1,258 @@
|
||||
## no critic (ProhibitStringyEval ProhibitSubroutinePrototypes RequireLocalizedPunctuationVars)
|
||||
package Test::Expander;
|
||||
|
||||
our $VERSION = '1.0.0'; ## no critic (RequireUseStrict, RequireUseWarnings)
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline portable recursion);
|
||||
no warnings qw(experimental);
|
||||
|
||||
use Const::Fast;
|
||||
use File::chdir;
|
||||
use File::Temp qw(tempdir tempfile);
|
||||
use Importer;
|
||||
use Path::Tiny qw(cwd path);
|
||||
use Scalar::Readonly qw(readonly_on);
|
||||
use Test::Files;
|
||||
use Test::Output;
|
||||
use Test::Warn;
|
||||
use Test2::Tools::Explain;
|
||||
use Test2::V0 ();
|
||||
|
||||
use Test::Expander::Constants qw(
|
||||
$ANY_EXTENSION
|
||||
$CLASS_HIERARCHY_LEVEL
|
||||
$ERROR_WAS
|
||||
$FALSE
|
||||
$EXCEPTION_PREFIX
|
||||
$INVALID_ENV_ENTRY
|
||||
$INVALID_VALUE
|
||||
$NEW_FAILED $NEW_SUCCEEDED
|
||||
$REPLACEMENT
|
||||
$REQUIRE_DESCRIPTION $REQUIRE_IMPLEMENTATION
|
||||
$SEARCH_PATTERN
|
||||
$TOP_DIR_IN_PATH
|
||||
$TRUE
|
||||
$UNKNOWN_OPTION
|
||||
$USE_DESCRIPTION $USE_IMPLEMENTATION
|
||||
$VERSION_NUMBER
|
||||
);
|
||||
|
||||
readonly_on($VERSION);
|
||||
|
||||
our ($CLASS, $METHOD, $METHOD_REF, $TEMP_DIR, $TEMP_FILE);
|
||||
our @EXPORT = (
|
||||
@{Const::Fast::EXPORT},
|
||||
@{Test::Files::EXPORT},
|
||||
@{Test::Output::EXPORT},
|
||||
@{Test::Warn::EXPORT},
|
||||
@{Test2::Tools::Explain::EXPORT},
|
||||
@{Test2::V0::EXPORT},
|
||||
qw(tempdir tempfile),
|
||||
qw(cwd path),
|
||||
qw($CLASS $METHOD $METHOD_REF $TEMP_DIR $TEMP_FILE),
|
||||
qw(BAIL_OUT dies_ok is_deeply lives_ok new_ok require_ok throws_ok use_ok),
|
||||
);
|
||||
|
||||
*BAIL_OUT = \&bail_out; # Explicit "sub BAIL_OUT" would be untestable
|
||||
|
||||
sub dies_ok (&;$) {
|
||||
my ($coderef, $description) = @_;
|
||||
|
||||
eval { $coderef->() };
|
||||
|
||||
return ok($@, $description);
|
||||
}
|
||||
|
||||
sub import {
|
||||
my ($class, @exports) = @_;
|
||||
|
||||
my %options;
|
||||
while (my $optionName = shift(@exports)) {
|
||||
given ($optionName) {
|
||||
when ('-tempdir') {
|
||||
my $optionValue = shift(@exports);
|
||||
die(sprintf($INVALID_VALUE, $optionName, $optionValue)) if ref($optionValue) ne 'HASH';
|
||||
$TEMP_DIR = tempdir(CLEANUP => 1, %$optionValue);
|
||||
}
|
||||
when ('-tempfile') {
|
||||
my $optionValue = shift(@exports);
|
||||
die(sprintf($INVALID_VALUE, $optionName, $optionValue)) if ref($optionValue) ne 'HASH';
|
||||
my $fileHandle;
|
||||
($fileHandle, $TEMP_FILE) = tempfile(UNLINK => 1, %$optionValue);
|
||||
}
|
||||
when (/^-\w/) {
|
||||
$options{$optionName} = shift(@exports);
|
||||
}
|
||||
default {
|
||||
die(sprintf($UNKNOWN_OPTION, $optionName, shift(@exports) // ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my $testFile = path((caller(2))[1]) =~ s{^/}{}r; ## no critic (ProhibitMagicNumbers)
|
||||
my ($testRoot) = $testFile =~ $TOP_DIR_IN_PATH;
|
||||
unless (exists($options{-target})) {
|
||||
my $testee = path($testFile)->relative($testRoot)->parent;
|
||||
$options{-target} = join('::', split(qr{/}, $testee))
|
||||
if grep { path($_)->child($testee . '.pm')->is_file } @INC;
|
||||
}
|
||||
|
||||
$METHOD = path($testFile)->basename($ANY_EXTENSION);
|
||||
my $startDir = cwd();
|
||||
_setEnv($METHOD, $options{-target}, $testFile);
|
||||
|
||||
Test2::V0->import(%options);
|
||||
$METHOD_REF = '-target' ~~ %options ? $CLASS->can($METHOD) : undef;
|
||||
$METHOD = undef unless($METHOD_REF);
|
||||
|
||||
readonly_on($CLASS) if $CLASS;
|
||||
readonly_on($METHOD) if $METHOD;
|
||||
readonly_on($METHOD_REF) if $METHOD_REF;
|
||||
readonly_on($TEMP_DIR) if $TEMP_DIR;
|
||||
readonly_on($TEMP_FILE) if $TEMP_FILE;
|
||||
|
||||
Importer->import_into($class, scalar(caller), ());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub is_deeply ($$;$@) {
|
||||
my ($got, $expected, $title) = @_;
|
||||
|
||||
return is($got, $expected, $title);
|
||||
}
|
||||
|
||||
sub lives_ok (&;$) {
|
||||
my ($coderef, $description) = @_;
|
||||
|
||||
eval { $coderef->() };
|
||||
|
||||
return ok(!$@, $description);
|
||||
}
|
||||
|
||||
sub new_ok {
|
||||
my ($class, $args) = @_;
|
||||
|
||||
$args ||= [];
|
||||
my $obj = eval { $class->new(@$args) };
|
||||
ok(!$@, _newTestMessage($class));
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
sub require_ok {
|
||||
my ($module) = @_;
|
||||
|
||||
my $package = caller;
|
||||
my $requireResult = eval(sprintf($REQUIRE_IMPLEMENTATION, $package, $module));
|
||||
ok($requireResult, sprintf($REQUIRE_DESCRIPTION, $module, _error()));
|
||||
|
||||
return $requireResult;
|
||||
}
|
||||
|
||||
sub throws_ok (&$;$) {
|
||||
my ($coderef, $expecting, $description) = @_;
|
||||
|
||||
eval { $coderef->() };
|
||||
|
||||
return like($@, qr/$expecting/, $description);
|
||||
}
|
||||
|
||||
sub use_ok ($;@) {
|
||||
my ($module, @imports) = @_;
|
||||
|
||||
my ($package, $filename, $line) = caller(0);
|
||||
$filename =~ y/\n\r/_/; # taken over from Test::More
|
||||
|
||||
my $requireResult = eval(sprintf($USE_IMPLEMENTATION, $package, $module, _useImports(\@imports)));
|
||||
ok(
|
||||
$requireResult,
|
||||
sprintf($USE_DESCRIPTION, $module, _error($SEARCH_PATTERN, sprintf($REPLACEMENT, $filename, $line)))
|
||||
);
|
||||
|
||||
return $requireResult;
|
||||
}
|
||||
|
||||
sub _error {
|
||||
my ($searchString, $replacementString) = @_;
|
||||
|
||||
return '' if $@ eq '';
|
||||
|
||||
my $error = $ERROR_WAS . $@ =~ s/\n$//mr;
|
||||
$error =~ s/$searchString/$replacementString/m if defined($searchString);
|
||||
return $error;
|
||||
}
|
||||
|
||||
sub _newTestMessage {
|
||||
my ($class) = @_;
|
||||
|
||||
return $@ ? sprintf($NEW_FAILED, $class, _error()) : sprintf($NEW_SUCCEEDED, $class, $class);
|
||||
}
|
||||
|
||||
sub _readEnvFile {
|
||||
my ($envFile) = @_;
|
||||
|
||||
my @lines = path($envFile)->lines({ chomp => 1 });
|
||||
my %env;
|
||||
while (my ($index, $line) = each(@lines)) {
|
||||
next unless $line =~ /^ (?<name> \w+) \s* = \s* (?<value> \S .*)/x;
|
||||
$env{$+{name}} = eval($+{value});
|
||||
die(sprintf($INVALID_ENV_ENTRY, $index, $envFile, $line, $@)) if $@;
|
||||
}
|
||||
|
||||
return \%env;
|
||||
}
|
||||
|
||||
sub _setEnv {
|
||||
my ($method, $class, $testFile) = @_;
|
||||
|
||||
my $envFound = $FALSE;
|
||||
my $newEnv = {};
|
||||
{
|
||||
local $CWD = $testFile =~ s{/.*}{}r; ## no critic (ProhibitLocalVars)
|
||||
($envFound, $newEnv) = _setEnvHierarchically($class, $envFound, $newEnv);
|
||||
}
|
||||
|
||||
my $envFile = $testFile =~ s/$ANY_EXTENSION/.env/r;
|
||||
|
||||
if (path($envFile)->is_file) {
|
||||
$envFound = $TRUE unless $envFound;
|
||||
my $methodEnv = _readEnvFile($envFile);
|
||||
@$newEnv{keys(%$methodEnv)} = values(%$methodEnv)
|
||||
}
|
||||
|
||||
%ENV = %$newEnv if $envFound;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub _setEnvHierarchically {
|
||||
my ($class, $envFound, $newEnv) = @_;
|
||||
|
||||
return ($envFound, $newEnv) unless $class;
|
||||
|
||||
my $classTopLevel;
|
||||
($classTopLevel, $class) = $class =~ $CLASS_HIERARCHY_LEVEL;
|
||||
|
||||
return ($FALSE, {}) unless path($classTopLevel)->is_dir;
|
||||
|
||||
my $envFile = $classTopLevel . '.env';
|
||||
if (path($envFile)->is_file) {
|
||||
$envFound = $TRUE unless $envFound;
|
||||
$newEnv = { %$newEnv, %{ _readEnvFile($envFile) } };
|
||||
}
|
||||
|
||||
local $CWD = $classTopLevel; ## no critic (ProhibitLocalVars)
|
||||
return _setEnvHierarchically($class, $envFound, $newEnv);
|
||||
}
|
||||
|
||||
sub _useImports {
|
||||
my ($imports) = @_;
|
||||
|
||||
return @$imports == 1 && $imports->[0] =~ $VERSION_NUMBER ? ' ' . $imports->[0] : '';
|
||||
}
|
||||
|
||||
1;
|
373
lib/Test/Expander.pod
Normal file
373
lib/Test/Expander.pod
Normal file
@ -0,0 +1,373 @@
|
||||
=pod
|
||||
|
||||
=head1 NAME
|
||||
|
||||
B<Test::Expander> - Expansion of test functionalities that appear to be frequently used while testing.
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
# Tries to determine both class and method to be tested automatically,
|
||||
# does not create any temporary directory:
|
||||
use Test::Expander;
|
||||
|
||||
# Tries to determine both class and method to be tested automatically,
|
||||
# does not create any temporary directory,
|
||||
# passes the option '-srand' to Test::V0 changing the random seed to the current time in seconds:
|
||||
use Test::Expander -srand => time;
|
||||
|
||||
# Tries to determine only the method to be tested automatically, class is explicitly supplied,
|
||||
# a temporary directory is created with name corresponing to the template supplied:
|
||||
use Test::Expander -target => 'My::Class', -tempdir => { TEMPLATE => 'my_dir.XXXXXXXX' };
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
B<Test::Expander> combines all advanced possibilities provided by L<Test2::V0|https://metacpan.org/pod/Test2::V0>
|
||||
with some specific functions available in the older module L<Test::More|https://metacpan.org/pod/Test::More> only
|
||||
(which allows a smooth migration from L<Test::More|https://metacpan.org/pod/Test::More>-based tests to
|
||||
L<Test2::V0|https://metacpan.org/pod/Test2::V0>-based ones) and handy functions from some other modules
|
||||
often used in test suites.
|
||||
|
||||
Furthermore, this module provides a recognition of class to be tested (see variable B<$CLASS> below) so that
|
||||
in contrast to L<Test2::V0|https://metacpan.org/pod/Test2::V0> you do not need to specify this explicitly
|
||||
if the path to the test file is in accordance with the name of class to be tested.
|
||||
|
||||
A similar recognition is provided in regard to the method / subroutine to be tested
|
||||
(see variables B<$METHOD> and B<METHOD_REF> below) if the base name (without extension) of test file is
|
||||
identical with the name of this method / subroutine.
|
||||
|
||||
Finally, a configurable setting of specific environment variables is provided so that
|
||||
there is no need to hard-code this in the test itself.
|
||||
|
||||
For the time being the following options are accepted by B<Test::Expander>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
Options specific for this module only:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
B<-target> - identical with the same-named option of L<Test2::V0|https://metacpan.org/pod/Test2::V0> and
|
||||
has the same purpose namely the explicit definition of class to be tested as a value of this option;
|
||||
|
||||
=item
|
||||
|
||||
B<-tempdir> - activates creation of a temporary directory by the function B<tempdir> provided by
|
||||
L<File::Temp::tempdir|https://metacpan.org/pod/File::Temp>.
|
||||
|
||||
=item
|
||||
|
||||
B<-tempfile> - activates creation of a temporary file by the method B<tempfile> provided by
|
||||
L<File::Temp::tempfile|https://metacpan.org/pod/File::Temp>.
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
All other valid options (i.e. arguments starting with the dash sign B<->) are forwarded to
|
||||
L<Test2::V0|https://metacpan.org/pod/Test2::V0> along with their values.
|
||||
|
||||
=item
|
||||
|
||||
If an argument cannot be recognized as an option, an exception is raised.
|
||||
|
||||
=back
|
||||
|
||||
The proper application of B<Test::Expander> implies that is is used as the very first in your unit test.
|
||||
|
||||
The only exception currently known is the case, when some actions performed on the module level
|
||||
(e.g. determination of constants) base on results of other actions (e.g. mocking of built-ins).
|
||||
|
||||
To explain this let us assume that your unit test file should mock the built-in B<close>
|
||||
to verify if the testee properly reacts both on its success and failure.
|
||||
For this purpose a reasonable implementation might look as follows:
|
||||
|
||||
my $closeSuccess = 1;
|
||||
BEGIN {
|
||||
*CORE::GLOBAL::close = sub (*) { return $closeSuccess ? CORE::close($_[0]) : 0 };
|
||||
}
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
Furthermore, the automated recognition of name of class to be tested can only work properly
|
||||
if the test file is located in the corresponding subdirectory of B<t>, or B<xt>, or any other folder
|
||||
containing a bunch of test files.
|
||||
For instance, if the class to be tested is I<Foo::Bar::Baz>, then the folder with test files
|
||||
related to this class should be B<t/>I<Foo>B</>I<Bar>B</>I<Baz> or B<xt/>I<Foo>B</>I<Bar>B</>I<Baz>
|
||||
(the name of the top-level directory in this relative name - B<t>, or B<xt>, or B<my_test> is not important) -
|
||||
otherwise the module name cannot be put into the exported variable B<$CLASS> and, if you want to use this variable,
|
||||
should be supplied as a value of the option B<-target>:
|
||||
|
||||
use Test::Expander -target => 'Foo::Bar::Baz';
|
||||
|
||||
What is more, the automated recognition of name of method / subroutine to be tested can only work properly
|
||||
if the base name of the test file without extension (usually B<.t>) is equal to the method / subroutine
|
||||
name. In other words, this recognition only works if the file containing the class mentioned above exists and
|
||||
if this class has the method / subroutine with the same name as the test file base name without extension.
|
||||
If this is the case, the exported variables B<$METHOD> and B<$METHOD_REF> contain the name of method / subroutine
|
||||
to be tested and its reference, correspondingly, otherwise both variables are undefined.
|
||||
|
||||
Finally, B<Test::Expander> supports testing inside of a clean environment containing only some clearly
|
||||
specified environment variables required for the particular test.
|
||||
Names and values of these environment variables should be configured in files,
|
||||
which names are identical with pathes to single class levels or method to be tested,
|
||||
and the extension is always B<.env>.
|
||||
For instance, if the test file name is B<t/Foo/Bar/Baz/myMethod.t>, the following approach is applied:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
if the file B<t/Foo.env> exists, its content is used for the initialization of test environment,
|
||||
|
||||
=item
|
||||
|
||||
if the file B<t/Foo/Bar.env> exists, its content is used either for extension of test environment
|
||||
initialized in the previous step or for its initialization if the file B<t/Foo.env> does not exist,
|
||||
|
||||
=item
|
||||
|
||||
if the file B<t/Foo/Bar/Baz.env> exists, its content is used either for extension of test
|
||||
environment initialized in one of the previous steps or for its initialization if neither the file B<t/Foo.env> nor
|
||||
the file B<t/Foo/Bar.env> exists,
|
||||
|
||||
=item
|
||||
|
||||
if the file B<t/Foo/Bar/Baz/myMethod.env> exists, its content will be used either for extension of test environment
|
||||
initialized in one of the previous steps or for its initialization if no one of B<.env> files mentioned above exists.
|
||||
|
||||
=back
|
||||
|
||||
If the B<.env> files existing on different levels have identical names of environment variables,
|
||||
the priority is the higher the later they have been detected.
|
||||
I.e. B<VAR = 'VALUE0'> in B<t/Foo/Bar/Baz/myMethod.env> overwrites B<VAR = 'VALUE1'> in B<t/Foo/Bar/Baz.env>.
|
||||
|
||||
If no one of these B<.env> files exists, the environment will not be changed by B<Test::Expander>
|
||||
during the execution of B<t/Foo/Bar/Baz/myMethod.t>.
|
||||
|
||||
An environment configuration file (B<.env> file) is a line-based text file,
|
||||
which content is interpreted as follows:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
if such files don't exist, the B<%ENV> hash remains unchanged;
|
||||
|
||||
=item
|
||||
|
||||
otherwise, if at least one of such files exists, the B<%ENV> gets emptied (without localization) and
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
lines not matching the RegEx B</^\w+\s\*=\s\*\S/> (some alphanumeric characters representing a name of
|
||||
environment variable, optional blanks, the equal sign, again optional blanks and at least one non-blank
|
||||
character representing the first sign of environment variable value) are skipped;
|
||||
|
||||
=item
|
||||
|
||||
in all other lines the value of the environment variable is everything from the first non-blank
|
||||
character after the equal sign until end of the line;
|
||||
|
||||
=item
|
||||
|
||||
the value of the environment variable is evaluated by the L<string eval|https://perldoc.perl.org/functions/eval>
|
||||
so that
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
constant values must be quoted;
|
||||
|
||||
=item
|
||||
|
||||
variables and subroutines must not be quoted:
|
||||
|
||||
NAME_CONST = 'VALUE'
|
||||
NAME_VAR = $KNIB::App::MyApp::Constants::ABC
|
||||
NAME_FUNC = join(' ', $KNIB::App::MyApp::Constants::DEF)
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
Another feature frequently applied inside of test suites is creation of a temporary directory / file used as an
|
||||
isolated container for some testing actions.
|
||||
The module options B<-tempdir> and B<-tempfile> fully synactically compatible with
|
||||
L<File::Temp::tempdir|https://metacpan.org/pod/File::Temp#FUNCTIONS> /
|
||||
L<File::Temp::tempfile|https://metacpan.org/pod/File::Temp#FUNCTIONS> make sure that such temporary
|
||||
directory / file are created after B<use Test::Expander> and their names are stored in the variables
|
||||
B<$TEMP_DIR> / B<$TEMP_FILE>, correspondingly.
|
||||
Both temporary directory and file are removed by default after execution.
|
||||
|
||||
All functions provided by this module are exported by default. These and the exported variables are:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
all functions exported by default from L<Test2::V0|https://metacpan.org/pod/Test2::V0>,
|
||||
|
||||
=item
|
||||
|
||||
all functions exported by default from L<Test::Files|https://metacpan.org/pod/Test::Files>,
|
||||
|
||||
=item
|
||||
|
||||
all functions exported by default from L<Test::Output|https://metacpan.org/pod/Test::Output>,
|
||||
|
||||
=item
|
||||
|
||||
all functions exported by default from L<Test::Warn|https://metacpan.org/pod/Test::Warn>,
|
||||
|
||||
=item
|
||||
|
||||
some functions exported by default from L<Test::More|https://metacpan.org/pod/Test::More>
|
||||
and often used in older tests but not supported by L<Test2::V0|https://metacpan.org/pod/Test2::V0>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
BAIL_OUT,
|
||||
|
||||
=item
|
||||
|
||||
is_deeply,
|
||||
|
||||
=item
|
||||
|
||||
new_ok,
|
||||
|
||||
=item
|
||||
|
||||
require_ok,
|
||||
|
||||
=item
|
||||
|
||||
use_ok,
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
some functions exported by default from L<Test::Exception|https://metacpan.org/pod/Test::Exception>
|
||||
and often used in older tests but not supported by L<Test2::V0|https://metacpan.org/pod/Test2::V0>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
dies_ok,
|
||||
|
||||
=item
|
||||
|
||||
explain,
|
||||
|
||||
=item
|
||||
|
||||
lives_ok,
|
||||
|
||||
=item
|
||||
|
||||
throws_ok,
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
function exported by default from L<Const::Fast|https://metacpan.org/pod/Const::Fast>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
const,
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
some functions exported by request from L<File::Temp|https://metacpan.org/pod/File::Temp>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
tempdir,
|
||||
|
||||
=item
|
||||
|
||||
tempfile,
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
some functions exported by request from L<Path::Tiny|https://metacpan.org/pod/Path::Tiny>:
|
||||
|
||||
=over 2
|
||||
|
||||
=item
|
||||
|
||||
cwd,
|
||||
|
||||
=item
|
||||
|
||||
path,
|
||||
|
||||
=back
|
||||
|
||||
=item
|
||||
|
||||
variable B<$CLASS> containing the name of class to be tested,
|
||||
|
||||
=item
|
||||
|
||||
variable B<$METHOD> containing the name of method to be tested,
|
||||
|
||||
=item
|
||||
|
||||
variable B<$METHOD_REF> containing the reference to subroutine to be tested.
|
||||
|
||||
=item
|
||||
|
||||
variable B<$TEMP_DIR> containing the name of a temporary directory created at compile time
|
||||
if the option B<-tempdir> was supplied.
|
||||
|
||||
=item
|
||||
|
||||
variable B<$TEMP_FILE> containing the name of a temporary file created at compile time
|
||||
if the option B<-tempfile> was supplied.
|
||||
|
||||
=back
|
||||
|
||||
All variables mentioned above are read-only if they are defined after B<use Test::Expander ...>.
|
||||
|
||||
=head1 AUTHOR
|
||||
|
||||
Jurij Fajnberg, <fajnbergj at gmail.com>
|
||||
|
||||
=head1 BUGS
|
||||
|
||||
Please report any bugs or feature requests through the web interface at
|
||||
L<https://github.com/jsf116/Test-Expander/issues>.
|
||||
|
||||
=head1 COPYRIGHT AND LICENSE
|
||||
|
||||
=head2 LICENSE AND COPYRIGHT
|
||||
|
||||
Copyright (c) 2021 Jurij Fajnberg
|
||||
|
||||
This program is free software; you can redistribute it and/or modify it under the same terms
|
||||
as the Perl 5 programming language system itself.
|
||||
|
||||
=cut
|
35
lib/Test/Expander/Constants.pm
Normal file
35
lib/Test/Expander/Constants.pm
Normal file
@ -0,0 +1,35 @@
|
||||
## no critic (RequireVersionVar)
|
||||
package Test::Expander::Constants;
|
||||
|
||||
use v5.14.2;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline portable recursion);
|
||||
|
||||
use Const::Fast;
|
||||
use Exporter qw(import);
|
||||
use PadWalker qw(peek_our);
|
||||
|
||||
const our $ANY_EXTENSION => qr/ \. [^.]+ $/x;
|
||||
const our $CLASS_HIERARCHY_LEVEL => qr/^( \w+ ) (?: :: ( .+ ) )?/x;
|
||||
const our $ERROR_WAS => ' Error was: ';
|
||||
const our $FALSE => 0;
|
||||
const our $EXCEPTION_PREFIX => 'BEGIN failed--compilation aborted at ';
|
||||
const our $INVALID_ENV_ENTRY => "Erroneous line %d of '%s' containing '%s': %s\n";
|
||||
const our $INVALID_VALUE => "Option '%s' passed along with invalid value '%s'\n";
|
||||
const our $NEW_FAILED => '%s->new died.%s';
|
||||
const our $NEW_SUCCEEDED => "An object of class '%s' isa '%s'";
|
||||
const our $REPLACEMENT => $EXCEPTION_PREFIX . '%s line %s.';
|
||||
const our $REQUIRE_DESCRIPTION => 'require %s;%s';
|
||||
const our $REQUIRE_IMPLEMENTATION => 'package %s; require %s';
|
||||
const our $SEARCH_PATTERN => $EXCEPTION_PREFIX . '.*$';
|
||||
const our $TOP_DIR_IN_PATH => qr{^ ( [^/]+ )}x;
|
||||
const our $TRUE => 1;
|
||||
const our $UNKNOWN_OPTION => "Unknown option '%s' => '%s' supplied.\n";
|
||||
const our $USE_DESCRIPTION => 'use %s;%s';
|
||||
const our $USE_IMPLEMENTATION => 'package %s; use %s%s; 1';
|
||||
const our $VERSION_NUMBER => qr/^ \d+ (?: \. \d+ )* $/x;
|
||||
|
||||
push(our @EXPORT_OK, keys(%{peek_our(0)}));
|
||||
|
||||
1;
|
325
t/.perlcriticrc
Normal file
325
t/.perlcriticrc
Normal file
@ -0,0 +1,325 @@
|
||||
# You may disable specific policies appending the following annotation
|
||||
#
|
||||
# ## no critic (..., ...)
|
||||
#
|
||||
# to the corresponding code line. To direct perlcritic to ignore the
|
||||
# "## no critic" annotations, use the --force option.
|
||||
|
||||
# Policies shipped with Perl::Critic 1.125 were considered for the below
|
||||
# defintion of the new policy theme "critic".
|
||||
|
||||
severity = brutal
|
||||
theme = critic
|
||||
verbose = %f: %m at line %l, column %c. (Policy: %p)\n
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitLvalueSubstr]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitStringyEval]
|
||||
add_themes = critic
|
||||
allow_includes = 1
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitStringySplit]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalCan]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUniversalIsa]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitUselessTopic]
|
||||
# KNOWN BUGS: This policy flags a false positive on reverse() called in list
|
||||
# context, since reverse() in list context does not assume $_.
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidGrep]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::ProhibitVoidMap]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireBlockGrep]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireBlockMap]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::BuiltinFunctions::RequireSimpleSortBlock]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ClassHierarchies::ProhibitExplicitISA]
|
||||
# Note: Some people prefer parent over base.
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::ProhibitHardTabs]
|
||||
add_themes = critic
|
||||
allow_leading_tabs = 0
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::ProhibitTrailingWhitespace]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::CodeLayout::RequireConsistentNewlines]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitCStyleForLoops]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitCascadingIfElse]
|
||||
add_themes = critic
|
||||
max_elsif = 1
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitDeepNests]
|
||||
# Martin Fowler's book "Refactoring: Improving The Design of Existing Code".
|
||||
add_themes = critic
|
||||
max_nests = 5
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitLabelsWithSpecialBlockNames]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitMutatingListFunctions]
|
||||
# Read the LIMITATIONS that this policy has.
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitNegativeExpressionsInUnlessAndUntilConditions]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ControlStructures::ProhibitUnreachableCode]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Documentation::PodSpelling]
|
||||
add_themes =
|
||||
|
||||
[Perl::Critic::Policy::Documentation::RequirePackageMatchesPodName]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitBacktickOperators]
|
||||
add_themes = critic
|
||||
only_in_void_context = 1
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitBarewordFileHandles]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitExplicitStdin]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitInteractiveTest]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitJoinedReadline]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitReadlineInForLoop]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::ProhibitTwoArgOpen]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireBriefOpen]
|
||||
# http://www.perlmonks.org/?node_id=1134785
|
||||
add_themes = critic
|
||||
lines = 9
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireCheckedSyscalls]
|
||||
# Covers the policies
|
||||
# Perl::Critic::Policy::InputOutput::RequireCheckedClose and
|
||||
# Perl::Critic::Policy::InputOutput::RequireCheckedOpen
|
||||
add_themes = critic
|
||||
exclude_functions = print say
|
||||
functions = :builtins
|
||||
|
||||
[Perl::Critic::Policy::InputOutput::RequireEncodingWithUTF8Layer]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUnrestrictedNoCritic]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Miscellanea::ProhibitUselessNoCritic]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitAutomaticExportation]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitConditionalUseStatements]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitEvilModules]
|
||||
add_themes = critic
|
||||
modules = Class::ISA Error Pod::Plainer Shell Switch
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitExcessMainComplexity]
|
||||
# http://en.wikipedia.org/wiki/Cyclomatic_complexity
|
||||
add_themes = critic
|
||||
max_mccabe = 20
|
||||
|
||||
[Perl::Critic::Policy::Modules::ProhibitMultiplePackages]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireBarewordIncludes]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireEndWithOne]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireFilenameMatchesPackage]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Modules::RequireNoMatchVarsWithUseEnglish]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Objects::ProhibitIndirectSyntax]
|
||||
add_themes = critic
|
||||
# The new() subroutine is configured by default; any additional forbid values
|
||||
# are in addition to new().
|
||||
forbid = create destroy
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitFixedStringMatches]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitSingleCharAlternation]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUnusedCapture]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUnusualDelimiters]
|
||||
add_themes = critic
|
||||
allow_all_brackets = 0
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::ProhibitUselessTopic]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::RegularExpressions::RequireBracesForMultiline]
|
||||
add_themes = critic
|
||||
allow_all_brackets = 0
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitAmpersandSigils]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitBuiltinHomonyms]
|
||||
# Read the CAVEATS.
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExcessComplexity]
|
||||
# http://en.wikipedia.org/wiki/Cyclomatic_complexity
|
||||
add_themes = critic
|
||||
max_mccabe = 20
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitExplicitReturnUndef]
|
||||
# http://perlmonks.org/index.pl?node_id=741847
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitManyArgs]
|
||||
add_themes = critic
|
||||
max_arguments = 5
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitNestedSubs]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitReturnSort]
|
||||
# KNOWN BUGS: This Policy is not sensitive to the wantarray() function.
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitSubroutinePrototypes]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProhibitUnusedPrivateSubroutines]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::ProtectPrivateSubs]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::RequireArgUnpacking]
|
||||
add_themes = critic
|
||||
allow_delegation_to = SUPER:: NEXT::
|
||||
allow_subscripts = 0
|
||||
short_subroutine_statements = 0
|
||||
|
||||
[Perl::Critic::Policy::Subroutines::RequireFinalReturn]
|
||||
add_themes = critic
|
||||
terminal_funcs = return carp croak die exec exit goto throw
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoStrict]
|
||||
add_themes = critic
|
||||
allow = refs
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitNoWarnings]
|
||||
add_themes = critic
|
||||
allow_with_category_restriction = 1
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::ProhibitProlongedStrictureOverride]
|
||||
add_themes = critic
|
||||
statements = 3
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseStrict]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::TestingAndDebugging::RequireUseWarnings]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitInterpolationOfLiterals]
|
||||
add_themes = critic
|
||||
allow_if_string_contains_single_quote = 1
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitLeadingZeros]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitLongChainsOfMethodCalls]
|
||||
add_themes = critic
|
||||
max_chain_length = 3
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMagicNumbers]
|
||||
# Not yet configured completely.
|
||||
add_themes = critic
|
||||
allowed_values = -1 0 1
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitMixedBooleanOperators]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitQuotesAsQuotelikeOperatorDelimiters]
|
||||
add_themes = critic
|
||||
back_quote_allowed_operators =
|
||||
double_quote_allowed_operators =
|
||||
single_quote_allowed_operators =
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::ProhibitSpecialLiteralHeredocTerminator]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireConstantVersion]
|
||||
add_themes = critic
|
||||
allow_version_without_use_on_same_line = 1
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireNumberSeparators]
|
||||
add_themes = critic
|
||||
min_value = 10000
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireQuotedHeredocTerminator]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::ValuesAndExpressions::RequireUpperCaseHeredocTerminator]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitConditionalDeclarations]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitLocalVars]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitMatchVars]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::ProhibitUnusedVariables]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireInitializationForLocalVars]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLexicalLoopIterators]
|
||||
add_themes = critic
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireLocalizedPunctuationVars]
|
||||
add_themes = critic
|
||||
allow =
|
||||
|
||||
[Perl::Critic::Policy::Variables::RequireNegativeIndices]
|
||||
add_themes = critic
|
4
t/.proverc
Normal file
4
t/.proverc
Normal file
@ -0,0 +1,4 @@
|
||||
--lib
|
||||
--recurse
|
||||
--shuffle
|
||||
-I.
|
4
t/.proverc-cover
Normal file
4
t/.proverc-cover
Normal file
@ -0,0 +1,4 @@
|
||||
--lib
|
||||
--recurse
|
||||
--shuffle
|
||||
-I.
|
14
t/Test/Expander/Boilerplate.pm
Normal file
14
t/Test/Expander/Boilerplate.pm
Normal file
@ -0,0 +1,14 @@
|
||||
package t::Test::Expander::Boilerplate;
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
sub new {
|
||||
my ($class, @args) = @_;
|
||||
|
||||
return bless([\@args], $class);
|
||||
}
|
||||
|
||||
1;
|
12
t/Test/Expander/NoCLASS/NoMETHOD.t
Normal file
12
t/Test/Expander/NoCLASS/NoMETHOD.t
Normal file
@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
is($CLASS, undef, 'there is no class corresponding to this test file');
|
||||
|
||||
done_testing();
|
28
t/Test/Expander/_error.t
Normal file
28
t/Test/Expander/_error.t
Normal file
@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env perl
|
||||
## no critic (ProtectPrivateSubs RequireLocalizedPunctuationVars)
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Expander::Constants qw($ERROR_WAS);
|
||||
use constant TEST_CASES => {
|
||||
'no exception' => { exception => '', args => [], output => '' },
|
||||
'exception raised, no replacement required' => { exception => 'ABC', args => [], output => "${ERROR_WAS}ABC" },
|
||||
'exception raised, replacement required' => { exception => 'ABC', args => [qw(B b)], output => "${ERROR_WAS}AbC" },
|
||||
};
|
||||
use Test::Builder::Tester tests => scalar(keys(%{TEST_CASES()}));
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
foreach my $title (keys(%{TEST_CASES()})) {
|
||||
test_out("ok 1 - $title");
|
||||
$@ = TEST_CASES->{$title}->{exception};
|
||||
is(
|
||||
Test::Expander::_error(@{TEST_CASES->{$title}->{args}}),
|
||||
TEST_CASES->{$title}->{output},
|
||||
$title
|
||||
);
|
||||
test_test($title);
|
||||
}
|
24
t/Test/Expander/_newTestMessage.t
Normal file
24
t/Test/Expander/_newTestMessage.t
Normal file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env perl
|
||||
## no critic (ProtectPrivateSubs RequireLocalizedPunctuationVars)
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Expander::Constants qw($NEW_FAILED $NEW_SUCCEEDED);
|
||||
use constant TEST_CASES => {
|
||||
"'new' succeeded" => { exception => '', output => $NEW_SUCCEEDED },
|
||||
"'new' failed" => { exception => 'ABC', output => $NEW_FAILED },
|
||||
};
|
||||
use Test::Builder::Tester tests => scalar(keys(%{TEST_CASES()}));
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
foreach my $title (keys(%{TEST_CASES()})) {
|
||||
test_out("ok 1 - $title");
|
||||
$@ = TEST_CASES->{$title}->{exception};
|
||||
my $expected = TEST_CASES->{$title}->{output} =~ s/%s/.*/gr;
|
||||
like(Test::Expander::_newTestMessage('CLASS'), qr/$expected/, $title);
|
||||
test_test($title);
|
||||
}
|
99
t/Test/Expander/_setEnv.t
Normal file
99
t/Test/Expander/_setEnv.t
Normal file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env perl
|
||||
## no critic (RequireLocalizedPunctuationVars)
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use File::chdir;
|
||||
|
||||
use Test::Expander -tempdir => {}, -srand => time;
|
||||
|
||||
can_ok($CLASS, $METHOD);
|
||||
|
||||
ok(-d $TEMP_DIR, "temporary directory '$TEMP_DIR' created");
|
||||
|
||||
my $classPath = $CLASS =~ s{::}{/}gr;
|
||||
my $testPath = path($TEMP_DIR)->child('t');
|
||||
$testPath->child($classPath)->mkpath;
|
||||
|
||||
{
|
||||
local $CWD = $testPath->parent->stringify; ## no critic (ProhibitLocalVars)
|
||||
|
||||
my $testFile = path('t')->child($classPath)->child($METHOD . '.t')->stringify;
|
||||
my $envFile = path('t')->child($classPath)->child($METHOD . '.env');
|
||||
|
||||
is(Test2::Plugin::SRand->from, 'import arg', "random seed is supplied as 'time'");
|
||||
|
||||
subtest 'env variable filled from a variable' => sub {
|
||||
our $var = 'abc';
|
||||
my $name = 'ABC';
|
||||
my $value = '$' . __PACKAGE__ . '::var';
|
||||
$envFile->spew("$name = $value\nJust a comment line");
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
ok(lives { $METHOD_REF->($METHOD, $CLASS, $testFile) }, 'successfully executed');
|
||||
is(\%ENV, { $name => lc($name) }, "'%ENV' has the expected content");
|
||||
};
|
||||
|
||||
subtest 'env variable filled by a self-implemented sub' => sub {
|
||||
my $name = 'ABC';
|
||||
my $value = __PACKAGE__ . "::testEnv(lc('$name'))";
|
||||
$envFile->spew("$name = $value");
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
ok(lives { $METHOD_REF->($METHOD, $CLASS, $testFile) }, 'successfully executed');
|
||||
is(\%ENV, { $name => lc($name) }, "'%ENV' has the expected content");
|
||||
};
|
||||
|
||||
subtest "env variable filled by a 'File::Temp::tempdir'" => sub {
|
||||
my $name = 'ABC';
|
||||
my $value = 'File::Temp::tempdir';
|
||||
$envFile->spew("$name = $value");
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
ok(lives { $METHOD_REF->($METHOD, $CLASS, $testFile) }, 'successfully executed');
|
||||
is([ keys(%ENV) ], [ $name ], "'%ENV' has the expected keys");
|
||||
ok(-d $ENV{$name}, 'temporary directory exists');
|
||||
};
|
||||
|
||||
subtest 'env file does not exist' => sub {
|
||||
$envFile->remove;
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
ok(lives { $METHOD_REF->($METHOD, $CLASS, $testFile) }, 'successfully executed');
|
||||
is(\%ENV, { xxx => 'yyy' }, "'%ENV' remained unchanged");
|
||||
};
|
||||
|
||||
subtest 'directory structure does not correspond to class hierarchy' => sub {
|
||||
$envFile->remove;
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
ok(lives { $METHOD_REF->($METHOD, 'ABC::' . $CLASS, $testFile) }, 'successfully executed');
|
||||
is(\%ENV, { xxx => 'yyy' }, "'%ENV' remained unchanged");
|
||||
};
|
||||
|
||||
subtest 'env files exist on multiple levels' => sub {
|
||||
path($envFile->parent . '.env')->spew("A = '1'\nB = '2'");
|
||||
path($envFile->parent->parent . '.env')->spew("C = '0'");
|
||||
$envFile->spew("C = '3'");
|
||||
%ENV = (xxx => 'yyy');
|
||||
|
||||
local $CWD = $TEMP_DIR; ## no critic (ProhibitLocalVars)
|
||||
ok(lives { $METHOD_REF->($METHOD, $CLASS, $testFile) }, 'successfully executed');
|
||||
is(\%ENV, { A => '1', B => '2', C => '3' }, "'%ENV' has the expected content");
|
||||
};
|
||||
|
||||
subtest 'env file invalid' => sub {
|
||||
my $name = 'ABC';
|
||||
my $value = 'abc->';
|
||||
$envFile->spew("$name = $value");
|
||||
|
||||
like(dies { $METHOD_REF->($METHOD, $CLASS, $testFile) }, qr/syntax error/, 'expected exception raised');
|
||||
};
|
||||
}
|
||||
|
||||
done_testing();
|
||||
|
||||
sub testEnv { return $_[0] } ## no critic (RequireArgUnpacking)
|
22
t/Test/Expander/_useImports.t
Normal file
22
t/Test/Expander/_useImports.t
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env perl
|
||||
## no critic (ProtectPrivateSubs)
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use constant TEST_CASES => {
|
||||
'module version required' => { input => [ '1.22.333' ], output => ' 1.22.333' },
|
||||
'single import but not a module version' => { input => [ 'x' ], output => '' },
|
||||
'multiple imports' => { input => [ qw(x y) ], output => '' },
|
||||
};
|
||||
use Test::Builder::Tester tests => scalar(keys(%{TEST_CASES()}));
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
foreach my $title (keys(%{TEST_CASES()})) {
|
||||
test_out("ok 1 - $title");
|
||||
is(Test::Expander::_useImports(TEST_CASES->{$title}->{input}), TEST_CASES->{$title}->{output}, $title);
|
||||
test_test($title);
|
||||
}
|
16
t/Test/Expander/compare_ok.t
Normal file
16
t/Test/Expander/compare_ok.t
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
my $dir = path(__FILE__)->parent->child($METHOD);
|
||||
my $title = 'execution';
|
||||
test_out("ok 1 - $title");
|
||||
compare_ok($dir->child('got'), $dir->child('expected'), $title);
|
||||
test_test($title);
|
2
t/Test/Expander/compare_ok/expected
Normal file
2
t/Test/Expander/compare_ok/expected
Normal file
@ -0,0 +1,2 @@
|
||||
A
|
||||
BC
|
2
t/Test/Expander/compare_ok/got
Normal file
2
t/Test/Expander/compare_ok/got
Normal file
@ -0,0 +1,2 @@
|
||||
A
|
||||
BC
|
15
t/Test/Expander/dies_ok.t
Normal file
15
t/Test/Expander/dies_ok.t
Normal file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
my $title = 'execution';
|
||||
test_out("ok 1 - $title");
|
||||
dies_ok(sub { die() }, $title);
|
||||
test_test($title);
|
98
t/Test/Expander/import.t
Normal file
98
t/Test/Expander/import.t
Normal file
@ -0,0 +1,98 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
my (@functions, @variables);
|
||||
BEGIN {
|
||||
use Const::Fast;
|
||||
use File::Temp qw(tempdir tempfile);
|
||||
use Path::Tiny qw(cwd path);
|
||||
use Test::Output;
|
||||
use Test::Warn;
|
||||
use Test2::Tools::Explain;
|
||||
use Test2::V0;
|
||||
@functions = (
|
||||
@{Const::Fast::EXPORT},
|
||||
@{Test::Files::EXPORT},
|
||||
@{Test::Output::EXPORT},
|
||||
@{Test::Warn::EXPORT},
|
||||
@{Test2::Tools::Explain::EXPORT},
|
||||
@{Test2::V0::EXPORT},
|
||||
qw(tempdir tempfile),
|
||||
qw(cwd path),
|
||||
qw(BAIL_OUT dies_ok is_deeply lives_ok new_ok require_ok use_ok),
|
||||
);
|
||||
@variables = qw($CLASS $METHOD $METHOD_REF $TEMP_DIR $TEMP_FILE);
|
||||
}
|
||||
|
||||
use Scalar::Readonly qw(readonly_off);
|
||||
use Test::Builder::Tester tests => @functions + @variables + 4;
|
||||
|
||||
use Test::Expander -target => 'Test::Expander',
|
||||
-tempdir => { CLEANUP => 1 },
|
||||
-tempfile => { UNLINK => 1 };
|
||||
use Test::Expander::Constants qw($INVALID_VALUE $UNKNOWN_OPTION);
|
||||
|
||||
foreach my $function (sort @functions) {
|
||||
my $title = "$CLASS->can('$function')";
|
||||
test_out("ok 1 - $title");
|
||||
can_ok($CLASS, $function);
|
||||
test_test($title);
|
||||
}
|
||||
|
||||
foreach my $variable (sort @variables) {
|
||||
my $title = "$CLASS exports '$variable'";
|
||||
test_out("ok 1 - $title");
|
||||
ok(eval("defined($variable)"), $title); ## no critic (ProhibitStringyEval)
|
||||
test_test($title);
|
||||
}
|
||||
|
||||
my $title;
|
||||
my $expected;
|
||||
|
||||
$title = "invalid option value of '-tempdir'";
|
||||
$expected = $INVALID_VALUE =~ s/%s/.+/gr;
|
||||
readonly_off($CLASS);
|
||||
readonly_off($METHOD);
|
||||
readonly_off($METHOD_REF);
|
||||
readonly_off($TEMP_DIR);
|
||||
readonly_off($TEMP_FILE);
|
||||
test_out("ok 1 - $title");
|
||||
like(dies { $CLASS->$METHOD(-tempdir => 1) }, qr/$expected/, $title);
|
||||
test_test($title);
|
||||
|
||||
$title = "invalid option value of '-tempfile'";
|
||||
$expected = $INVALID_VALUE =~ s/%s/.+/gr;
|
||||
readonly_off($CLASS);
|
||||
readonly_off($METHOD);
|
||||
readonly_off($METHOD_REF);
|
||||
readonly_off($TEMP_DIR);
|
||||
readonly_off($TEMP_FILE);
|
||||
test_out("ok 1 - $title");
|
||||
like(dies { $CLASS->$METHOD(-tempfile => 1) }, qr/$expected/, $title);
|
||||
test_test($title);
|
||||
|
||||
$title = 'unknown option with some value';
|
||||
$expected = $UNKNOWN_OPTION =~ s/%s/.+/gr;
|
||||
readonly_off($CLASS);
|
||||
readonly_off($METHOD);
|
||||
readonly_off($METHOD_REF);
|
||||
readonly_off($TEMP_DIR);
|
||||
readonly_off($TEMP_FILE);
|
||||
test_out("ok 1 - $title");
|
||||
like(dies { $CLASS->$METHOD(unknown => 1) }, qr/$expected/, $title);
|
||||
test_test($title);
|
||||
|
||||
$title = 'unknown option without value';
|
||||
$expected = $UNKNOWN_OPTION =~ s/%s/.+/r =~ s/%s//r;
|
||||
readonly_off($CLASS);
|
||||
readonly_off($METHOD);
|
||||
readonly_off($METHOD_REF);
|
||||
readonly_off($TEMP_DIR);
|
||||
readonly_off($TEMP_FILE);
|
||||
test_out("ok 1 - $title");
|
||||
like(dies { $CLASS->$METHOD('unknown') }, qr/$expected/, $title);
|
||||
test_test($title);
|
18
t/Test/Expander/is_deeply.t
Normal file
18
t/Test/Expander/is_deeply.t
Normal file
@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Clone qw(clone);
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
my $title = 'execution';
|
||||
test_out("ok 1 - $title");
|
||||
my $got = bless({A => 0, B => [(0 .. 1)]}, 'some class');
|
||||
my $expected = clone($got);
|
||||
is_deeply($got, $expected, $title);
|
||||
test_test($title);
|
15
t/Test/Expander/lives_ok.t
Normal file
15
t/Test/Expander/lives_ok.t
Normal file
@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
my $title = 'execution';
|
||||
test_out("ok 1 - $title");
|
||||
lives_ok(sub {}, $title);
|
||||
test_test($title);
|
24
t/Test/Expander/new_ok.t
Normal file
24
t/Test/Expander/new_ok.t
Normal file
@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use constant {
|
||||
CLASS => 't::Test::Expander::Boilerplate',
|
||||
TEST_CASES => {
|
||||
'no args' => undef,
|
||||
'args supplied' => [ 0 .. 1 ],
|
||||
},
|
||||
};
|
||||
use Test::Builder::Tester tests => scalar(keys(%{TEST_CASES()}));
|
||||
|
||||
use Test::Expander;
|
||||
use t::Test::Expander::Boilerplate;
|
||||
|
||||
foreach my $title (keys(%{TEST_CASES()})) {
|
||||
test_out("ok 1 - An object of class '@{[CLASS]}' isa '@{[CLASS]}'");
|
||||
new_ok(CLASS, TEST_CASES->{$title}, $title);
|
||||
test_test($title);
|
||||
}
|
17
t/Test/Expander/require_ok.t
Normal file
17
t/Test/Expander/require_ok.t
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
use constant CLASS => 't::Test::Expander::Boilerplate';
|
||||
|
||||
my $title = "require @{[CLASS]}";
|
||||
test_out("ok 1 - $title;");
|
||||
require_ok(CLASS);
|
||||
test_test($title);
|
16
t/Test/Expander/throws_ok.t
Normal file
16
t/Test/Expander/throws_ok.t
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
my $title = 'execution';
|
||||
test_out("ok 1 - $title");
|
||||
my $expected = 'DIE TEST';
|
||||
throws_ok(sub { die($expected) }, $expected, $title);
|
||||
test_test($title);
|
17
t/Test/Expander/use_ok.t
Normal file
17
t/Test/Expander/use_ok.t
Normal file
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use v5.14;
|
||||
use warnings
|
||||
FATAL => qw(all),
|
||||
NONFATAL => qw(deprecated exec internal malloc newline once portable redefine recursion uninitialized);
|
||||
|
||||
use Test::Builder::Tester tests => 1;
|
||||
|
||||
use Test::Expander;
|
||||
|
||||
use constant CLASS => 't::Test::Expander::Boilerplate';
|
||||
|
||||
my $title = "use @{[CLASS]}";
|
||||
test_out("ok 1 - $title;");
|
||||
use_ok(CLASS);
|
||||
test_test($title);
|
Loading…
Reference in New Issue
Block a user