App-Test-Generator

 view release on metacpan or  search on metacpan

t/path.t  view on Meta::CPAN

# --------------------------------------------------
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};

t/path.t  view on Meta::CPAN


# --------------------------------------------------
# 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 )