first commit
This commit is contained in:
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;
|
Reference in New Issue
Block a user