App-Test-Generator
view release on metacpan or search on metacpan
# --------------------------------------------------
subtest '_representative_value: integer max-only, default < max â default' => sub {
is(_repr({ type => 'integer', max => 100 }), 42,
'integer max=100, 42 < 100 â default 42 returned');
};
# --------------------------------------------------
# Path: type='integer', max only, default (42) >= max (40) â max-1
# --------------------------------------------------
subtest '_representative_value: integer max-only, default >= max â max-1' => sub {
is(_repr({ type => 'integer', max => 40 }), 39,
'integer max=40, 42 >= 40 â returns max-1=39');
};
# --------------------------------------------------
# Path: type='integer', both min=0 and max=0 â midpoint = int((0+0)/2) = 0
# --------------------------------------------------
subtest '_representative_value: integer min=0 max=0 â midpoint 0' => sub {
is(_repr({ type => 'integer', min => 0, max => 0 }), 0,
'min=max=0 â midpoint int((0+0)/2)=0');
};
# --------------------------------------------------
# Path: type='boolean' â TYPE_DEFAULTS{boolean} = 1
# --------------------------------------------------
subtest '_representative_value: boolean â 1' => sub {
is(_repr({ type => 'boolean' }), 1, 'boolean â 1');
};
# --------------------------------------------------
# Path: type='arrayref' â TYPE_DEFAULTS{arrayref} = '[]'
# --------------------------------------------------
subtest '_representative_value: arrayref â "[]"' => sub {
is(_repr({ type => 'arrayref' }), '[]', 'arrayref â "[]"');
};
# --------------------------------------------------
# Path: type='hashref' â TYPE_DEFAULTS{hashref} = '{}'
# --------------------------------------------------
subtest '_representative_value: hashref â "{}"' => sub {
is(_repr({ type => 'hashref' }), '{}', 'hashref â "{}"');
};
# --------------------------------------------------
# Path: unknown type â falls to final return:
# `$TYPE_DEFAULTS{$type} // "'value'"`
# No key 'widget' â undef // "'value'" â "'value'"
# --------------------------------------------------
subtest '_representative_value: unknown type â fallback "\'value\'"' => sub {
is(_repr({ type => 'widget' }), "'value'",
"unknown type 'widget' â fallback \"'value'\"");
};
# ==================================================================
# BENCHMARKGENERATOR â _quote_value()
#
# Four paths:
# undef â 'undef'
# numeric (looks_like_number) â returned as-is
# plain string â single-quoted
# string with single quote â escaped
# ==================================================================
sub _qv { App::Test::Generator::BenchmarkGenerator::_quote_value($_[0]) }
# --------------------------------------------------
# Path: undef â 'undef' (early return)
# --------------------------------------------------
subtest '_quote_value: undef â "undef"' => sub {
is(_qv(undef), 'undef', 'undef input â string "undef"');
};
# --------------------------------------------------
# Path: numeric 0 â 0 (looks_like_number true â return as-is)
# --------------------------------------------------
subtest '_quote_value: numeric 0 returned as-is' => sub {
is(_qv(0), 0, 'numeric 0 â 0 (not quoted)');
};
# --------------------------------------------------
# Path: numeric 3.14 â 3.14 (looks_like_number true)
# --------------------------------------------------
subtest '_quote_value: numeric 3.14 returned as-is' => sub {
is(_qv(3.14), 3.14, 'float 3.14 â 3.14 (not quoted)');
};
# --------------------------------------------------
# Path: plain string â single-quoted
# --------------------------------------------------
subtest '_quote_value: plain string â single-quoted' => sub {
is(_qv('world'), "'world'", 'plain string wrapped in single quotes');
};
# --------------------------------------------------
# Path: string containing a single-quote â escaped
# "it's" â "'it\\'s'" via s/'/\\'/g
# --------------------------------------------------
subtest "_quote_value: string with embedded single-quote â escaped" => sub {
is(_qv("it's"), q{'it\'s'}, "single quote in string is backslash-escaped");
};
# ==================================================================
# BENCHMARKGENERATOR â _build_call() branch paths
#
# Two dimensions: positional vs named params à has_new à builtin
# named + has_new â $obj->func(k => v)
# named + !has_new â Module::func(k => v)
# pos + has_new â $obj->func(args)
# pos + !has_new + builtin â func(args) [covered by BenchmarkGenerator_unit.t]
# pos + !has_new + !builtin â Module::func(args)
# ==================================================================
# --------------------------------------------------
# Path: named params + has_new â $obj->func(key => val)
# --------------------------------------------------
subtest '_build_call: named params + has_new â $obj->method(...)' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'My::Mod',
function => 'run',
new => undef,
input => {
alpha => { type => 'string' },
beta => { type => 'number' },
},
});
my $src = $bg->generate;
like($src, qr/\$obj->run\(/, 'has_new + named â $obj->func(...)');
like($src, qr/alpha\s*=>/, 'named param alpha appears as key => val');
like($src, qr/beta\s*=>/, 'named param beta appears as key => val');
diag($src) if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: named params + !has_new â Module::func(key => val)
# --------------------------------------------------
subtest '_build_call: named params + !has_new â Module::func(...)' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'My::Mod',
function => 'compute',
input => { x => { type => 'number' } },
});
my $src = $bg->generate;
like($src, qr/My::Mod::compute\(/, '!has_new + named â Module::func(...)');
diag($src) if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: positional params + has_new â $obj->func(args)
# --------------------------------------------------
subtest '_build_call: positional + has_new â $obj->func(args)' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'My::Mod',
function => 'process',
new => undef,
input => { n => { type => 'number', position => 0 } },
});
my $src = $bg->generate;
like($src, qr/\$obj->process\(42\)/, 'positional + has_new â $obj->func(args)');
diag($src) if $ENV{TEST_VERBOSE};
# --------------------------------------------------
# Path: has_new=1 AND is_builtin=1
# Condition: `if($has_new && !$is_builtin)` â false
# â entire constructor block skipped; $obj never declared
# --------------------------------------------------
subtest 'generate: has_new + builtin â no constructor emitted' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'builtin',
function => 'abs',
new => undef,
input => { n => { type => 'number', position => 0 } },
});
my $src = $bg->generate;
unlike($src, qr/->new/, 'builtin + new key: no constructor call emitted');
diag($src) if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: has_new=1, !builtin, new_spec = {} (empty hashref)
# Condition: `ref $new_spec eq 'HASH' && %$new_spec`
# â true for ref check, false for %$new_spec (empty)
# â else branch â Module->new()
# --------------------------------------------------
subtest 'generate: has_new with empty hashref new_spec â no-arg constructor' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'My::Mod',
function => 'run',
new => {},
input => {},
});
my $src = $bg->generate;
like($src, qr/My::Mod->new\(\)/, 'empty new hashref â Module->new() (no args)');
diag($src) if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: transforms present but empty ({})
# Condition: `if(%xforms)` â false (empty hash)
# â falls to else: emits 'default' variant
# --------------------------------------------------
subtest 'generate: empty transforms hashref â default variant emitted' => sub {
my $bg = App::Test::Generator::BenchmarkGenerator->new(schema => {
module => 'builtin',
function => 'abs',
input => { n => { type => 'number', position => 0 } },
transforms => {},
});
my $src = $bg->generate;
like($src, qr/'default'/, 'empty transforms hashref â default variant');
unlike($src, qr/'positive'|'negative'/, 'no transform variants emitted');
diag($src) if $ENV{TEST_VERBOSE};
};
# ==================================================================
# ANALYZER::COMPLEXITY â additional CFG paths
#
# Uncovered branches in _strip_strings_and_comments and analyze():
# 'given'/'when' keywords in @BRANCH_TOKENS
# keyword inside a double-quoted string body â not counted
# escaped characters inside double-quoted string
# unmatched '}' when depth=0 â clamp prevents underflow
# exact early_returns boundary: 1 return â 0 early; 2 â 1 early
# ==================================================================
# --------------------------------------------------
# Path: 'given' keyword â branching point
# 'given' is in @BRANCH_TOKENS but not tested in Analyzer-Complexity.t
# --------------------------------------------------
subtest 'Complexity::analyze: "given" keyword â branching point' => sub {
my $r = _complexity_body('given($x) { do_thing(); }');
is($r->{branching_points}, 1, '"given" adds 1 branching point');
diag("score=$r->{cyclomatic_score}") if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: 'when' keyword â branching point
# --------------------------------------------------
subtest 'Complexity::analyze: "when" keyword â branching point' => sub {
my $r = _complexity_body('when(1) { action(); }');
is($r->{branching_points}, 1, '"when" adds 1 branching point');
};
# --------------------------------------------------
# Path: both 'given' and 'when' in one body
# --------------------------------------------------
subtest 'Complexity::analyze: given + when â 2 branching points' => sub {
my $r = _complexity_body('given($x) { when(1) { do_thing(); } }');
is($r->{branching_points}, 2, 'given + when each add 1 â total 2');
};
# --------------------------------------------------
# Path: 'if' keyword inside a double-quoted string
# _strip_strings_and_comments blanks the string content;
# the regex then matches no branching keyword.
# --------------------------------------------------
subtest 'Complexity::analyze: "if" inside double-quoted string â not counted' => sub {
my $r = _complexity_body('my $msg = "run if condition is true";');
is($r->{branching_points}, 0, '"if" inside dquote string is stripped â 0 branching points');
diag("score=$r->{cyclomatic_score}") if $ENV{TEST_VERBOSE};
};
# --------------------------------------------------
# Path: 'die' inside a double-quoted string
# --------------------------------------------------
subtest 'Complexity::analyze: "die" inside double-quoted string â not counted' => sub {
my $r = _complexity_body('print "may die here";');
is($r->{exception_paths}, 0, '"die" inside dquote string is stripped â 0 exception paths');
};
# --------------------------------------------------
# Path: escaped double-quote inside a double-quoted string
# "said \"if\" here" â the content including the escaped quote is stripped
# --------------------------------------------------
subtest 'Complexity::analyze: escaped dquote inside string handled correctly' => sub {
my $r = _complexity_body(q{my $msg = "it said \"if\" here";});
is($r->{branching_points}, 0, 'escaped dquote: "if" inside string still stripped');
};
# --------------------------------------------------
# Path: unmatched '}' when depth=0
# Condition: `$depth-- if $depth > 0`
# When depth=0, the guard fires (false branch) â depth stays 0
# --------------------------------------------------
subtest 'Complexity::analyze: unmatched } when depth=0 â depth stays 0' => sub {
my $r = _complexity_body('my $x = 1; }');
is($r->{nesting_depth}, 0, 'unmatched } does not underflow depth to negative');
};
# --------------------------------------------------
# Path: exactly 1 return â early_returns = 0
# Condition: `$return_count > 1 ? $return_count - 1 : 0`
# 1 > 1 â false â 0
# --------------------------------------------------
subtest 'Complexity::analyze: exactly 1 return â early_returns=0' => sub {
my $r = _complexity_body('sub f { return $self->{x}; }');
is($r->{early_returns}, 0, 'one return â early_returns=0 (false branch of count>1)');
};
# --------------------------------------------------
# Path: exactly 0 returns â early_returns = 0
# $return_count = 0 â 0 > 1 is false â 0
# --------------------------------------------------
subtest 'Complexity::analyze: zero returns â early_returns=0' => sub {
my $r = _complexity_body('sub f { my $x = 1; }');
is($r->{early_returns}, 0, 'zero returns â early_returns=0');
};
# --------------------------------------------------
# Path: exactly 2 returns â early_returns = 1
# $return_count = 2 â 2 > 1 is true â 2 - 1 = 1
# --------------------------------------------------
subtest 'Complexity::analyze: exactly 2 returns â early_returns=1' => sub {
my $r = _complexity_body('if($x) { return 0; } return 1;');
is($r->{early_returns}, 1, 'two returns â early_returns=1 (true branch of count>1)');
};
# ==================================================================
# ANALYZER::SIDEEFFECT â additional CFG paths
#
# Uncovered paths in analyze():
# 'read' keyword alone â performs_io
# 'write' keyword alone â performs_io
# mutates_self=1 AND mutates_globals=1 â impure
# $self->{field}= inside a double-quoted string â not detected
# $self->{field}= inside a comment â not detected
# ==================================================================
# --------------------------------------------------
# Path: 'read' keyword â performs_io=1
# IO_PATTERN: qr/\b(?:print|say|...|read|write)\b/
# --------------------------------------------------
subtest 'SideEffect::analyze: "read" keyword â performs_io' => sub {
my $r = _sideeffect_body('read($fh, my $buf, 1024);');
is($r->{performs_io}, 1, '"read" keyword matches IO_PATTERN â performs_io=1');
};
( run in 0.526 second using v1.01-cache-2.11-cpan-788537b7465 )