App-Test-Generator
view release on metacpan or search on metacpan
bin/pod-example-tester view on Meta::CPAN
# Single annotated call: wrap in eval, capture return value, compare.
# Strip 'my $var =' prefix so the eval captures the bare expression
# value directly rather than shadowing with an inner variable.
my $expr = $code;
$expr =~ s/^\s*my\s+\$\w+\s*=\s*//;
$expr =~ s/\s*;\s*$//;
my $n_tests = defined($expected) ? 2 : 1;
print $fh "\tplan tests => $n_tests;\n";
if($ctor && $expr =~ /\$obj\s*->/) {
print $fh "\tmy \$obj = $ctor;\n";
}
print $fh "\tmy \$result = eval { $expr };\n";
print $fh "\tok(!\$\@, 'example runs without error') or diag \$\@;\n";
if(defined $expected) {
print $fh "\tis(\$result, $expected, 'returns expected value from POD annotation');\n";
}
} else {
# Verbatim block: run whole block in eval, check no exception.
# Stub any variables the snippet uses but doesn't declare so the
# generated file compiles cleanly under "use strict".
print $fh "\tplan tests => 1;\n";
print $fh "\tmy \$ok = eval {\n";
my @stubs = _stub_undeclared_vars($code);
if(@stubs) {
print $fh "\t\tmy (", join(', ', @stubs), ");\n";
}
for my $line (split /\n/, $code) {
if(my $neutralized = _neutralize_exec($line)) {
print $fh "\t\t$neutralized\n";
} else {
print $fh "\t\t$line\n";
}
}
print $fh "\t\t1;\n";
print $fh "\t};\n";
print $fh "\tok(\$ok && !\$\@, 'example runs without error') or diag \$\@;\n";
}
print $fh "};\n\n";
}
# ---------------------------------------------------------------------------
# _neutralize_exec â if a line from a verbatim SYNOPSIS block would invoke
# a shell command (system, exec, backticks, qx), return a replacement
# note() call that describes what would have run instead of executing it.
# Returns the empty string (false) for safe lines so the caller can pass
# them through unchanged.
#
# Lines that are already comments are left alone â they can't execute.
# ---------------------------------------------------------------------------
sub _neutralize_exec {
my ($line) = @_;
return '' if $line =~ /^\s*#/; # pure comment â harmless
return '' unless $line =~ /\b(?:system|exec)\s*\(|`|\bqx\s*[{(\[\/|]/;
(my $msg = $line) =~ s/^\s+//; # strip leading whitespace for display
$msg =~ s/'/\\'/g; # escape single quotes for q{}
$msg =~ s/\s+$//;
return "note('pod-example-tester: skipped shell call: $msg');";
}
# ---------------------------------------------------------------------------
# _stub_undeclared_vars â return sorted list of sigil+name variables (e.g.
# '$dir', '@items') that appear in the code block but are not declared with
# my/our/local. Used to inject stub declarations so generated eval blocks
# compile cleanly under "use strict" even when SYNOPSIS snippets assume
# surrounding context (e.g. a $dir variable set up by the caller).
#
# Well-known Perl builtins (@_, @INC, @ARGV, %ENV, %INC, %SIG, $_) are
# excluded because they exist without explicit declaration.
# ---------------------------------------------------------------------------
{
my %PERL_BUILTINS = map { $_ => 1 } qw(
$_ @_ @INC @ARGV %ENV %INC %SIG
);
sub _stub_undeclared_vars {
my ($code) = @_;
my %declared;
# Capture every variable following a my/our/local keyword, including
# list forms: my ($a, $b) and foreach my $x(...)
while($code =~ /\b(?:my|our|local)\b[^;{]*?([\$\@\%]\w+)/g) {
$declared{$1} = 1;
}
my %used;
while($code =~ /([\$\@\%])([a-zA-Z_]\w*)/g) {
$used{"$1$2"} = 1;
}
return sort grep { !$declared{$_} && !$PERL_BUILTINS{$_} } keys %used;
}
}
# ---------------------------------------------------------------------------
# _detect_package â extract 'package Foo::Bar' from source
# ---------------------------------------------------------------------------
sub _detect_package {
my ($file) = @_;
open my $fh, '<', $file or return;
while(<$fh>) {
return $1 if /^package\s+([\w:]+)\s*[;{]/;
}
return;
}
# ---------------------------------------------------------------------------
# _detect_constructor â return a string like 'Foo::Bar->new()' if the
# package has a new() method, else undef
# ---------------------------------------------------------------------------
sub _detect_constructor {
my ($package) = @_;
# Try loading and querying can()
eval { (my $mod = $package) =~ s{::}{/}g; require "$mod.pm" };
( run in 0.592 second using v1.01-cache-2.11-cpan-788537b7465 )