App-Test-Generator
view release on metacpan or search on metacpan
lib/App/Test/Generator/SchemaExtractor.pm view on Meta::CPAN
my $statements = $doc->find(
sub {
$_[1]->isa('PPI::Statement') && $_[1]->content =~ /^\s*signature_for\b/
}
) or return;
foreach my $stmt (@$statements) {
my $content = $stmt->content;
if ($content =~ /^\s*signature_for\s+\Q$function\E\b/) {
return $stmt;
}
}
return;
}
# --------------------------------------------------
# _extract_signature_expression
#
# Purpose: Extract the Type::Params signature
# expression (everything after =>) from
# a signature_for statement node.
#
# Entry: $stmt - PPI::Statement node.
# $function - function name string,
# used in the match pattern.
#
# Exit: Returns the signature expression
# string, or undef if the pattern
# does not match.
#
# Side effects: None.
# --------------------------------------------------
sub _extract_signature_expression {
my ($self, $stmt, $function) = @_;
my $content = $stmt->content;
if ($content =~ /^\s*signature_for\s+\Q$function\E\s*=>\s*(.+?);?\s*$/s) {
return $1;
}
return;
}
# --------------------------------------------------
# _compile_signature_isolated
#
# Purpose: Compile and evaluate a Type::Params
# signature expression in an isolated
# environment to extract parameter
# metadata without polluting the
# current process.
#
# Entry: $function - function name string.
# $signature_expr - Type::Params
# signature expression
# string.
#
# Exit: Returns a decoded JSON hashref
# containing parameters and returns
# metadata on success.
# Croaks on unsafe expressions, timeout,
# or compile errors.
#
# Side effects: May fork a child process with a
# memory limit applied via
# BSD::Resource if available.
# Memory limiting is best-effort and
# silently skipped on platforms where
# BSD::Resource is unavailable.
# --------------------------------------------------
sub _compile_signature_isolated {
my ($self, $function, $signature_expr) = @_;
# Remove comments
$signature_expr =~ s/#.*$//mg;
# Reject obviously dangerous constructs
if ($signature_expr =~ /\b(?:system|exec|open|fork|require|do|eval|qx)\b/) {
die 'Unsafe signature expression';
}
if ($signature_expr =~ /[`{};]/) {
die "Unsafe signature expression";
}
my $payload = <<'PERL';
use strict;
use warnings;
use Type::Params -sigs;
use Types::Common -types;
use JSON::MaybeXS;
# Stub sub so Perl can parse it
sub FUNCTION_NAME {}
# Create the Type::Params signature object
my $sig = signature_for FUNCTION_NAME => SIGNATURE_EXPR;
# Extract parameters
my @sig_params = @{ $sig->parameters || [] };
my $pos = 0;
my @params;
# if ($sig->method) {
# The $self value
# push @params, {
# name => 'arg0',
# optional => 0,
# position => $pos++,
# };
# }
for my $p (@sig_params) {
push @params, {
name => "arg$pos",
optional => $p->optional ? 1 : 0,
position => $pos,
type => $p->type->name
lib/App/Test/Generator/SchemaExtractor.pm view on Meta::CPAN
# Substitute function name and signature expression
$payload =~ s/FUNCTION_NAME/$function/g;
$payload =~ s/SIGNATURE_EXPR/$signature_expr/;
my $compartment = Safe->new();
$compartment->permit_only(qw(:base_core :base_mem :base_orig :load));
if(my $sig = $compartment->reval($payload)) {
return $sig;
}
# Run in an isolated Perl process
my ($wtr, $rdr, $err) = (undef, undef, gensym);
local %ENV;
# Apply memory limit if BSD::Resource is available.
# This module is Unix-only and not available on Windows,
# so we guard the call and skip silently if not present.
eval {
require BSD::Resource;
BSD::Resource::setrlimit(
BSD::Resource::RLIMIT_AS(),
$MEMORY_LIMIT_BYTES,
$MEMORY_LIMIT_BYTES
);
};
# Ignore failure â resource limiting is best-effort only
my $pid = open3($wtr, $rdr, $err, $^X, '-T');
print $wtr $payload;
close $wtr;
local $SIG{ALRM} = sub { croak 'Signature compile timeout' };
eval { alarm($SIGNATURE_TIMEOUT_SECS) }; # no-op on Windows
my $stdout = do { local $/; <$rdr> };
my $stderr = do { local $/; <$err> };
eval { alarm 0 };
waitpid($pid, 0);
if ($stderr && length $stderr) {
croak "Error compiling signature:\n$stderr";
}
return decode_json($stdout);
}
# --------------------------------------------------
# _build_schema_from_meta
#
# Purpose: Convert the parameter and return type
# metadata produced by
# _compile_signature_isolated into a
# standard schema hashref.
#
# Entry: $meta - hashref with 'parameters'
# arrayref and optional
# 'returns' hashref, as decoded
# from the isolated compile
# JSON output.
#
# Exit: Returns a schema hashref with input,
# output, style, source, _notes, and
# _confidence keys.
#
# Side effects: None.
#
# Notes: Unknown Type::Params type names are
# mapped to 'string' with a note added
# and confidence downgraded to 'medium'.
# --------------------------------------------------
sub _build_schema_from_meta {
my ($self, $meta) = @_;
my %type_map = (
Num => 'number',
Int => 'integer',
Str => 'string',
Bool => 'boolean',
Object => 'object',
ArrayRef => 'array',
HashRef => 'object',
);
my $input;
my $position = 0;
my $confidence = 'high';
my @notes = ('Type::Params detected');
foreach my $p (@{ $meta->{parameters} || [] }) {
my $type = $type_map{ $p->{type} } // 'string';
if (!exists $type_map{$p->{type}}) {
push @notes, "Unknown type $p->{type}, defaulting to string";
$confidence = 'medium';
}
$input->{"arg$position"} = {
type => $type,
position => $position,
optional => $p->{optional} ? 1 : 0,
};
$position++;
}
my $output;
if (my $ret = $meta->{returns}) {
my $type = $type_map{ $ret->{type} } // 'string';
if (!exists $type_map{$ret->{type}}) {
push @notes, "Unknown return type $ret->{type}, defaulting to string";
$confidence = 'medium';
}
$output = {
type => $type,
( run in 1.041 second using v1.01-cache-2.11-cpan-df04353d9ac )