Alien-Selenium
view release on metacpan or search on metacpan
inc/My/Tests/Below.pm view on Meta::CPAN
=cut "
package My::Tests::Below;
use strict;
use File::Temp ();
our $VERSION = 2.0;
## This is done at the top level, not in a sub, as "require
## My::Tests::Below" is what gets the ball rolling:
our $singleton = __PACKAGE__->_parse(\*main::DATA, caller(0));
unless (defined $singleton) {
die "My::Tests::Below invoked, but no tests were found below!";
}
close(main::DATA);
$singleton->run();
## Creates an instance of My::Tests::Below from a source file
## that has tests at the bottom.
sub _parse {
my ($class, $fd, $package, $packfilename, $packline) = @_;
if (!defined $package) { # Handle self-testing case
$package="My::Tests::Below";
$0 =~ m/^(.*)$/; $packfilename = $1;
}
my $self = bless {
package => $package,
packfilename => $packfilename
}, $class;
$self->{testsuite} = do { no warnings; scalar <$fd> };
return undef if ! defined $self->{testsuite};
$self->{testsuite} .= join('',<$fd>); # The rest of it
=head2 Comfort features
Unlike the C<< eval >> form recommended in L<perlmodlib>,
My::Tests::Below provides a couple of comfort features that help
making the system smooth to use for tests.
=over
=item I<Support for code and data snippets in the POD>
A mechanism similar to the now-deprecated L<Pod::Tests> is proposed to
test documented examples such as code fragments in the SYNOPSIS. See
L</CLASS METHODS> below.
=cut
## Parse the whole source file again in order to provide said
## features. Yes, seeking to start also works on main::DATA!
## Gotta love Perl :-)
seek($fd, 0, 0) or die $!; $. = 0;
my $insnippet;
SOURCELINE: while(<$fd>) {
if (m/^=for\s+My::Tests::Below\s+"([^"]*)"(.*)$/) {
my $snipkey = $1; my @args = split m/\s+/, $2;
if (grep { lc($_) eq "end" } @args) {
die qq'Not in an "=for My::Tests::Below" directive'.
qq' at line $.\n' unless (defined $insnippet);
die qq'Badly nested "=for My::Tests::Below" directives'.
qq' at line $.\n' unless ($insnippet eq $snipkey);
undef $insnippet;
} else { # Assume "begin" for compatibility with old tests
die qq'Duplicate "=for My::Tests::Below" directive'.
qq' for label $snipkey at line $.\n' if
(exists $self->{podsnippets}{$snipkey});
die qq'Badly nested "=for My::Tests::Below" directives'.
qq' at line $.\n' if (defined $insnippet);
$self->{podsnippets}{$snipkey}->{lineno} = $. + 1;
$insnippet = $snipkey;
}
} elsif (m/^=for\s+My::Tests::Below/) {
die qq'Parse error in "=for My::Tests::Below"'.
qq' directive at line $.\n';
} else {
$self->{podsnippets}{$insnippet}->{text} .= $_ if (defined $insnippet);
};
next if (defined($packline) && $. <= $packline); # Be sure to
# catch the first marker *after* the require directive, and
# mind the self-test case too.
next SOURCELINE unless (m/^__(END|DATA)__\s+$/);
=item I<Line counting for the debugger>
You can step through the test using a GUI debugger (e.g. perldb in
Emacs) because the line numbers are appropriately translated.
=cut
$self->{testsuite}="#line ".($.+1)." \"$self->{packfilename}\"\n".
$self->{testsuite};
last SOURCELINE;
}
=item I<Tests always start in package main>
The perlmodlib idiomatics puts you either in C<main> or in the package
where the eval was called from, depending on the version of Perl.
=cut
$self->{testsuite}="package main;\n" . $self->{testsuite};
return $self;
}
## Actually runs the test suite in an eval.
sub run {
my ($self) = @_;
=item I<Tested package is available for "use">
As shown in L</SYNOPSIS>, one can invoke "use MyPackage;" at the top
of the test suite and this will not cause the package under test to be
inc/My/Tests/Below.pm view on Meta::CPAN
use strict;
=head1 NAME
Zoinx!
=head1 SYNOPSIS
=for My::Tests::Below "create-zoinx" begin
my $zoinx = new Zoinx;
=for My::Tests::Below "create-zoinx" end
=cut
package Zoinx;
sub new {
bless {}, "Zoinx";
}
require My::Tests::Below unless caller;
=for My::Tests::Below "reflection is a fun thing" end
=for great "justice"
__END__
then C<< My::Tests::Below->pod_data_snippet("create-zoinx") >> would
return "\nmy $zoinx = new Zoinx;\n\n".
The syntax of the C<=for My::Tests::Below> POD markup lines obeys the
following rules:
=over
=item *
the first token after C<My::Tests::Below> is a double-quoted string
that contains the unique label of the POD snippet (passed as the first
argument to I<pod_data_snippet()>);
=item *
the second token is either C<begin> and C<end>, which denote the start
and end of the snippet as shown above. Nesting is forbidden (for now).
=back
=cut
sub pod_data_snippet {
my ($self, $name)=@_; $self = $singleton if ! ref $self;
local $_ = $self->{podsnippets}{$name}->{text};
return unless defined;
my $ragamount;
foreach my $line (split m/\n/s) {
next if $line =~ m/^\t/; # tab width is treated as infinite to
# cut through the whole mess
next if $line eq ""; # Often authors leave empty lines to
# delimit paragraphs in SYNOPSIS, count them as undefined
# length as well
$line =~ m/^( *)/; my $spaceamount = length($1);
$ragamount = $spaceamount if ((!defined $ragamount) ||
($ragamount > $spaceamount));
}
s/^[ ]{$ragamount}//gm;
m/^(.*)$/s; return $1; # Untainted
}
=item I<pod_code_snippet($snippetname)>
Works like L</pod_data_snippet>, except that an adequate #line is
prepended for the benefit of the debugger. You can thus single-step
inside your POD documentation, yow! Using the above sample .pm file
(see L</pod_data_snippet>), you could do something like this in the
test trailer:
=for My::Tests::Below "POD testing example" begin
my $snippet = My::Tests::Below->pod_code_snippet("create-zoinx");
# Munging $snippet a bit before running it (e.g. with regexp
# replaces) is par for the course.
my $zoinx = eval $snippet;
die $@ if $@; # If snippet fails, we want to know
# Optionally proceed to test the outcome of the snippet:
is(ref($zoinx), "Zoinx", '$zoinx is a Zoinx');
=for My::Tests::Below "POD testing example" end
=cut
sub pod_code_snippet {
my ($self, $name) = @_; $self = $singleton if ! ref $self;
return "#line " . $self->{podsnippets}{$name}->{lineno} .
" \"$self->{packfilename}\"\n" .
$self->pod_data_snippet($name);
}
=back
=head1 SEE ALSO
L<My::Module::Build> knows how to remove I<My::Tests::Below>
suites at "make" or "./Build code" time, so as not to burden the
compiled package with the test suite.
While I am (obviously) partial to putting tests at the bottom of the
package, I also occasionally make use of classic C<t/*.t> tests; in
particular I use the same C<t/maintainer/*.t> tests in all my CPAN
modules.
( run in 1.138 second using v1.01-cache-2.11-cpan-efa8479b9fe )