Initial Commit
This commit is contained in:
145
database/perl/vendor/lib/Test2/Manual/Tooling/FirstTool.pm
vendored
Normal file
145
database/perl/vendor/lib/Test2/Manual/Tooling/FirstTool.pm
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package Test2::Manual::Tooling::FirstTool;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::FirstTool - Write your first tool with Test2.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This tutorial will help you write your very first tool by cloning the C<ok()>
|
||||
tool.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Tools::MyOk;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test2::API qw/context/;
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = qw/ok/;
|
||||
|
||||
sub ok($;$@) {
|
||||
my ($bool, $name, @diag) = @_;
|
||||
|
||||
my $ctx = context();
|
||||
|
||||
return $ctx->pass_and_release($name) if $bool;
|
||||
return $ctx->fail_and_release($name, @diag);
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item sub ok($;$@) {
|
||||
|
||||
In this case we are emulating the C<ok()> function exported by
|
||||
L<Test2::Tools::Basic>.
|
||||
|
||||
C<ok()> and similar test tools use prototypes to enforce argument parsing. Your
|
||||
test tools do not necessarily need prototypes, like any perl function you need
|
||||
to make the decision based on how it is used.
|
||||
|
||||
The prototype requires at least 1 argument, which will
|
||||
be forced into a scalar context. The second argument is optional, and is also
|
||||
forced to be scalar, it is the name of the test. Any remaining arguments are
|
||||
treated as diagnostics messages that will only be used if the test failed.
|
||||
|
||||
=item my ($bool, $name, @diag) = @_;
|
||||
|
||||
This line does not need much explanation, we are simply grabbing the args.
|
||||
|
||||
=item my $ctx = context();
|
||||
|
||||
This is a vital line in B<ALL> tools. The context object is the primary API for
|
||||
test tools. You B<MUST> get a context if you want to issue any events, such as
|
||||
making assertions. Further, the context is responsible for making sure failures
|
||||
are attributed to the correct file and line number.
|
||||
|
||||
B<Note:> A test function B<MUST> always release the context when it is done,
|
||||
you cannot simply let it fall out of scope and be garbage collected. Test2 does
|
||||
a pretty good job of yelling at you if you make this mistake.
|
||||
|
||||
B<Note:> You B<MUST NOT> ever store or pass around a I<real> context object. If
|
||||
you wish to hold on to a context for any reason you must use clone to make a
|
||||
copy C<< my $copy = $ctx->clone >>. The copy may be passed around or stored,
|
||||
but the original B<MUST> be released when you are done with it.
|
||||
|
||||
=item return $ctx->pass_and_release($name) if $bool;
|
||||
|
||||
When C<$bool> is true, this line uses the context object to issue a
|
||||
L<Test2::Event::Pass> event. Along with issuing the event this will also
|
||||
release the context object and return true.
|
||||
|
||||
This is short form for:
|
||||
|
||||
if($bool) {
|
||||
$ctx->pass($name);
|
||||
$ctx->release;
|
||||
return 1;
|
||||
}
|
||||
|
||||
=item return $ctx->fail_and_release($name, @diag);
|
||||
|
||||
This line issues a L<Test2::Event::Fail> event, releases the context object,
|
||||
and returns false. The fail event will include any diagnostics messages from
|
||||
the C<@diag> array.
|
||||
|
||||
This is short form for:
|
||||
|
||||
$ctx->fail($name, @diag);
|
||||
$ctx->release;
|
||||
return 0;
|
||||
|
||||
=back
|
||||
|
||||
=head1 CONTEXT OBJECT DOCUMENTATION
|
||||
|
||||
L<Test2::API::Context> is the place to read up on what methods the context
|
||||
provides.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
138
database/perl/vendor/lib/Test2/Manual/Tooling/Formatter.pm
vendored
Normal file
138
database/perl/vendor/lib/Test2/Manual/Tooling/Formatter.pm
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
package Test2::Manual::Tooling::Formatter;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Formatter - How to write a custom formatter, in our
|
||||
case a JSONL formatter.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This tutorial explains a minimal formatter that outputs each event as a json
|
||||
string on its own line. A true formatter will probably be significantly more
|
||||
complicated, but this will give you the basics needed to get started.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Formatter::MyFormatter;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use JSON::MaybeXS qw/encode_json/;
|
||||
|
||||
use base qw/Test2::Formatter/;
|
||||
|
||||
sub new { bless {}, shift }
|
||||
|
||||
sub encoding {};
|
||||
|
||||
sub write {
|
||||
my ($self, $e, $num, $f) = @_;
|
||||
$f ||= $e->facet_data;
|
||||
|
||||
print encode_json($f), "\n";
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item use base qw/Test2::Formatter/;
|
||||
|
||||
All formatters should inherit from L<Test2::Formatter>.
|
||||
|
||||
=item sub new { bless {}, shift }
|
||||
|
||||
Formatters need to be instantiable objects, this is a minimal C<new()> method.
|
||||
|
||||
=item sub encoding {};
|
||||
|
||||
For this example we leave this sub empty. In general you should implement this
|
||||
sub to make sure you honor situations where the encoding is set. L<Test2::V0>
|
||||
itself will try to set the encoding to UTF8.
|
||||
|
||||
=item sub write { ... }
|
||||
|
||||
The C<write()> method is the most important, each event is sent here.
|
||||
|
||||
=item my ($self, $e, $num, $f) = @_;
|
||||
|
||||
The C<write()> method receives 3 or 4 arguments, the fourth is optional.
|
||||
|
||||
=over 4
|
||||
|
||||
=item $self
|
||||
|
||||
The formatter itself.
|
||||
|
||||
=item $e
|
||||
|
||||
The event being written
|
||||
|
||||
=item $num
|
||||
|
||||
The most recent assertion number. If the event being processed is an assertion
|
||||
then this will have been bumped by 1 since the last call to write. For non
|
||||
assertions this number is set to the most recent assertion.
|
||||
|
||||
=item $f
|
||||
|
||||
This MAY be a hashref containing all the facet data from the event. More often
|
||||
then not this will be undefined. This is only set if the facet data was needed
|
||||
by the hub, and it usually is not.
|
||||
|
||||
=back
|
||||
|
||||
=item $f ||= $e->facet_data;
|
||||
|
||||
We want to dump the event facet data. This will set C<$f> to the facet data
|
||||
unless we already have the facet data.
|
||||
|
||||
=item print encode_json($f), "\n";
|
||||
|
||||
This line prints the JSON encoded facet data, and a newline.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
140
database/perl/vendor/lib/Test2/Manual/Tooling/Nesting.pm
vendored
Normal file
140
database/perl/vendor/lib/Test2/Manual/Tooling/Nesting.pm
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
package Test2::Manual::Tooling::Nesting;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Nesting - Tutorial for using other tools within your
|
||||
own.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Sometimes you find yourself writing the same test pattern over and over, in
|
||||
such cases you may want to encapsulate the logic in a new test function that
|
||||
calls several tools together. This sounds easy enough, but can cause headaches
|
||||
if not done correctly.
|
||||
|
||||
=head1 NAIVE WAY
|
||||
|
||||
Lets say you find yourself writing the same test pattern over and over for multiple objects:
|
||||
|
||||
my $obj1 = $class1->new;
|
||||
is($obj1->foo, 'foo', "got foo");
|
||||
is($obj1->bar, 'bar', "got bar");
|
||||
|
||||
my $obj2 = $class1->new;
|
||||
is($obj2->foo, 'foo', "got foo");
|
||||
is($obj2->bar, 'bar', "got bar");
|
||||
|
||||
... 10x more times for classes 2-12
|
||||
|
||||
The naive way to do this is to write a C<check_class()> function like this:
|
||||
|
||||
sub check_class {
|
||||
my $class = shift;
|
||||
my $obj = $class->new;
|
||||
is($obj->foo, 'foo', "got foo");
|
||||
is($obj->bar, 'bar', "got bar");
|
||||
}
|
||||
|
||||
check_class($class1);
|
||||
check_class($class2);
|
||||
check_class($class3);
|
||||
...
|
||||
|
||||
This will appear to work fine, and you might not notice any problems,
|
||||
I<so long as the tests are passing.>
|
||||
|
||||
=head2 WHATS WRONG WITH IT?
|
||||
|
||||
The problems with the naive approach become obvious if things start to fail.
|
||||
The diagnostics that tell you what file and line the failure occurred on will be
|
||||
wrong. The failure will be reported to the line I<inside> C<check_class>, not
|
||||
to the line where C<check_class()> was called. This is problem because it
|
||||
leaves you with no idea which class is failing.
|
||||
|
||||
=head2 HOW TO FIX IT
|
||||
|
||||
Luckily this is extremely easy to fix. You need to acquire a context object at
|
||||
the start of your function, and release it at the end... yes it is that simple.
|
||||
|
||||
use Test2::API qw/context/;
|
||||
|
||||
sub check_class {
|
||||
my $class = shift;
|
||||
|
||||
my $ctx = context();
|
||||
|
||||
my $obj = $class->new;
|
||||
is($obj->foo, 'foo', "got foo");
|
||||
is($obj->bar, 'bar', "got bar");
|
||||
|
||||
$ctx->release;
|
||||
}
|
||||
|
||||
See, that was easy. With these 2 additional lines we know have proper file+line
|
||||
reporting. The nested tools will find the context we acquired here, and know to
|
||||
use it's file and line numbers.
|
||||
|
||||
=head3 THE OLD WAY (DO NOT DO THIS ANYMORE)
|
||||
|
||||
With L<Test::Builder> there was a global variables called
|
||||
C<$Test::Builder::Level> which helped solve this problem:
|
||||
|
||||
sub check_class {
|
||||
my $class = shift;
|
||||
|
||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||
|
||||
my $obj = $class->new;
|
||||
is($obj->foo, 'foo', "got foo");
|
||||
is($obj->bar, 'bar', "got bar");
|
||||
}
|
||||
|
||||
This variable worked well enough (and will still work) but was not very
|
||||
discoverable. Another problem with this variable is that it becomes cumbersome
|
||||
if you have a more deeply nested code structure called the nested tools, you
|
||||
might need to count stack frames, and hope they never change due to a third
|
||||
party module. The context solution has no such caveats.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
108
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/TestExit.pm
vendored
Normal file
108
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/TestExit.pm
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package Test2::Manual::Tooling::Plugin::TestExit;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Plugin::TestExit - How to safely add pre-exit
|
||||
behaviors.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This describes the correct/safe way to add pre-exit behaviors to tests via a
|
||||
custom plugin.
|
||||
|
||||
The naive way to attempt this would be to add an C<END { ... }> block. That can
|
||||
work, and may not cause problems.... On the other hand there are a lot of ways
|
||||
that can bite you. Describing all the potential problems of an END block, and
|
||||
how it might conflict with Test2 (Which has its own END block) is beyond the
|
||||
scope of this document.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Plugin::MyPlugin;
|
||||
|
||||
use Test2::API qw{test2_add_callback_exit};
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
|
||||
test2_add_callback_exit(sub {
|
||||
my ($ctx, $orig_code, $new_exit_code_ref) = @_;
|
||||
|
||||
return if $orig_code == 42;
|
||||
|
||||
$$new_exit_code_ref = 42;
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item use Test2::API qw{test2_add_callback_exit};
|
||||
|
||||
This imports the C<(test2_add_callback_exit)> callback.
|
||||
|
||||
=item test2_add_callback_exit(sub { ... });
|
||||
|
||||
This adds our callback to be called before exiting.
|
||||
|
||||
=item my ($ctx, $orig_code, $new_exit_code_ref) = @_
|
||||
|
||||
The callback gets 3 arguments. First is a context object you may use. The
|
||||
second is the original exit code of the C<END> block Test2 is using. The third
|
||||
argument is a scalar reference which you may use to get the current exit code,
|
||||
or set a new one.
|
||||
|
||||
=item return if $orig_code == 42
|
||||
|
||||
This is a short-cut to do nothing if the original exit code was already 42.
|
||||
|
||||
=item $$new_exit_code_ref = 42
|
||||
|
||||
This changes the exit code to 42.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
121
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/TestingDone.pm
vendored
Normal file
121
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/TestingDone.pm
vendored
Normal file
@@ -0,0 +1,121 @@
|
||||
package Test2::Manual::Tooling::Plugin::TestingDone;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Plugin::TestingDone - Run code when the test file is
|
||||
finished, or when done_testing is called.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This is a way to add behavior to the end of a test file. This code is run
|
||||
either when done_testing() is called, or when the test file has no more
|
||||
run-time code to run.
|
||||
|
||||
When triggered by done_testing() this will be run BEFORE the plan is calculated
|
||||
and sent. This means it IS safe to make test assertions in this callback.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Plugin::MyPlugin;
|
||||
|
||||
use Test2::API qw{test2_add_callback_testing_done};
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
|
||||
test2_add_callback_testing_done(sub {
|
||||
ok(!$some_global, '$some_global was not set');
|
||||
print "The test file is done, or done_testing was just called\n"
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item use Test2::API qw{test2_add_callback_testing_done};
|
||||
|
||||
This imports the C<test2_add_callback_testing_done()> callback.
|
||||
|
||||
=item test2_add_callback_testing_done(sub { ... });
|
||||
|
||||
This adds our callback to be called when testing is done.
|
||||
|
||||
=item ok(!$some_global, '$some_global was not set')
|
||||
|
||||
It is safe to make assertions in this type of callback. This code simply
|
||||
asserts that some global was never set over the course of the test.
|
||||
|
||||
=item print "The test file is done, or done_testing was just called\n"
|
||||
|
||||
This prints a message when the callback is run.
|
||||
|
||||
=back
|
||||
|
||||
=head1 UNDER THE HOOD
|
||||
|
||||
Before test2_add_callback_testing_done() this kind of thing was still possible,
|
||||
but it was hard to get right, here is the code to do it:
|
||||
|
||||
test2_add_callback_post_load(sub {
|
||||
my $stack = test2_stack();
|
||||
|
||||
# Insure we have at least one hub, but we do not necessarily want the
|
||||
# one this returns.
|
||||
$stack->top;
|
||||
|
||||
# We want the root hub, not the top one.
|
||||
my ($root) = Test2::API::test2_stack->all;
|
||||
|
||||
# Make sure the hub does not believe nothing has happened.
|
||||
$root->set_active(1);
|
||||
|
||||
# Now we can add our follow-up code
|
||||
$root->follow_up(sub {
|
||||
# Your callback code here
|
||||
});
|
||||
});
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
94
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/ToolCompletes.pm
vendored
Normal file
94
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/ToolCompletes.pm
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
package Test2::Manual::Tooling::Plugin::ToolCompletes;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Plugin::ToolCompletes - How to add behaviors that occur
|
||||
when a tool completes work.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This tutorial helps you understand how to add behaviors that occur when a tool
|
||||
is done with its work. All tools need to acquire and then release a context,
|
||||
for this tutorial we make use of the release hooks that are called every time a
|
||||
tool releases the context object.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Plugin::MyPlugin;
|
||||
|
||||
use Test2::API qw{test2_add_callback_context_release};
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
|
||||
test2_add_callback_context_release(sub {
|
||||
my $ctx_ref = shift;
|
||||
|
||||
print "Context was released\n";
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item use Test2::API qw{test2_add_callback_context_release};
|
||||
|
||||
This imports the C<test2_add_callback_context_release()> callback.
|
||||
|
||||
=item test2_add_callback_context_release(sub { ... })
|
||||
|
||||
=item my $ctx_ref = shift
|
||||
|
||||
The coderefs for test2_add_callback_context_release() will receive exactly 1
|
||||
argument, the context being released.
|
||||
|
||||
=item print "Context was released\n"
|
||||
|
||||
Print a notification whenever the context is released.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
126
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/ToolStarts.pm
vendored
Normal file
126
database/perl/vendor/lib/Test2/Manual/Tooling/Plugin/ToolStarts.pm
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
package Test2::Manual::Tooling::Plugin::ToolStarts;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Plugin::ToolStarts - How to add behaviors that occur
|
||||
when a tool starts work.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This tutorial will help you write plugins that have behavior when a tool
|
||||
starts. All tools should start by acquiring a context object. This tutorial
|
||||
shows you the hooks you can use to take advantage of the context acquisition.
|
||||
|
||||
=head1 COMPLETE CODE UP FRONT
|
||||
|
||||
package Test2::Plugin::MyPlugin;
|
||||
|
||||
use Test2::API qw{
|
||||
test2_add_callback_context_init
|
||||
test2_add_callback_context_acquire
|
||||
};
|
||||
|
||||
sub import {
|
||||
my $class = shift;
|
||||
|
||||
# Let us know every time a tool requests a context, and give us a
|
||||
# chance to modify the parameters before we find it.
|
||||
test2_add_callback_context_acquire(sub {
|
||||
my $params_ref = shift;
|
||||
|
||||
print "A tool has requested the context\n";
|
||||
});
|
||||
|
||||
# Callback every time a new context is created, not called if an
|
||||
# existing context is found.
|
||||
test2_add_callback_context_init(sub {
|
||||
my $ctx_ref = shift;
|
||||
|
||||
print "A new context was created\n";
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
|
||||
=head1 LINE BY LINE
|
||||
|
||||
=over 4
|
||||
|
||||
=item use Test2::API qw{test2_add_callback_context_init test2_add_callback_context_acquire};
|
||||
|
||||
This imports the C<test2_add_callback_context_init()> and
|
||||
C<test2_add_callback_context_acquire()> callbacks.
|
||||
|
||||
=item test2_add_callback_context_acquire(sub { ... })
|
||||
|
||||
This is where we add our callback for context acquisition. Every time
|
||||
C<Test2::API::context()> is called the callback will be run.
|
||||
|
||||
=item my $params_ref = shift
|
||||
|
||||
In the test2_add_callback_context_acquire() callbacks we get exactly 1
|
||||
argument, a reference to the parameters that C<context()> will use to find the
|
||||
context.
|
||||
|
||||
=item print "A tool has requested the context\n"
|
||||
|
||||
Print a notification whenever a tool asks for a context.
|
||||
|
||||
=item test2_add_callback_context_init(sub { ... })
|
||||
|
||||
Add our context init callback. These callbacks are triggered whenever a
|
||||
completely new context is created. This is not called if an existing context is
|
||||
found. In short this only fires off for the top level tool, not nested tools.
|
||||
|
||||
=item my $ctx_ref = shift
|
||||
|
||||
The coderefs for test2_add_callback_context_init() will receive exactly 1
|
||||
argument, the newly created context.
|
||||
|
||||
=item print "A new context was created\n"
|
||||
|
||||
Print a notification whenever a new context is created.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
164
database/perl/vendor/lib/Test2/Manual/Tooling/Subtest.pm
vendored
Normal file
164
database/perl/vendor/lib/Test2/Manual/Tooling/Subtest.pm
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
package Test2::Manual::Tooling::Subtest;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Subtest - How to implement a tool that makes use of
|
||||
subtests.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Subtests are a nice way of making related events visually, and architecturally
|
||||
distinct.
|
||||
|
||||
=head1 WHICH TYPE OF SUBTEST DO I NEED?
|
||||
|
||||
There are 2 types of subtest. The first type is subtests with user-supplied
|
||||
coderefs, such as the C<subtest()> function itself. The second type is subtest
|
||||
that do not have any user supplied coderefs.
|
||||
|
||||
So which type do you need? The answer to that is simple, if you are going to
|
||||
let the user define the subtest with their own codeblock, you have the first
|
||||
type, otherwise you have the second.
|
||||
|
||||
In either case, you will still need use the same API function:
|
||||
C<Test2::API::run_subtest>.
|
||||
|
||||
=head2 SUBTEST WITH USER SUPPLIED CODEREF
|
||||
|
||||
This example will emulate the C<subtest> function.
|
||||
|
||||
use Test2::API qw/context run_subtest/;
|
||||
|
||||
sub my_subtest {
|
||||
my ($name, $code) = @_;
|
||||
|
||||
# Like any other tool, you need to acquire a context, if you do not then
|
||||
# things will not report the correct file and line number.
|
||||
my $ctx = context();
|
||||
|
||||
my $bool = run_subtest($name, $code);
|
||||
|
||||
$ctx->release;
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
This looks incredibly simple... and it is. C<run_subtest()> does all the hard
|
||||
work for you. This will issue an L<Test2::Event::Subtest> event with the
|
||||
results of the subtest. The subtest event itself will report to the proper file
|
||||
and line number due to the context you acquired (even though it does not I<look>
|
||||
like you used the context.
|
||||
|
||||
C<run_subtest()> can take additional arguments:
|
||||
|
||||
run_subtest($name, $code, \%params, @args);
|
||||
|
||||
=over 4
|
||||
|
||||
=item @args
|
||||
|
||||
This allows you to pass arguments into the codeblock that gets run.
|
||||
|
||||
=item \%params
|
||||
|
||||
This is a hashref of parameters. Currently there are 3 possible parameters:
|
||||
|
||||
=over 4
|
||||
|
||||
=item buffered => $bool
|
||||
|
||||
This will turn the subtest into the new style buffered subtest. This type of
|
||||
subtest is recommended, but not default.
|
||||
|
||||
=item inherit_trace => $bool
|
||||
|
||||
This is used for tool-side coderefs.
|
||||
|
||||
=item no_fork => $bool
|
||||
|
||||
react to forking/threading inside the subtest itself. In general you are
|
||||
unlikely to need/want this parameter.
|
||||
|
||||
=back
|
||||
|
||||
=back
|
||||
|
||||
=head2 SUBTEST WITH TOOL-SIDE CODEREF
|
||||
|
||||
This is particularly useful if you want to turn a tool that wraps other tools
|
||||
into a subtest. For this we will be using the tool we created in
|
||||
L<Test2::Manual::Tooling::Nesting>.
|
||||
|
||||
use Test2::API qw/context run_subtest/;
|
||||
|
||||
sub check_class {
|
||||
my $class = shift;
|
||||
|
||||
my $ctx = context();
|
||||
|
||||
my $code = sub {
|
||||
my $obj = $class->new;
|
||||
is($obj->foo, 'foo', "got foo");
|
||||
is($obj->bar, 'bar', "got bar");
|
||||
};
|
||||
|
||||
my $bool = run_subtest($class, $code, {buffered => 1, inherit_trace => 1});
|
||||
|
||||
$ctx->release;
|
||||
|
||||
return $bool;
|
||||
}
|
||||
|
||||
The C<run_subtest()> function does all the heavy lifting for us. All we need
|
||||
to do is give the function a name, a coderef to run, and the
|
||||
C<< inherit_trace => 1 >> parameter. The C<< buffered => 1 >> parameter is
|
||||
optional, but recommended.
|
||||
|
||||
The C<inherit_trace> parameter tells the subtest tool that the contexts acquired
|
||||
inside the nested tools should use the same trace as the subtest itself. For
|
||||
user-supplied codeblocks you do not use inherit_trace because you want errors
|
||||
to report to the user-supplied file+line.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
171
database/perl/vendor/lib/Test2/Manual/Tooling/TestBuilder.pm
vendored
Normal file
171
database/perl/vendor/lib/Test2/Manual/Tooling/TestBuilder.pm
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
package Test2::Manual::Tooling::TestBuilder;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::TestBuilder - This section maps Test::Builder methods
|
||||
to Test2 concepts.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
With Test::Builder tools were encouraged to use methods on the Test::Builder
|
||||
singleton object. Test2 has a different approach, every tool should get a new
|
||||
L<Test2::API::Context> object, and call methods on that. This document maps
|
||||
several concepts from Test::Builder to Test2.
|
||||
|
||||
=head1 CONTEXT
|
||||
|
||||
First thing to do, stop using the Test::Builder singleton, in fact stop using
|
||||
or even loading Test::Builder. Instead of Test::Builder each tool you write
|
||||
should follow this template:
|
||||
|
||||
use Test2::API qw/context/;
|
||||
|
||||
sub my_tool {
|
||||
my $ctx = context();
|
||||
|
||||
... do work ...
|
||||
|
||||
$ctx->ok(1, "a passing assertion");
|
||||
|
||||
$ctx->release;
|
||||
|
||||
return $whatever;
|
||||
}
|
||||
|
||||
The original Test::Builder style was this:
|
||||
|
||||
use Test::Builder;
|
||||
my $tb = Test::Builder->new; # gets the singleton
|
||||
|
||||
sub my_tool {
|
||||
... do work ...
|
||||
|
||||
$tb->ok(1, "a passing assertion");
|
||||
|
||||
return $whatever;
|
||||
}
|
||||
|
||||
=head1 TEST BUILDER METHODS
|
||||
|
||||
=over 4
|
||||
|
||||
=item $tb->BAIL_OUT($reason)
|
||||
|
||||
The context object has a 'bail' method:
|
||||
|
||||
$ctx->bail($reason)
|
||||
|
||||
=item $tb->diag($string)
|
||||
|
||||
=item $tb->note($string)
|
||||
|
||||
The context object has diag and note methods:
|
||||
|
||||
$ctx->diag($string);
|
||||
$ctx->note($string);
|
||||
|
||||
=item $tb->done_testing
|
||||
|
||||
The context object has a done_testing method:
|
||||
|
||||
$ctx->done_testing;
|
||||
|
||||
Unlike the Test::Builder version, no arguments are allowed.
|
||||
|
||||
=item $tb->like
|
||||
|
||||
=item $tb->unlike
|
||||
|
||||
These are not part of context, instead look at L<Test2::Compare> and
|
||||
L<Test2::Tools::Compare>.
|
||||
|
||||
=item $tb->ok($bool, $name)
|
||||
|
||||
# Preferred
|
||||
$ctx->pass($name);
|
||||
$ctx->fail($name, @diag);
|
||||
|
||||
# Discouraged, but supported:
|
||||
$ctx->ok($bool, $name, \@failure_diags)
|
||||
|
||||
=item $tb->subtest
|
||||
|
||||
use the C<Test2::API::run_subtest()> function instead. See L<Test2::API> for documentation.
|
||||
|
||||
=item $tb->todo_start
|
||||
|
||||
=item $tb->todo_end
|
||||
|
||||
See L<Test2::Tools::Todo> instead.
|
||||
|
||||
=item $tb->output, $tb->failure_output, and $tb->todo_output
|
||||
|
||||
These are handled via formatters now. See L<Test2::Formatter> and
|
||||
L<Test2::Formatter::TAP>.
|
||||
|
||||
=back
|
||||
|
||||
=head1 LEVEL
|
||||
|
||||
L<Test::Builder> had the C<$Test::Builder::Level> variable that you could
|
||||
modify in order to set the stack depth. This was useful if you needed to nest
|
||||
tools and wanted to make sure your file and line number were correct. It was
|
||||
also frustrating and prone to errors. Some people never even discovered the
|
||||
level variable and always had incorrect line numbers when their tools would
|
||||
fail.
|
||||
|
||||
L<Test2> uses the context system, which solves the problem a better way. The
|
||||
top-most tool get a context, and holds on to it until it is done. Any tool
|
||||
nested under the first will find and use the original context instead of
|
||||
generating a new one. This means the level problem is solved for free, no
|
||||
variables to mess with.
|
||||
|
||||
L<Test2> is also smart enough to honor c<$Test::Builder::Level> if it is set.
|
||||
|
||||
=head1 TODO
|
||||
|
||||
L<Test::Builder> used the C<$TODO> package variable to set the TODO state. This
|
||||
was confusing, and easy to get wrong. See L<Test2::Tools::Todo> for the modern
|
||||
way to accomplish a TODO state.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
151
database/perl/vendor/lib/Test2/Manual/Tooling/Testing.pm
vendored
Normal file
151
database/perl/vendor/lib/Test2/Manual/Tooling/Testing.pm
vendored
Normal file
@@ -0,0 +1,151 @@
|
||||
package Test2::Manual::Tooling::Testing;
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = '0.000139';
|
||||
|
||||
1;
|
||||
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
Test2::Manual::Tooling::Testing - Tutorial on how to test your testing tools.
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Testing your test tools used to be a complex and difficult prospect. The old
|
||||
tools such as L<Test::Tester> and L<Test::Builder::Tester> were limited, and
|
||||
fragile. Test2 on the other hand was designed from the very start to be easily
|
||||
tested! This tutorial shows you how.
|
||||
|
||||
=head1 THE HOLY GRAIL OF TESTING YOUR TOOLS
|
||||
|
||||
The key to making Test2 easily testable (specially when compared to
|
||||
Test::Builder) is the C<intercept> function.
|
||||
|
||||
use Test2::API qw/intercept/;
|
||||
|
||||
my $events = intercept {
|
||||
ok(1, "pass");
|
||||
ok(0, "fail");
|
||||
|
||||
diag("A diag");
|
||||
};
|
||||
|
||||
The intercept function lets you use any test tools you want inside a codeblock.
|
||||
No events or contexts generated within the intercept codeblock will have any
|
||||
effect on the outside testing state. The C<intercept> function completely
|
||||
isolates the tools called within.
|
||||
|
||||
B<Note:> Plugins and things that effect global API state may not be fully
|
||||
isolated. C<intercept> is intended specifically for event isolation.
|
||||
|
||||
The C<intercept> function will return an arrayref containing all the events
|
||||
that were generated within the codeblock. You can now make any assertions you
|
||||
want about the events you expected your tools to generate.
|
||||
|
||||
[
|
||||
bless({...}, 'Test2::Event::Ok'), # pass
|
||||
bless({...}, 'Test2::Event::Ok'), # fail
|
||||
bless({...}, 'Test2::Event::Diag'), # Failure diagnostics (not always a second event)
|
||||
bless({...}, 'Test2::Event::Diag'), # custom 'A diag' message
|
||||
]
|
||||
|
||||
Most test tools eventually produce one or more events. To effectively verify
|
||||
the events you get from intercept you really should read up on how events work
|
||||
L<Test2::Manual::Anatomy::Event>. Once you know about events you can move on to
|
||||
the next section which points you at some helpers.
|
||||
|
||||
=head1 ADDITIONAL HELPERS
|
||||
|
||||
=head2 Test2::Tools::Tester
|
||||
|
||||
This is the most recent set of tools to help you test your events. To really
|
||||
understand these you should familiarize yourself with
|
||||
L<Test2::Manual::Anatomy::Event>. If you are going to be writing anything more
|
||||
than the most simple of tools you should know how events work.
|
||||
|
||||
The L<Test2::Tools::Tester> documentation is a good place for further reading.
|
||||
|
||||
=head2 Test2::Tools::HarnessTester
|
||||
|
||||
The L<Test2::Tools::HarnessTester> can export the C<summarize_events()> tool.
|
||||
This tool lets you run your event arrayref through L<Test2::Harness> so that you
|
||||
can get a pass/fail summary.
|
||||
|
||||
my $summary = summarize_events($events);
|
||||
|
||||
The summary looks like this:
|
||||
|
||||
{
|
||||
plan => $plan_facet, # the plan event facet
|
||||
pass => $bool, # true if the events result in a pass
|
||||
fail => $bool, # true if the events result in a fail
|
||||
errors => $error_count, # Number of error facets seen
|
||||
failures => $failure_count, # Number of failing assertions seen
|
||||
assertions => $assertion_count, # Total number of assertions seen
|
||||
}
|
||||
|
||||
=head2 Test2::Tools::Compare
|
||||
|
||||
B<DEPRECATED> These tools were written before the switch to faceted events.
|
||||
These will still work, but are no longer the recommended way to test your
|
||||
tools.
|
||||
|
||||
The L<Test2::Tools::Compare> library exports a handful of extras to help test
|
||||
events.
|
||||
|
||||
=over 4
|
||||
|
||||
=item event $TYPE => ...
|
||||
|
||||
Use in an array check against $events to check for a specific type of event
|
||||
with the properties you specify.
|
||||
|
||||
=item fail_events $TYPE => ...
|
||||
|
||||
Use when you expect a failing assertion of $TYPE. This will automatically check
|
||||
that the next event following it is a diagnostics message with the default
|
||||
failure text.
|
||||
|
||||
B<Note:> This is outdated as a single event may now possess both the failing
|
||||
assertion AND the failing text, such events will fail this test.
|
||||
|
||||
=back
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
L<Test2::Manual> - Primary index of the manual.
|
||||
|
||||
=head1 SOURCE
|
||||
|
||||
The source code repository for Test2-Manual can be found at
|
||||
F<https://github.com/Test-More/Test2-Suite/>.
|
||||
|
||||
=head1 MAINTAINERS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 AUTHORS
|
||||
|
||||
=over 4
|
||||
|
||||
=item Chad Granum E<lt>exodist@cpan.orgE<gt>
|
||||
|
||||
=back
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2018 Chad Granum E<lt>exodist@cpan.orgE<gt>.
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the same terms as Perl itself.
|
||||
|
||||
See F<http://dev.perl.org/licenses/>
|
||||
|
||||
=cut
|
||||
Reference in New Issue
Block a user