diff --git a/lib/T2.pm b/lib/T2.pm new file mode 100644 index 000000000..46607dd29 --- /dev/null +++ b/lib/T2.pm @@ -0,0 +1,167 @@ +package T2; +use strict; +use warnings; + +my $INIT; +my $HANDLE; +sub handle { $HANDLE } + +sub import { + my $class = shift; + my ($handle) = @_; + + my ($caller, $file, $line) = caller; + + die "The ${ \__PACKAGE__ } namespace has already been initialized (Originally initiated at $INIT->[1] line $INIT->[2]) at $file line $line.\n" + if $INIT; + + unless ($handle) { + die "The '$caller' package does not provide a T2 handler at $file line $line.\n" + unless $caller->can('T2'); + + $handle = $caller->T2 or die "Could not get handle via '$caller\->T2()' at $file line $line.\n"; + } + + die "'$handle' is not a Test2::Handle instance at $file line $line.\n" + unless $handle->isa('Test2::Handle'); + + $INIT = [$caller, $file, $line]; + $HANDLE = $handle; + + for my $sym ($HANDLE->HANDLE_SUBS) { + next if $sym eq 'import'; + next if $sym eq 'handle'; + + my $code = $HANDLE->HANDLE_NAMESPACE->can($sym); + my $proto = prototype($code); + + my $header = defined($proto) ? "sub $sym($proto) {" : "sub $sym {"; + + my $line = __LINE__ + 3; + my $sub = eval <<" EOT" or die $@; +#line $line ${ \__FILE__ } +$header + my (\$f) = \@_; + shift if \$f && "\$f" eq "$class"; + goto &\$code; +}; + +\\&$sym; + EOT + + no strict 'refs'; + *$sym = $sub; + } +} + +sub AUTOLOAD { + my ($this) = @_; + + if ($this) { + shift if "$this" eq 'T2'; + shift if ref($this) eq 'T2'; + } + + my ($name) = (our $AUTOLOAD =~ m/^(?:.*::)?([^:]+)$/); + + my @caller = caller; + my $sub = $HANDLE->HANDLE_NAMESPACE->can($name) or die qq{"$name" is not provided by this T2 handle at $caller[1] line $caller[2].\n}; + goto &$sub; +} + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +T2 - Define the L namespace that can always be used to access functionality +from a Test2 bundle such as L. + +=head1 DESCRIPTION + +If you want a global C that can be called from anywhere, without needing to +import L in every package, you can do that with the L module. + +This defines the L namespace so you can always call methods on it like +C<< T2->ok(1, "pass") >> and C<< T2->done_testing >>. + +=head1 SYNOPSIS + +Create a file/package somewhere to initialize it. Only initialize it once! + + package My::Global::T2; + + # Load Test2::V1 (or future bundle) + # Add any customizations like including extra tools, overriding tools, etc. + use Test2::V1 ...; + + # Load T2, it will find the T2() handle in the current package and make it global + use T2; + + ######################################### + # Alternatively you can do this: + my $handle = Test2::V1::Handle->new(...); + require T2; + T2->import($handle); + +Now use it somewhere in your code: + + use My::Global::T2; + +Now T2 is available from any package + + T2->ok(1, "pass"); + T2->ok(0, "fail"); + + T2->done_testing; + +B In this case T2 is a package name, not a function, so C<< T2() >> will +not work. However you can import L into any package providing a T2() +function that will be used preferentially to the L namespace. + +B You can use the C form to leverage the original +prototype of the tool. + + T2::is(@foo, 3, "Array has 3 elements"); + +Without the prototype (method form does not allow prototypes) you would have to +prefix scalar on C<@foo>: + + T2->is(scalar(@foo), 3, "Array matches expections"); + +=head1 SOURCE + +The source code repository for Test2-Suite can be found at +F. + +=head1 MAINTAINERS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 AUTHORS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 COPYRIGHT + +Copyright Chad Granum Eexodist@cpan.orgE. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +See F + +=cut diff --git a/lib/Test2/API/InterceptResult.pm b/lib/Test2/API/InterceptResult.pm index c64a77c51..632cb1e64 100644 --- a/lib/Test2/API/InterceptResult.pm +++ b/lib/Test2/API/InterceptResult.pm @@ -186,7 +186,7 @@ needing a deep understanding of the event/facet model. Usually you get an instance of this class when you use C from L. - use Test2::V0; + use Test2::V1 '-iPp'; use Test2::API qw/intercept/; my $events = intercept { diff --git a/lib/Test2/API/InterceptResult/Event.pm b/lib/Test2/API/InterceptResult/Event.pm index 352dd8854..8d886e627 100644 --- a/lib/Test2/API/InterceptResult/Event.pm +++ b/lib/Test2/API/InterceptResult/Event.pm @@ -507,7 +507,7 @@ you to use when testing your test tools. =head1 SYNOPSIS - use Test2::V0; + use Test2::V1 '-iPp'; use Test2::API qw/intercept/; my $events = intercept { diff --git a/lib/Test2/Bundle/Extended.pm b/lib/Test2/Bundle/Extended.pm index f30f3218a..33d6c49aa 100644 --- a/lib/Test2/Bundle/Extended.pm +++ b/lib/Test2/Bundle/Extended.pm @@ -33,6 +33,8 @@ Test2::Bundle::Extended - Old name for Test2::V0 This bundle has been renamed to L, in which the C<':v1'> tag has been removed as unnecessary. +B L is the latest bundle, you probably want to look at that. + =head1 DESCRIPTION This is the big-daddy bundle. This bundle includes nearly every tool, and diff --git a/lib/Test2/Handle.pm b/lib/Test2/Handle.pm new file mode 100644 index 000000000..8af4c0acc --- /dev/null +++ b/lib/Test2/Handle.pm @@ -0,0 +1,296 @@ +package Test2::Handle; +use strict; +use warnings; + +our $VERSION = '1.302217'; + +require Carp; +require Test2::Util; + +use Test2::Util::HashBase qw{ + +namespace + +base + +include + +import + +stomp +}; + +my $NS = 1; + +# Things we do not want to import automagically +my %EXCLUDE_SYMBOLS = ( + BEGIN => 1, + DESTROY => 1, + DOES => 1, + END => 1, + VERSION => 1, + does => 1, + can => 1, + isa => 1, + import => 1, +); + +sub DEFAULT_HANDLE_BASE { Carp::croak("Not Implemented") } + +sub HANDLE_BASE { $_[0]->{+BASE} } + +sub HANDLE_NAMESPACE { $_[0]->{+NAMESPACE} } + +sub _HANDLE_INCLUDE { + my $self = shift; + + return $self->{+IMPORT} if $self->{+IMPORT}; + + my $ns = $self->{+NAMESPACE}; + + my $line = __LINE__ + 3; + $self->{+IMPORT} = eval <<" EOT" or die $@; +#line $line ${ \__FILE__ } + package $ns; + sub { + my (\$module, \$caller, \@imports) = \@_; + unless (eval { require(Test2::Util::pkg_to_file(\$module)); 1 }) { + my \$err = \$@; + chomp(\$err); + \$err =~ s/\.\$//; + die "\$err (called from \$caller->[1] line \$caller->[2]).\n"; + } + \$module->import(\@imports); + }; + EOT +} + +sub HANDLE_INCLUDE { + my $self = shift; + my ($mod, @imports) = @_; + @imports = @{$imports[0]} if @imports == 1 && ref($imports[0]) eq 'ARRAY'; + + my $caller = [caller]; + + $self->_HANDLE_INCLUDE->($mod, $caller, @imports); + $self->_HANDLE_WRAP($_) for @imports; +} + +sub HANDLE_SUBS { + my $self = shift; + + my @out; + + my $seen = {class => {}, export => {}}; + my @todo = ($self->{+NAMESPACE}); + + while (my $check = shift @todo) { + next if $seen->{class}->{$check}++; + + no strict 'refs'; + my $stash = \%{"$check\::"}; + push @out => grep { !$seen->{export}->{$_}++ && !$EXCLUDE_SYMBOLS{$_} && $_ !~ m/^_/ && $check->can($_) } keys %$stash; + push @todo => @{"$check\::ISA"}; + } + + return @out; +} + +sub _HANDLE_WRAP { + my $self = shift; + my ($name) = @_; + + return if $self->SUPER::can($name); + + my $wrap = sub { + my $handle = shift; + my $ns = $handle->{+NAMESPACE}; + my @caller = caller; + my $sub = $ns->can($name) or die qq{"$name" is not provided by this T2 handle at $caller[1] line $caller[2].\n}; + goto &$sub; + }; + + { + no strict 'refs'; + *$name = $wrap; + } + + return $wrap; +} + +sub import { + my $class = shift; + my ($name, %params) = @_; + + my $self = $class->new(%params); + + my $caller = caller; + no strict 'refs'; + *{"$caller\::$name"} = sub() { $self }; +} + +sub init { + my $self = shift; + + my $stomp = $self->{+STOMP} ||= 0; + my $inc = $self->{+INCLUDE} ||= []; + my $base = $self->{+BASE} ||= $self->DEFAULT_HANDLE_BASE; + + require(Test2::Util::pkg_to_file($base)); + + my $new; + my $ns = $self->{+NAMESPACE} ||= do { $new = 1; __PACKAGE__ . '::GEN_' . $NS++ }; + + my $stash = do { no strict 'refs'; \%{"$ns\::"} }; + + Carp::croak("Namespace '$ns' already appears to be populated") if !$stomp && keys %$stash; + + $INC{Test2::Util::pkg_to_file($ns)} ||= __FILE__ if $new; + + { + no strict 'refs'; + push @{"$ns\::ISA"} => $self->{+BASE}; + } + + if (my $include = $self->{+INCLUDE}) { + my $r = ref($include); + if ($r eq 'ARRAY') { + $self->HANDLE_INCLUDE(ref($_) ? @{$_} : $_) for @$include; + } + elsif ($r eq 'HASH') { + $self->HANDLE_INCLUDE($_ => $include->{$_}) for keys %$include; + } + else { + die "Not sure what to do with '$r'"; + } + } +} + +sub can { + my $self = shift; + my ($name) = @_; + + my $sub = $self->SUPER::can($name); + return $sub if $sub; + + return undef unless ref $self; + + $self->{+NAMESPACE}->can($name) or return undef; + return $self->_HANDLE_WRAP($name); +} + +sub AUTOLOAD { + my ($self) = @_; + + my ($name) = (our $AUTOLOAD =~ m/^(?:.*::)?([^:]+)$/); + return if $EXCLUDE_SYMBOLS{$name}; + + my $wrap = $self->_HANDLE_WRAP($name); + goto &$wrap; +} + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Test2::Handle - Base class for Test2 handles used in V# bundles. + +=head1 DESCRIPTION + +This is what you interact with when you use the C function in a test that +uses L. + +=head1 SYNOPSIS + +=head2 RECOMMENDED + + use Test2::V1; + + my $handle = T2(); + + $handle->ok(1, "Passing Test"); + +=head2 WITHOUT SUGAR + + use Test2::Handle(); + + my $handle = Test2::Handle->new(base => 'Test2::V1::Base'); + + $handle->ok(1, "Passing test"); + +=head1 METHODS + +Most methods are delegated to the base class provided at construction. There +are however a few methods that are defined by this package itself. + +=over 4 + +=item $base = $class_or_inst->DEFAULT_HANDLE_BASE + +Get the default handle base. This throws an exception on the base handle class, +you should override it in a subclass. + +=item $base = $inst->HANDLE_BASE + +In this base class this method always throws an exception. In a subclass it +should return the default base class to use for that subclass. + +=item $namespace = $inst->HANDLE_NAMESPACE + +Get the namespace used to store function we wrap as methods. + +=item @sub_names = $inst->HANDLE_SUBS + +Get a list of all subs available in the handle namespace. + +=item $inst->HANDLE_INCLUDE($package, @subs) + +Import the specified subs from the specified package into our internal +namespace. + +=item $inst = $class->import() + +Used to create a C sub in your namsepace at import. + +=item $inst->init() + +Internally used to intialize and validate the handle object. + +=item AUTOLOAD + +Internally used to wrap functions as methods. + +=back + +=head1 SOURCE + +The source code repository for Test2-Suite can be found at +F. + +=head1 MAINTAINERS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 AUTHORS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 COPYRIGHT + +Copyright Chad Granum Eexodist@cpan.orgE. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +See F + +=cut diff --git a/lib/Test2/Manual/Testing.pm b/lib/Test2/Manual/Testing.pm index 599d690d9..256d3c07d 100644 --- a/lib/Test2/Manual/Testing.pm +++ b/lib/Test2/Manual/Testing.pm @@ -39,7 +39,7 @@ lexically scoped, or they may be global. =item Test2::Bundle::* Bundles combine toolsets and plugins together to reduce your boilerplate. First -time test writers are encouraged to start with the L bundle (which +time test writers are encouraged to start with the L bundle (which is an exception to the namespace rule as it does not live under C). If you find yourself loading several plugins and toolsets over and over again you could benefit from writing your own bundle. @@ -69,18 +69,18 @@ and define new ones, typically with a new bundle. In short, if we feel the need to break something we will do so by creating a new bundle, and discouraging the old one, but we will not break the old one. -So for example, if you use L, and L you +So for example, if you use L, and L you should have this in your config: [Prereqs / TestRequires] - Test2::V0 = 0.000060 + Test2::V1 = 0.000060 You B do this: [Prereqs / TestRequires] Test2::Suite = 0.000060 -Because L might not always be part of L. +Because L might not always be part of L. When writing new tests you should often check L to see what the current recommended bundle is. @@ -88,28 +88,28 @@ current recommended bundle is. =head3 Dist::Zilla [Prereqs / TestRequires] - Test2::V0 = 0.000060 + Test2::V1 = 0.000060 =head3 ExtUtils::MakeMaker my %WriteMakefileArgs = ( ..., "TEST_REQUIRES" => { - "Test2::V0" => "0.000060" + "Test2::V1" => "0.000060" }, ... ); =head3 Module::Install - test_requires 'Test2::V0' => '0.000060'; + test_requires 'Test2::V1' => '0.000060'; =head3 Module::Build my $build = Module::Build->new( ..., test_requires => { - "Test2::V0" => "0.000060", + "Test2::V1" => "0.000060", }, ... ); diff --git a/lib/Test2/Manual/Testing/Introduction.pm b/lib/Test2/Manual/Testing/Introduction.pm index d18b21100..053b71fc5 100644 --- a/lib/Test2/Manual/Testing/Introduction.pm +++ b/lib/Test2/Manual/Testing/Introduction.pm @@ -26,7 +26,7 @@ C<.t> file extension. C: - use Test2::V0; + use Test2::V1 -ipP; # Assertions will go here @@ -36,10 +36,26 @@ This is all the boilerplate you need. =over 4 -=item use Test2::V0; +=item use Test2::V1 -ipP; This loads a collection of testing tools that will be described later in the -tutorial. This will also turn on C and C for you. +tutorial. See L for more details, but for starters '-ipP' is a good +set of import flags. + +If you do not like importing a ton of symbols or enabling pragmas/plugins all +in one swoop you can do C<< use Test2::V1; >>. If you do this you will need to +use the T2() function to access tools, and load any pragmas/plugins manually: + + use Test2::V1; + use strict; + use warnings; + use Test2::Plugin::UTF8; + use Test2::Plugin::SRand; + + T2->ok(1, "pass"); + T2->is(5, 5, "5 is 5"); + + T2->done_testing; =item done_testing; @@ -59,28 +75,28 @@ L to their own dists at any time. =head3 Dist::Zilla [Prereqs / TestRequires] - Test2::V0 = 0.000060 + Test2::V1 = 0.000060 =head3 ExtUtils::MakeMaker my %WriteMakefileArgs = ( ..., "TEST_REQUIRES" => { - "Test2::V0" => "0.000060" + "Test2::V1" => "0.000060" }, ... ); =head3 Module::Install - test_requires 'Test2::V0' => '0.000060'; + test_requires 'Test2::V1' => '0.000060'; =head3 Module::Build my $build = Module::Build->new( ..., test_requires => { - "Test2::V0" => "0.000060", + "Test2::V1" => "0.000060", }, ... ); @@ -94,12 +110,20 @@ that a condition is true. Here is a complete C: - use Test2::V0; + use Test2::V1 -import; ok(1, "1 is true, so this will pass"); done_testing; +If you are doing it without imports: + + use Test2::V1; + + T2->ok(1, "1 is true, so this will pass"); + + T2->done_testing; + =head1 RUNNING THE TEST Test files are simply scripts. Just like any other script you can run the test @@ -204,6 +228,8 @@ There are 2 primary ways to set the plan: =item done_testing() +=item T2->done_testing() + The most common, and recommended way to set a plan is to add C at the end of your test file. This will automatically calculate the plan for you at the end of the test. If the test were to exit early then C @@ -211,6 +237,8 @@ would not run and no plan would be found, forcing a failure. =item plan($COUNT) +=item T2->plan($COUNT) + The C function allows you to specify an exact number of assertions you want to run. If you run too many or too few assertions then the plan will not match and it will be counted as a failure. The primary problem with this way of @@ -224,13 +252,15 @@ cannot be done in the middle of making assertions. =head1 ADDITIONAL ASSERTION TOOLS -The L bundle provides a lot more than C, +The L bundle provides a lot more than C, C, and C. The biggest tools to note are: =over 4 =item is($a, $b, $description) +=item T2->is($a, $b, $description) + C allows you to compare 2 structures and insure they are identical. You can use it for simple string comparisons, or even deep data structure comparisons. @@ -241,6 +271,8 @@ comparisons. =item like($a, $b, $description) +=item T2->like($a, $b, $description) + C is similar to C except that it only checks items listed on the right, it ignores any extra values found on the left. diff --git a/lib/Test2/Manual/Testing/Migrating.pm b/lib/Test2/Manual/Testing/Migrating.pm index be8192e5c..c76099509 100644 --- a/lib/Test2/Manual/Testing/Migrating.pm +++ b/lib/Test2/Manual/Testing/Migrating.pm @@ -99,7 +99,10 @@ BEFORE: AFTER: - use Test2::V0; + use strict; + use warnings; + + use Test2::V1 '-import'; plan(11); use Scalar::Util; @@ -107,26 +110,43 @@ AFTER: =over 4 -=item Replace Test::More with Test2::V0 +=item Replace Test::More with Test2::V1 -L is the recommended bundle. In a full migration you -will want to replace L with the L bundle. +L is the recommended bundle. In a full migration you +will want to replace L with the L bundle. B You should always double check the latest L to see if there is a new recommended bundle. When writing a new test you should always use the newest Test::V# module. Higher numbers are newer version. +You probably want the C<-import> argument when using L as it will +populate your namespace with all the tools you expect from a test helper +module. However if you want your namespace left clean you can omit the +argument, in which case C is the only thing added to your namespace, and +it can be used to access the tools: + + use Test2::V1; + + T2->ok(1, "pass"); + + T2->done_testing; + =item NOTE: srand -When srand is on (default) it can cause problems with things like L -which will end up attempting the same "random" filenames for every test process -started on a given day (or sharing the same seed). +When srand is on (not default in V1, but Default in older V0) it can cause +problems with things like L which will end up attempting the same +"random" filenames for every test process started on a given day (or sharing +the same seed). If this is a problem for you then please disable srand when loading -L: + +For L: use Test2::V0 -no_srand => 1; +For L simply do not use the C<-P>, or C<-Plugins> import option and it will not be loaded. + + =item Stop using use_ok() C has been removed. a C statement will throw an exception @@ -144,9 +164,12 @@ The main difference here is that there is a space instead of an underscore. C has been removed just like C. There is no L module equivalent here. Just use C. -=item Remove strict/warnings (optional) +=item (optional) remove strict/warnings -The L bundle turns strict and warnings on for you. +In the L bundle turns strict and warnings on for you. + +In the L bundle you must ask for strict and warnings with one of the +following import args: C<-p>, C<-pragmas>, C<-strict>, C<-warnings>. =item Change where the plan is set @@ -330,10 +353,14 @@ argument, then a test name as the third argument. =head1 FINAL VERSION +=head2 IMPORTS + ##################### # Boilerplate - use Test2::V0; + use strict; + use warnings; + use Test2::V1 '-import'; plan(11); use Scalar::Util; @@ -391,6 +418,129 @@ argument, then a test name as the third argument. can_ok(__PACKAGE__, [qw/ok is/], "have expected subs"); +=head2 REDUCED BOILERPLATE + + ##################### + # Boilerplate + + use Test2::V1 '-ipP'; + plan(11); + + use Scalar::Util; + require Exporter; + + ##################### + # Simple assertions (no changes) + + ok(1, "pass"); + + is("apple", "apple", "Simple string compare"); + + like("foo bar baz", qr/bar/, "Regex match"); + + ##################### + # Todo + + todo "These are todo" => sub { + ok(0, "oops"); + }; + + ##################### + # Deep comparisons + + is([1, 2, 3], [1, 2, 3], "Deep comparison"); + + ##################### + # Comparing references + + my $ref = [1]; + ref_is($ref, $ref, "Check that we have the same ref both times"); + + ##################### + # Things that are gone + + is([1], [1], "array comparison"); + is({a => 1}, {a => 1}, "hash comparison"); + + is([1, 3, 2], bag { item 1; item 2; item 3; end }, "set comparison"); + + use Data::Dumper; + note Dumper([1, 2, 3]); + + { + package THING; + sub new { bless({}, shift) } + } + + my $thing = THING->new; + + ##################### + # Tools that changed + + isa_ok($thing, ['THING'], 'got a THING'); + + can_ok(__PACKAGE__, [qw/ok is/], "have expected subs"); + +=head2 CLEAN NAMESPACE + + use Test2::V1; + T2->plan(11); + + use Scalar::Util; + require Exporter; + + ##################### + # Simple assertions (no changes) + + T2->ok(1, "pass"); + + T2->is("apple", "apple", "Simple string compare"); + + T2->like("foo bar baz", qr/bar/, "Regex match"); + + ##################### + # Todo + + T2->todo("These are todo" => sub { + ok(0, "oops"); + }); + + ##################### + # Deep comparisons + + T2->is([1, 2, 3], [1, 2, 3], "Deep comparison"); + + ##################### + # Comparing references + + my $ref = [1]; + T2->ref_is($ref, $ref, "Check that we have the same ref both times"); + + ##################### + # Things that are gone + + T2->is([1], [1], "array comparison"); + T2->is({a => 1}, {a => 1}, "hash comparison"); + + T2->is([1, 3, 2], bag { item 1; item 2; item 3; end }, "set comparison"); + + use Data::Dumper; + T2->note(Dumper([1, 2, 3])); + + { + package THING; + sub new { bless({}, shift) } + } + + my $thing = THING->new; + + ##################### + # Tools that changed + + T2->isa_ok($thing, ['THING'], 'got a THING'); + + T2->can_ok(__PACKAGE__, [qw/ok is/], "have expected subs"); + =head1 SEE ALSO L - Primary index of the manual. diff --git a/lib/Test2/Plugin/BailOnFail.pm b/lib/Test2/Plugin/BailOnFail.pm index b05320543..e723d0326 100644 --- a/lib/Test2/Plugin/BailOnFail.pm +++ b/lib/Test2/Plugin/BailOnFail.pm @@ -40,12 +40,12 @@ diagnostics they may need. =head1 SYNOPSIS - use Test2::V0; + use Test2::V1; use Test2::Plugin::BailOnFail; - ok(1, "pass"); - ok(0, "fail"); - ok(1, "Will not run"); + T2->ok(1, "pass"); + T2->ok(0, "fail"); + T2->ok(1, "Will not run"); =head1 SOURCE diff --git a/lib/Test2/Plugin/DieOnFail.pm b/lib/Test2/Plugin/DieOnFail.pm index 2e4344bfd..ec787bb9d 100644 --- a/lib/Test2/Plugin/DieOnFail.pm +++ b/lib/Test2/Plugin/DieOnFail.pm @@ -38,12 +38,12 @@ This gives the tools the ability to output any extra diagnostics they may need. =head1 SYNOPSIS - use Test2::V0; + use Test2::V1; use Test2::Plugin::DieOnFail; - ok(1, "pass"); - ok(0, "fail"); - ok(1, "Will not run"); + T2->ok(1, "pass"); + T2->ok(0, "fail"); + T2->ok(1, "Will not run"); =head1 SOURCE diff --git a/lib/Test2/Suite.pm b/lib/Test2/Suite.pm index 373d82477..559afdd7c 100644 --- a/lib/Test2/Suite.pm +++ b/lib/Test2/Suite.pm @@ -62,30 +62,24 @@ also produces undesirable side-effects. These do not live in the bundle namespace as they are the primary ways to use Test2::Suite. -The current latest is L. +The current latest is L. - use Test2::V0; - # strict and warnings are on for you now. + use Test2::V1; - ok(...); + T2->ok(...); # Note: is does deep checking, unlike the 'is' from Test::More. - is(...); + $T2->is(...); ... - done_testing; - -This bundle includes every tool listed in the L section below, -except for L. This bundle provides most of what -anyone writing tests could need. This is also the preferred bundle/toolset of -the L author. + T2->done_testing; -See L for complete documentation. +See L for complete documentation. =item Extended -B<** Deprecated **> See L +B<** Deprecated **> See L or L. use Test2::Bundle::Extended; # strict and warnings are on for you now. diff --git a/lib/Test2/V0.pm b/lib/Test2/V0.pm index 385f4a449..6421daf72 100644 --- a/lib/Test2/V0.pm +++ b/lib/Test2/V0.pm @@ -147,6 +147,10 @@ __END__ Test2::V0 - 0Th edition of the Test2 recommended bundle. +=head1 V1 IS OUT NOW! + +See L for the most recent "Recommended" bundle. + =head1 DESCRIPTION This is the big-daddy bundle. This bundle includes nearly every tool, and diff --git a/lib/Test2/V1.pm b/lib/Test2/V1.pm new file mode 100644 index 000000000..4409f3784 --- /dev/null +++ b/lib/Test2/V1.pm @@ -0,0 +1,1107 @@ +package Test2::V1; +use strict; +use warnings; + +our $VERSION = '1.302217'; + +use Carp qw/croak/; + +use Test2::V1::Base(); +use Test2::V1::Handle(); + +use Test2::Plugin::ExitSummary(); +use Test2::Plugin::SRand(); +use Test2::Plugin::UTF8(); +use Test2::Tools::Target(); + +# Magic reference to check against later +my $SET = \'set'; + +# Lists of pragmas and plugins +my @PRAGMAS = qw/strict warnings/; +my @PLUGINS = qw/utf8 srand summary target/; + +sub import { + my $class = shift; + + my $caller = caller; + + croak "Got One or more undefined arguments, this usually means you passed in a single-character flag like '-p' without quoting it, which conflicts with the -p builtin" + if grep { !defined($_) } @_; + + my ($requested_exports, $options) = $class->_parse_args(\@_); + + my $pragmas = $class->_compute_pragmas($options); + my $plugins = $class->_compute_plugins($options); + + my ($handle_name, $handle) = $class->_build_handle($options); + my $ns = $handle->HANDLE_NAMESPACE; + + unshift @$requested_exports => $handle->HANDLE_SUBS() if delete $options->{'-import'}; + + unshift @$requested_exports => grep { my $p = prototype($ns->can($_)); $p && $p =~ '&' } $handle->HANDLE_SUBS() if delete $options->{'-x'}; + + my $exports = $class->_build_exports($handle, $requested_exports); + unless (delete $options->{'-no-T2'}) { + my $h = $handle; + $exports->{$handle_name} = sub() { $h }; + } + + croak "Unknown option(s): " . join(', ', sort keys %$options) if keys %$options; + + strict->import() if $pragmas->{strict}; + 'warnings'->import() if $pragmas->{warnings}; + Test2::Plugin::UTF8->import() if $plugins->{utf8}; + Test2::Plugin::ExitSummary->import() if $plugins->{summary}; + + if (my $set = $plugins->{srand}) { + Test2::Plugin::SRand->import((ref($set) && "$set" ne "$SET") ? $set->{seed} : ()); + } + + if (my $target = $plugins->{target}) { + Test2::Tools::Target->import_into($caller, $plugins->{target}) unless "$target" eq "$SET"; + } + + for my $exp (keys %$exports) { + no strict 'refs'; + *{"$caller\::$exp"} = $exports->{$exp}; + } +} + +sub _build_exports { + my $class = shift; + my ($handle, $requested) = @_; + + my %exports; + + while (my $exp = shift @$requested) { + if ($exp =~ m/^!(.+)$/) { + delete $exports{$1}; + next; + } + + my $code = $handle->HANDLE_NAMESPACE->can($exp) or croak "requested export '$exp' is not available"; + + my $args = shift @$requested if @$requested && ref($requested->[0]) eq 'HASH'; + + my $name = $exp; + if ($args) { + $name = delete $args->{-as} if $args->{-as}; + $name = delete($args->{-prefix}) . $name if $args->{-prefix}; + $name = $name . delete($args->{-postfix}) if $args->{-postfix}; + } + + $exports{$name} = $code; + } + + return \%exports; +} + +sub _build_handle { + my $class = shift; + my ($options) = @_; + + my $handle_opts = delete $options->{'-T2'} || {}; + my $handle_name = delete $handle_opts->{'-as'} || delete $handle_opts->{'as'} || 'T2'; + my $handle = Test2::V1::Handle->new(%$handle_opts); + + return ($handle_name, $handle); +} + +sub _compute_plugins { + my $class = shift; + my ($options) = @_; + + my $plugins = { summary => $SET }; + + if (my $plug = delete $options->{'-plugins'}) { + if (ref($plug)) { + $plugins = $plug; + } + else { + $plugins = { map { $_ => $SET } @PLUGINS }; + } + } + + for my $plug (@PLUGINS) { + my $set = delete $options->{"-$plug"}; + $plugins->{$plug} = $set if $set && "$set" ne "$SET"; + $plugins->{$plug} = $set unless defined $plugins->{$plug}; + } + + return $plugins; +} + +sub _compute_pragmas { + my $class = shift; + my ($options) = @_; + + my $pragmas = {}; + if (my $prag = delete $options->{'-pragmas'}) { + if (ref($prag) && "$prag" ne "$SET") { + $pragmas = $prag; + } + else { + $pragmas = { map { $_ => $SET } @PRAGMAS }; + } + } + + for my $prag (@PRAGMAS) { + my $set = delete $options->{"-$prag"}; + $pragmas->{$prag} = $set if $set && "$set" ne "$SET"; + $pragmas->{$prag} = $set unless defined $pragmas->{$prag}; + } + + return $pragmas +} + +sub _parse_args { + my $class = shift; + my ($args) = @_; + + my (@exports, %options); + + while (my $arg = shift @$args) { + $arg = '-T2' if $arg eq 'T2'; + push @exports => $arg and next unless substr($arg, 0, 1) eq '-'; + $options{$arg} = shift @$args and next if $arg eq '-target'; + $options{$arg} = (@$args && (ref($args->[0]) || "$args->[0]" eq "1" || "$args->[0]" eq "0")) ? shift @$args : $SET; + } + + if (my $inc = delete $options{'-include'}) { + $options{'-T2'}->{include} = $inc; + } + + for my $key (keys %options) { + next unless $key =~ m/^-([ipP]{1,3})$/; + delete $options{$key}; + for my $flag (split //, $1) { + $options{"-$flag"} = 1; + } + } + + $options{'-import'} ||= 1 if delete $options{'-i'}; + $options{'-pragmas'} ||= 1 if delete $options{'-p'}; + $options{'-plugins'} ||= 1 if delete $options{'-P'}; + + return (\@exports, \%options); +} + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Test2::V1 - V1 edition of the Test2 recommended bundle. + +=head1 DESCRIPTION + +This is the first sequel to L. This module is recommended over +L for new tests. + +=head2 Key differences from L + +=over 4 + +=item Only 1 export by default: T2() + +=item No pragmas by default + +=item srand and utf8 are not enabled by default + +=item Easy to still import everything + +=item East to still enable pragmas + +=back + +=head1 NAMING, USING, DEPENDING + +This bundle should not change in a I incompatible way. Some minor +breaking changes, specially bugfixes, may be allowed. If breaking changes are +needed then a new C module should be released instead. + +Adding new optional exports, and new methods on the T2() handle are not +considered breaking changes, and are allowed without bumping the V# number. +Adding new plugin shortcuts is also allowed, but they cannot be added to the +C<-P> or C<-plugins> shortcuts without a bump in V# number. + +As new C modules are released old ones I be moved to different cpan +distributions. You should always use a specific bundle version and list that +version in your distributions testing requirements. You should never simply +list L as your modules dep, instead list the specific bundle, or +tools and plugins you use directly in your metadata. + +See the L section for an explanation of why L was +created. + +=head1 SYNOPSIS + +=head2 RECOMMENDED + + use Test2::V1 -utf8; + + T2->ok(1, "pass"); + + T2->is({1 => 1}, {1 => 1}, "Structures Match"); + + # Note that prototypes do not work in method form: + my @foo = (1, 2, 3); + T2->is(scalar(@foo), 3, "Needed to force scalar context"); + + T2->done_testing; + +=head2 WORK LIKE V0 DID + + use Test2::V1 -ipP; + + ok(1, "pass"); + + is({1 => 1}, {1 => 1}, "Structures Match"); + + my @foo = (1, 2, 3); + is(@foo, 3, "Prototype forces @foo into scalar context"); + + # You still have access to T2 + T2->ok(1, "Another Pass"); + + done_testing; + +The C<-ipP> argument is short for C<-include, -pragmas, -plugins> which together enable all +pragmas, plugins, and import all symbols. + +B The order in which C, C

, and C

appear is not important; +C<-Ppi> and C<-piP> and any other order are all perfectly valid. + +=head2 IMPORT ARGUMENT GUIDE + +=over 4 + +=item C<-P> or C<-plugins> + +Shortcut to include the following plugins: L, +L, L. + +=item C<-p> or C<-pragmas> + +Shortcut to enable the following pragmas: C, C. + +=item C<-i> or C<-import> + +Shortcut to import all possible exports. + +=item C<-x> + +Shortcut to import any sub that has '&' in its prototype, things like +C<< dies { ... } >>, C<< warns { ... } >>, etc. + +While these can be used in method form: C<< T2->dies(sub { ... }) >> it is a +little less convenient than having them imported. '-x' will import all of +these, and any added in the future or included via an C<< -include => ... >> +import argument. + +=item C<-ipP>, C<-pPi>, C<-pP>, C<-Pix>, etc.. + +The C, C

, C

, and C short options may all be grouped in any order +following a single dash. + +=item C<@EXPORT_LIST> + +Any arguments provided that are not prefixed with a C<-> will be assumed to be +export requests. If there is an exported sub by the given name it will be +imported into your namespace. If there is no such sub an exception will be +thrown. + +=item C + +You can prefix an export name with C to exclude it at import time. This is +really only usedul when combined with C<-import> or C<-i>. + +=item C<< EXPORT_NAME => { -as => "ALT_NAME" } >> + +=item C<< EXPORT_NAME => { -prefix => "PREFIX_" } >> + +=item C<< EXPORT_NAME => { -postfix => "_POSTFIX" } >> + +You may specify a hashref after an export name to rename it, or add a +prefix/postfix to the name. + +=back + +=head2 RENAMING IMPORTS + + use Test2::V1 '-import', '!ok', ok => {-as => 'my_ok'}; + +Explanation: + +=over 4 + +=item '-import' + +Bring in ALL imports, no need to list them all by hand. + +=item '!ok' + +Do not import C (remove it from the list added by '-import') + +=item ok => {-as => 'my_ok'} + +Actually, go ahead and import C but under the name C. + +=back + +If you did not add the C<'!ok'> argument then you would have both C and +C + +=head1 MAKING THE T2 HANDLE GLOBAL + +If you want a global C that can be called from anywhere, without needing to +import L in every package, you can do that with the L module. + +=head1 PRAGMAS AND PLUGINS + +B +B + +This is a significant departure from L. + +You can enable all of these with the C<-pP> argument, which is short for +C<-plugins, -pragmas>. C

is short for plugins, and C

is short for +pragmas. When using the single-letter form they may both be together following +a single dash, and can be in any order. They may also be combined with C to +bring in all imports. C<-p> or C<-P> ont heir own are also perfectly valid. + +=over 4 + +=item strict + +You can enable this with any of these arguments: C<-strict>, C<-p>, C<-pragmas>. + +This enables strict for you. + +=item warnings + +You can enable this with any of these arguments: C<-warnings>, C<-p>, C<-pragmas>. + +This enables warnings for you. + +=item srand + +You can enable this in multiple ways: + + use Test2::V1 -srand + use Test2::V1 -P + use Test2::V1 -plugins + +See L. + +This will set the random seed to today's date. + +You can also set a random seed: + + use Test2::V1 -srand => { seed => 'my seed' }; + +=item utf8 + +You can enable this in multiple ways: + + use Test2::V1 -utf8 + use Test2::V1 -P + use Test2::V1 -plugins + +See L. + +This will set the file, and all output handles (including formatter handles), to +utf8. This will turn on the utf8 pragma for the current scope. + +=item summary + +This is turned on by default. + +You can avoid enabling it at import this way: + + use Test2::V1 -summary => 0; + +See L. + +This plugin has no configuration. + +=back + +=head1 ENVIRONMENT VARIABLES + +See L for a list of meaningful environment variables. + +=head1 API FUNCTIONS + +See L for these + +=over 4 + +=item $ctx = T2->context() + +=item $events = T2->intercept(sub { ... }); + +=back + +=head1 THE T2() HANDLE + +The C subroutine imported into your namespace returns an instance of +L. This gives you a handle on all the tools included by +default. It also creates a completely new namespace for use by your test that +can have additional tools added to it. + +=head2 ADDING/OVERRIDING TOOLS IN YOUR T2 HANDLE + + # Method 1 + use Test2::V1 T2 => { + include => [ + ['Test2::Tools::MyTool', 'my_tool', 'my_other_tool'], + ['Data::Dumper', 'Dumper'], + ], + }; + + # Method 2 + use Test2::V1 T2 => { + include => { + 'Test2::Tools::MyTool' => ['my_tool', 'my_other_tool'], + 'Data::Dumper' => 'Dumper', + }, + }; + + # Method 3 (This also works with a hashref instead of an arrayref) + use Test2::V1 -include => [ + ['Test2::Tools::MyTool', 'my_tool', 'my_other_tool'], + ['Data::Dumper', 'Dumper'], + ]; + + # Method 4 + T2->include('Test2::Tools::MyTool', 'my_tool', 'my_other_tool'); + T2->include('Data::Dumper', 'Dumper'); + + # Using them: + + T2->my_tool(...); + + T2->Dumper({hi => 'there'}); + +Note that you MAY override original tools such as ok(), note(), etc. by +importing different copies this way. The first time you do this there should be +no warnings or errors. If you pull in multiple tools of the same name an +redefine warning is likely. + +This also effects exports: + + use Test2::V1 -import, -include => ['Data::Dumper']; + + print Dumper("Dumper can be imported from your include!"); + +=head2 OTHER HANDLE OPTIONS + + use Test2::V1 T2 => { + include => $ARRAYREF_OR_HASHREF, + namespace => $NAMESPACE, + base => $BASE_PACKAGE // 'T2', + stomp => $BOOL, + }; + +=over 4 + +=item include => $ARRAYREF_OR_HASHREF + +See L. + +=item namespace => $NAMESPACE + +Normally a new namespace will be generated for you. You B rely on the +package name being anything specific unless you provide your own. + +The namespace here will be where any tools you 'include' will be imported into. +It will also have its base class set to the base class you specify, or the +L module if you do not provide any. + +If this namespace already has any symbols defined in it an exception will be +thrown unless the C argument is set to true (not recommended). + +=item stomp => $BOOL + +Used to allow the handle to stomp on an existing namespace (NOT RECOMMENDED). + +=item base => $BASE + +Set the base class from which functions should be inherited. Normally this is +set to L. + +Another interesting use case is to have multiple handles that use eachothers +namespaces as base classes: + + use Test2::V1; + + use Test2::V1::Handle( + 'T3', + base => T2->HANDLE_NAMESPACE, + include => {'Alt::Ok' => 'ok'}; + ); + + T3->ok(1, "This uses ok() from Alt::Ok, but all other -> methods are the original"); + T3->done_testing(); # Uses the original done_testing + +=back + +=head1 EXAMPLE USE CASES + +=head2 OVERRIDING INCLUDED TOOLS WITH ALTERNATES + +Lets say you want to use the L version of C, +C instead of the L versions, and also +wanted to import everything else L provides. + + use Test2::V1 -import, -include => ['Test2::Warnings']; + +The C<< -include => ['Test2::Warnings'] >> option means we want to import the +default set of imports from L into our C handle's +private namespace. This will override any methods that were also previously +defined by default. + +The C<-import> option means we want to import all subs into the current namespace. +This includes anything we got from L, and we will get the +L version of those subs. + + like( + warning { warn 'xxx' }, # This is the Test2::Warnings version of 'warning' + qr/xxx/, + "Got expected warning" + ); + +=head1 TOOLS + +=head2 TARGET + +I + +See L. + +You can specify a target class with the C<-target> import argument. If you do +not provide a target then C<$CLASS> and C will not be imported. + + use Test2::V1 -target => 'My::Class'; + + print $CLASS; # My::Class + print CLASS(); # My::Class + +Or you can specify names: + + use Test2::V1 -target => { pkg => 'Some::Package' }; + + pkg()->xxx; # Call 'xxx' on Some::Package + $pkg->xxx; # Same + +=over 4 + +=item $CLASS + +Package variable that contains the target class name. + +=item $class = CLASS() + +Constant function that returns the target class name. + +=back + +=head2 DEFER + +See L. + +=over 4 + +=item def $func => @args; + +I + +=item do_def() + +I + +=back + +=head2 BASIC + +See L. + +=over 4 + +=item ok($bool, $name) + +=item ok($bool, $name, @diag) + +I + +=item pass($name) + +=item pass($name, @diag) + +I + +=item fail($name) + +=item fail($name, @diag) + +I + +=item diag($message) + +I + +=item note($message) + +I + +=item $todo = todo($reason) + +=item todo $reason => sub { ... } + +I + +=item skip($reason, $count) + +I + +=item plan($count) + +I + +=item skip_all($reason) + +I + +=item done_testing() + +I + +=item bail_out($reason) + +I + +=back + +=head2 COMPARE + +See L. + +=over 4 + +=item is($got, $want, $name) + +I + +=item isnt($got, $do_not_want, $name) + +I + +=item like($got, qr/match/, $name) + +I + +=item unlike($got, qr/mismatch/, $name) + +I + +=item $check = match(qr/pattern/) + +I + +=item $check = mismatch(qr/pattern/) + +I + +=item $check = validator(sub { return $bool }) + +I + +=item $check = hash { ... } + +I + +=item $check = array { ... } + +I + +=item $check = bag { ... } + +I + +=item $check = object { ... } + +I + +=item $check = meta { ... } + +I + +=item $check = number($num) + +I + +=item $check = string($str) + +I + +=item $check = bool($bool) + +I + +=item $check = check_isa($class_name) + +I + +=item $check = in_set(@things) + +I + +=item $check = not_in_set(@things) + +I + +=item $check = check_set(@things) + +I + +=item $check = item($thing) + +I + +=item $check = item($idx => $thing) + +I + +=item $check = field($name => $val) + +I + +=item $check = call($method => $expect) + +I + +=item $check = call_list($method => $expect) + +I + +=item $check = call_hash($method => $expect) + +I + +=item $check = prop($name => $expect) + +I + +=item $check = check($thing) + +I + +=item $check = T() + +I + +=item $check = F() + +I + +=item $check = D() + +I + +=item $check = DF() + +I + +=item $check = E() + +I + +=item $check = DNE() + +I + +=item $check = FDNE() + +I + +=item $check = U() + +I + +=item $check = L() + +I + +=item $check = exact_ref($ref) + +I + +=item end() + +I + +=item etc() + +I + +=item filter_items { grep { ... } @_ } + +I + +=item $check = event $type => ... + +I + +=item @checks = fail_events $type => ... + +I + +=back + +=head2 CLASSIC COMPARE + +See L. + +=over 4 + +=item cmp_ok($got, $op, $want, $name) + +I + +=back + +=head2 SUBTEST + +See L. + +=over 4 + +=item subtest $name => sub { ... }; + +I + +(Note: This is called C in the Tools module.) + +=back + +=head2 CLASS + +See L. + +=over 4 + +=item can_ok($thing, @methods) + +I + +=item isa_ok($thing, @classes) + +I + +=item DOES_ok($thing, @roles) + +I + +=back + +=head2 ENCODING + +See L. + +=over 4 + +=item set_encoding($encoding) + +I + +=back + +=head2 EXPORTS + +See L. + +=over 4 + +=item imported_ok('function', '$scalar', ...) + +I + +=item not_imported_ok('function', '$scalar', ...) + +I + +=back + +=head2 REF + +See L. + +=over 4 + +=item ref_ok($ref, $type) + +I + +=item ref_is($got, $want) + +I + +=item ref_is_not($got, $do_not_want) + +I + +=back + +See L. + +=over 4 + +=item is_refcount($ref, $count, $description) + +I + +=item is_oneref($ref, $description) + +I + +=item $count = refcount($ref) + +I + +=back + +=head2 MOCK + +See L. + +=over 4 + +=item $control = mock ... + +I + +=item $bool = mocked($thing) + +I + +=back + +=head2 EXCEPTION + +See L. + +=over 4 + +=item $exception = dies { ... } + +I + +=item $bool = lives { ... } + +I + +=item $bool = try_ok { ... } + +I + +=back + +=head2 WARNINGS + +See L. + +=over 4 + +=item $count = warns { ... } + +I + +=item $warning = warning { ... } + +I + +=item $warnings_ref = warnings { ... } + +I + +=item $bool = no_warnings { ... } + +I + +=back + +=head1 JUSTIFICATION + +L is a rich set of tools. But it made several assumptions about how +it would be used. The assumptions are fairly good for new users writing simple +scripts, but they can get in the way in many cases. + +=head2 PROBLEMS WITH V0 + +=over 4 + +=item Assumptions of strict/warnings + +Many people would put custom strict/warnings settings at the top of their +tests, only to have them wiped out when they use L. + +=item Assumptions of UTF8 + +Occasionally you do not want this assumption. The way it impacts all your +regular and test handles, as well as how your source is read, can be a problem +if you are not working with UTF8, or have other plans entirly. + +=item Huge default set of exports, which can grow + +Sometimes you want to keep your namespace clean. + +Sometimes you import a tool that does not conflict with anything in +L, then we go and add a new tool which conflicts with yours! We make +a point not to break/remove exports, but there is no such commitment about +adding new ones. + +Now the only default export is C which gives you a handle where all the +tools we expose are provided as methods. We also provide the L namespace +for use with an identical number of keystrokes, which allow you to leverage the +prototypes on the original tool subroutines. + +=back + +=head1 SOURCE + +The source code repository for Test2-Suite can be found at +F. + +=head1 MAINTAINERS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 AUTHORS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 COPYRIGHT + +Copyright Chad Granum Eexodist@cpan.orgE. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +See F + +=cut diff --git a/lib/Test2/V1/Base.pm b/lib/Test2/V1/Base.pm new file mode 100644 index 000000000..8f1cf11b6 --- /dev/null +++ b/lib/Test2/V1/Base.pm @@ -0,0 +1,108 @@ +package Test2::V1::Base; +use strict; +use warnings; + +our $VERSION = '1.302217'; + +use Test2::API qw/intercept context/; + +use Test2::Tools::Event qw/gen_event/; + +use Test2::Tools::Defer qw/def do_def/; + +use Test2::Tools::Basic qw{ + ok pass fail diag note todo skip + plan skip_all done_testing bail_out +}; + +use Test2::Tools::Compare qw{ + is like isnt unlike + match mismatch validator + hash array bag object meta meta_check number float rounded within string subset bool check_isa + number_lt number_le number_ge number_gt + in_set not_in_set check_set + item field call call_list call_hash prop check all_items all_keys all_vals all_values + etc end filter_items + T F D DF E DNE FDNE U L + event fail_events + exact_ref +}; + +use Test2::Tools::Warnings qw{ + warns warning warnings no_warnings +}; + +use Test2::Tools::ClassicCompare qw/cmp_ok/; + +use Test2::Util::Importer 'Test2::Tools::Subtest' => ( + subtest_buffered => { -as => 'subtest' }, +); + +use Test2::Tools::Class qw/can_ok isa_ok DOES_ok/; +use Test2::Tools::Encoding qw/set_encoding/; +use Test2::Tools::Exports qw/imported_ok not_imported_ok/; +use Test2::Tools::Ref qw/ref_ok ref_is ref_is_not/; +use Test2::Tools::Mock qw/mock mocked/; +use Test2::Tools::Exception qw/try_ok dies lives/; +use Test2::Tools::Refcount qw/is_refcount is_oneref refcount/; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Test2::V1::Base - Base namespace used for L objects created via +L. + +=head1 DESCRIPTION + +This is the default set of functions/methods available in L. + +=head1 SYNOPSIS + +See L. This module is not typically used directly. + +=head1 INCLUDED FUNCTIONALITY + +See L for documentation about the tools included here, and +when they were added. + +Documentation is not duplicated here as that would mean maintaining 2 +locations for every change. + +=head1 SOURCE + +The source code repository for Test2-Suite can be found at +F. + +=head1 MAINTAINERS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 AUTHORS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 COPYRIGHT + +Copyright Chad Granum Eexodist@cpan.orgE. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +See F + +=cut diff --git a/lib/Test2/V1/Handle.pm b/lib/Test2/V1/Handle.pm new file mode 100644 index 000000000..2f0d32750 --- /dev/null +++ b/lib/Test2/V1/Handle.pm @@ -0,0 +1,74 @@ +package Test2::V1::Handle; +use strict; +use warnings; + +our $VERSION = '1.302217'; + +sub DEFAULT_HANDLE_BASE { 'Test2::V1::Base' } + +use parent 'Test2::Handle'; + +1; + +__END__ + +=pod + +=encoding UTF-8 + +=head1 NAME + +Test2::V1::Handle - V1 subclass of L. + +=head1 DESCRIPTION + +The L subclass of the L object. This is what you +interact with when you use the C function in a test. + +=head1 SYNOPSIS + + use Test2::V1::Handle; + + my $t2 = Test2::V1::Handle->new(); + + $t2->ok(1, "Passing test"); + +=head1 SUBCLASS OVERRIDES + +The default base class used is L. + +=head1 SEE ALSO + +See L for more information. + +=head1 SOURCE + +The source code repository for Test2-Suite can be found at +F. + +=head1 MAINTAINERS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 AUTHORS + +=over 4 + +=item Chad Granum Eexodist@cpan.orgE + +=back + +=head1 COPYRIGHT + +Copyright Chad Granum Eexodist@cpan.orgE. + +This program is free software; you can redistribute it and/or +modify it under the same terms as Perl itself. + +See F + +=cut diff --git a/t/00-report.t b/t/00-report.t index 014ed3040..5ad39c910 100644 --- a/t/00-report.t +++ b/t/00-report.t @@ -11,7 +11,7 @@ use Test2::Util::Table qw/table/; use Test2::Util qw/CAN_FORK CAN_REALLY_FORK CAN_THREAD/; my $exit = 0; -END{ $? = $exit } +END { $? = $exit } diag "\nDIAGNOSTICS INFO IN CASE OF FAILURE:\n"; diag(join "\n", table(rows => [[ 'perl', $] ]])); diff --git a/t/modules/Handle.t b/t/modules/Handle.t new file mode 100644 index 000000000..a4610d49c --- /dev/null +++ b/t/modules/Handle.t @@ -0,0 +1,118 @@ +use Test2::V1 -Ppi, -target => 'Test2::Handle'; + +isa_ok(T2, [$CLASS], "The T2 function returns a handle"); + +like(dies { $CLASS->new }, qr/Not Implemented/, "Handle base class does not have a handle base"); +like(dies { $CLASS->DEFAULT_HANDLE_BASE }, qr/Not Implemented/, "Handle base class does not have a handle base"); + +my $h = $CLASS->new(base => 'Test2::V1::Base'); +is($h->HANDLE_BASE, "Test2::V1::Base", "Can get base that was used to build object"); + +is( + [sort $h->HANDLE_SUBS], + [ + sort qw{ + intercept context + gen_event + + def do_def + + ok pass fail diag note todo skip + plan skip_all done_testing bail_out + + is like isnt unlike + match mismatch validator + hash array bag object meta meta_check number float rounded within string subset bool check_isa + number_lt number_le number_ge number_gt + in_set not_in_set check_set + item field call call_list call_hash prop check all_items all_keys all_vals all_values + etc end filter_items + T F D DF E DNE FDNE U L + event fail_events + exact_ref + + warns warning warnings no_warnings + + cmp_ok + + subtest + can_ok isa_ok DOES_ok + set_encoding + imported_ok not_imported_ok + ref_ok ref_is ref_is_not + mock mocked + try_ok dies lives + is_refcount is_oneref refcount + }, + ], + "Can find all the subs, excluded things like BEGIN, isa, can, etc.." +); + +$CLASS->import('my_t2', base => 'Test2::V1::Base'); +imported_ok('my_t2'); + +like($h->HANDLE_NAMESPACE, qr/^$CLASS\::GEN_\d+$/, "Got a generated namespace"); +isa_ok($h->HANDLE_NAMESPACE, ['Test2::V1::Base'], "Namespace uses the correct base class"); + +like( + dies { $CLASS->new(base => 'Test2::V1::BaseXX') }, + qr{Can't locate Test2/V1/BaseXX\.pm}, + "Need a valid base" +); + +like( + dies { $CLASS->new(namespace => $h->HANDLE_NAMESPACE, base => 'Test2::V1::Base') }, + qr/Namespace '$CLASS\::GEN_\d+' already appears to be populated/, + "Cannot override a defined namespace" +); + +ok(!$h->can('Dumper'), "handle 1 does not have Dumper"); +$h->HANDLE_INCLUDE('Data::Dumper', 'Dumper'); +can_ok($h, ['Dumper'], "handle 1 has Data::Dumper::Dumper"); +like($h->Dumper('xxx'), qr/\$VAR\d\s*=\s*'xxx';/, "Can use h->Dumper"); + +my $h2 = $CLASS->new(namespace => $h->HANDLE_NAMESPACE, base => 'Data::Dumper', stomp => 1); +isa_ok($h->HANDLE_NAMESPACE, ['Test2::V1::Base', 'Data::Dumper'], "Added Data::Dumper as a base"); +ok((grep { $_ eq 'Dumper' } $h->HANDLE_SUBS), "Got 'Dumper' in the subs"); + +my $line = __LINE__ + 1; +my $err = dies { $h->HANDLE_INCLUDE('FASDFASasfdfasfagasAFDSS', 'fasd') }; + +like( + $err, + qr|Test2/Handle\.pm line 52 \(called from ${ \__FILE__ } line $line\)\.|, + "Error reported to the ideal place" +); + +like( + dies { $line = __LINE__; $h->do_nothing }, + qr/"do_nothing" is not provided by this T2 handle at ${ \__FILE__ } line $line/, + "Useful error when we do not have method", +); + +my $h3 = $CLASS->new(base => 'Test2::V1::Base', include => ['Data::Dumper']); +can_ok($h3, ['Dumper'], "handle 1 has Data::Dumper::Dumper"); +like($h3->Dumper('xxx'), qr/\$VAR\d\s*=\s*'xxx';/, "Can use h3->Dumper"); + +my $h4 = $CLASS->new(base => 'Test2::V1::Base', include => {'Data::Dumper' => 'Dumper'}); +can_ok($h4, ['Dumper'], "handle 1 has Data::Dumper::Dumper"); +like($h4->Dumper('xxx'), qr/\$VAR\d\s*=\s*'xxx';/, "Can use h4->Dumper"); + +my $h5 = $CLASS->new(base => 'Test2::V1::Base', include => {'Data::Dumper' => ['Dumper']}); +can_ok($h5, ['Dumper'], "handle 1 has Data::Dumper::Dumper"); +like($h5->Dumper('xxx'), qr/\$VAR\d\s*=\s*'xxx';/, "Can use h5->Dumper"); + +like( + dies { $CLASS->new(base => 'Test2::V1::Base', include => \"hi") }, + qr/Not sure what to do with '/, + "Invalid include" +); + +my $ns = $h->HANDLE_NAMESPACE; +{ + no strict 'refs'; + *{"$ns\::FOO"} = sub { 'foo' }; +} +is($h->FOO, 'foo', "AUTOLOAD works"); + +done_testing; diff --git a/t/modules/T2.t b/t/modules/T2.t new file mode 100644 index 000000000..74994f8d5 --- /dev/null +++ b/t/modules/T2.t @@ -0,0 +1,19 @@ +use Test2::V1 -Ppi, T2 => {-as => 'MY_T2'}; + +{ + package BlahBlah; + use Test2::V1; + use T2; + + T2()->imported_ok('T2'); +} + +not_imported_ok('T2'); + +T2->ok(1, "Pass when calling on the 'T2' package"); +'T2'->ok(1, "Pass when calling on the 'T2' package string"); + +my @foo = (1, 2, 3); +T2::is(@foo, 3, "prototype works with :: calling form"); + +T2->done_testing; diff --git a/t/modules/V1.t b/t/modules/V1.t new file mode 100644 index 000000000..a4c79f7ff --- /dev/null +++ b/t/modules/V1.t @@ -0,0 +1,142 @@ +use Test2::V1 '-x', 'subtest'; +use Test2::API qw/test2_stack/; +use PerlIO; + +my @subs = qw{ + ok pass fail diag note todo skip + plan skip_all done_testing bail_out + + gen_event + + intercept context + + cmp_ok + + can_ok isa_ok DOES_ok + set_encoding + imported_ok not_imported_ok + ref_ok ref_is ref_is_not + mock mocked + + dies lives try_ok + + is like isnt unlike + match mismatch validator + hash array object meta number string bool check_isa + in_set not_in_set check_set + item field call call_list call_hash prop check all_items all_keys all_vals all_values + etc end filter_items + T F D DF E DNE FDNE U L + event fail_events + exact_ref + + is_refcount is_oneref refcount + + subtest +}; + +T2->not_imported_ok(qw/ok like done_testing/); +T2->imported_ok('subtest'); + +T2->ok(!T2->can('Dumper'), "Cannot 'Dumper'"); +T2->HANDLE_INCLUDE('Data::Dumper', 'Dumper'); +T2->can_ok(T2, ['Dumper'], "Added Dumper to the can list"); + +T2->can_ok(T2(), [@subs], "Handle can do it all"); +T2->isa_ok(T2(), ['Test2::Handle'], "Got a handle instance"); + +T2->ok(Test2::Plugin::ExitSummary->active, "Exit Summary is loaded"); + +T2->ok(!defined(Test2::Plugin::SRand->seed), "SRand is not loaded"); + +subtest srand => sub { + Test2::V1->import('-srand', '-no-T2'); + T2->ok(defined(Test2::Plugin::SRand->seed), "SRand is loaded"); +}; + +subtest strictures => sub { + local $^H; + my $hbefore = $^H; + Test2::V1->import('-strict', '-no-T2'); + my $hafter = $^H; + + my $strict = do { local $^H; strict->import(); $^H }; + + T2->ok($strict, 'sanity, got $^H value for strict'); + T2->ok(!($hbefore & $strict), "strict is not on before loading Test2::V0"); + T2->ok(($hafter & $strict), "strict is on after loading Test2::V0"); +}; + +subtest warnings => sub { + local ${^WARNING_BITS}; + my $wbefore = ${^WARNING_BITS} || ''; + Test2::V1->import('-warnings', '-no-T2'); + my $wafter = ${^WARNING_BITS} || ''; + + my $warnings = do { local ${^WARNING_BITS}; 'warnings'->import(); ${^WARNING_BITS} || '' }; + + T2->ok($warnings, 'sanity, got ${^WARNING_BITS} value for warnings'); + T2->ok($wbefore ne $warnings, "warnings are not on before loading Test2::V0") || diag($wbefore, "\n", $warnings); + T2->ok(($wafter & $warnings), "warnings are on after loading Test2::V0"); +}; + +subtest utf8 => sub { + T2->ok(!utf8::is_utf8("癸"), "utf8 pragma is off"); + + eval <<' EOT'; + package A::UTF8::Thingy; + use Test2::V1 '-utf8'; + T2->ok(utf8::is_utf8("癸"), "utf8 pragma is on"); + + # -2 cause the subtest adds to the stack + my $format = test2_stack()->[-2]->format; + my $handles = $format->handles or return; + for my $hn (0 .. @$handles) { + my $h = $handles->[$hn] || next; + my $layers = { map {$_ => 1} PerlIO::get_layers($h) }; + T2->ok($layers->{utf8}, "utf8 is on for formatter handle $hn"); + } + EOT +}; + +subtest "rename imports" => sub { + package A::Consumer; + use Test2::V1 '-import', '!subtest', subtest => {-as => 'a_subtest'}; + imported_ok('a_subtest'); + not_imported_ok('subtest'); +}; + +subtest "no meta" => sub { + package B::Consumer; + use Test2::V1 '-import', '!meta'; + imported_ok('meta_check'); + not_imported_ok('meta'); +}; + +subtest "-x" => sub { + package C::Consumer; + use Test2::V1 '-x'; + T2->imported_ok('dies'); +}; + +subtest "unquoted -x" => sub { + package D::Consumer; + main::T2()->ok(!eval "use Test2::V1 -x;"); + main::T2()->like( + $@, + qr/Got One or more undefined arguments, this usually means you passed in a single-character flag like '-p' without quoting it, which conflicts with the -p builtin/, + "Caught easy mistake" + ); +}; + +subtest target => sub { + package E::Consumer; + use Test2::V1 '-i', -target => 'Data::Dumper'; + is($CLASS, 'Data::Dumper', "Added \$CLASS symbol"); + is(CLASS(), 'Data::Dumper', "Added \&CLASS symbol"); +}; + + +T2->done_testing; + +1; diff --git a/t/modules/V1/Base.t b/t/modules/V1/Base.t new file mode 100644 index 000000000..7afe8d1f9 --- /dev/null +++ b/t/modules/V1/Base.t @@ -0,0 +1,43 @@ +use Test2::V1 -Ppi, -target => 'Test2::V1::Base'; + +can_ok( + $CLASS, + [ + qw{ + intercept context + gen_event + + def do_def + + ok pass fail diag note todo skip + plan skip_all done_testing bail_out + + is like isnt unlike + match mismatch validator + hash array bag object meta meta_check number float rounded within string subset bool check_isa + number_lt number_le number_ge number_gt + in_set not_in_set check_set + item field call call_list call_hash prop check all_items all_keys all_vals all_values + etc end filter_items + T F D DF E DNE FDNE U L + event fail_events + exact_ref + + warns warning warnings no_warnings + + cmp_ok + + subtest + can_ok isa_ok DOES_ok + set_encoding + imported_ok not_imported_ok + ref_ok ref_is ref_is_not + mock mocked + try_ok dies lives + is_refcount is_oneref refcount + }, + ], + "Imported all symbols" +); + +done_testing; diff --git a/t/modules/V1/Handle.t b/t/modules/V1/Handle.t new file mode 100644 index 000000000..73b4504a0 --- /dev/null +++ b/t/modules/V1/Handle.t @@ -0,0 +1,7 @@ +use Test2::V1 -Ppi, -target => 'Test2::V1::Handle'; + +isa_ok($CLASS, ['Test2::Handle'], "subclassed properly"); + +is($CLASS->DEFAULT_HANDLE_BASE, 'Test2::V1::Base', "Got correct handle base"); + +done_testing;