📄 builder.pm
字号:
package Test::Builder;use 5.004;# $^C was only introduced in 5.005-ish. We do this to prevent# use of uninitialized value warnings in older perls.$^C ||= 0;use strict;use vars qw($VERSION);$VERSION = '0.74';$VERSION = eval $VERSION; # make the alpha version come out as a number# Make Test::Builder thread-safe for ithreads.BEGIN { use Config; # Load threads::shared when threads are turned on. # 5.8.0's threads are so busted we no longer support them. if( $] >= 5.008001 && $Config{useithreads} && $INC{'threads.pm'}) { require threads::shared; # Hack around YET ANOTHER threads::shared bug. It would # occassionally forget the contents of the variable when sharing it. # So we first copy the data, then share, then put our copy back. *share = sub (\[$@%]) { my $type = ref $_[0]; my $data; if( $type eq 'HASH' ) { %$data = %{$_[0]}; } elsif( $type eq 'ARRAY' ) { @$data = @{$_[0]}; } elsif( $type eq 'SCALAR' ) { $$data = ${$_[0]}; } else { die("Unknown type: ".$type); } $_[0] = &threads::shared::share($_[0]); if( $type eq 'HASH' ) { %{$_[0]} = %$data; } elsif( $type eq 'ARRAY' ) { @{$_[0]} = @$data; } elsif( $type eq 'SCALAR' ) { ${$_[0]} = $$data; } else { die("Unknown type: ".$type); } return $_[0]; }; } # 5.8.0's threads::shared is busted when threads are off # and earlier Perls just don't have that module at all. else { *share = sub { return $_[0] }; *lock = sub { 0 }; }}=head1 NAMETest::Builder - Backend for building test libraries=head1 SYNOPSIS package My::Test::Module; use Test::Builder; require Exporter; @ISA = qw(Exporter); @EXPORT = qw(ok); my $Test = Test::Builder->new; $Test->output('my_logfile'); sub import { my($self) = shift; my $pack = caller; $Test->exported_to($pack); $Test->plan(@_); $self->export_to_level(1, $self, 'ok'); } sub ok { my($test, $name) = @_; $Test->ok($test, $name); }=head1 DESCRIPTIONTest::Simple and Test::More have proven to be popular testing modules,but they're not always flexible enough. Test::Builder provides the abuilding block upon which to write your own test libraries I<which canwork together>.=head2 Construction=over 4=item B<new> my $Test = Test::Builder->new;Returns a Test::Builder object representing the current state of thetest.Since you only run one test per program C<new> always returns the sameTest::Builder object. No matter how many times you call new(), you'regetting the same object. This is called a singleton. This is done so thatmultiple modules share such global information as the test counter andwhere test output is going.If you want a completely new Test::Builder object different from thesingleton, use C<create>.=cutmy $Test = Test::Builder->new;sub new { my($class) = shift; $Test ||= $class->create; return $Test;}=item B<create> my $Test = Test::Builder->create;Ok, so there can be more than one Test::Builder object and this is howyou get it. You might use this instead of C<new()> if you're testinga Test::Builder based module, but otherwise you probably want C<new>.B<NOTE>: the implementation is not complete. C<level>, for example, isstill shared amongst B<all> Test::Builder objects, even ones created usingthis method. Also, the method name may change in the future.=cutsub create { my $class = shift; my $self = bless {}, $class; $self->reset; return $self;}=item B<reset> $Test->reset;Reinitializes the Test::Builder singleton to its original state.Mostly useful for tests run in persistent environments where the sametest might be run multiple times in the same process.=cutuse vars qw($Level);sub reset { my ($self) = @_; # We leave this a global because it has to be localized and localizing # hash keys is just asking for pain. Also, it was documented. $Level = 1; $self->{Test_Died} = 0; $self->{Have_Plan} = 0; $self->{No_Plan} = 0; $self->{Original_Pid} = $$; share($self->{Curr_Test}); $self->{Curr_Test} = 0; $self->{Test_Results} = &share([]); $self->{Exported_To} = undef; $self->{Expected_Tests} = 0; $self->{Skip_All} = 0; $self->{Use_Nums} = 1; $self->{No_Header} = 0; $self->{No_Ending} = 0; $self->_dup_stdhandles unless $^C; return undef;}=back=head2 Setting up testsThese methods are for setting up tests and declaring how many thereare. You usually only want to call one of these methods.=over 4=item B<exported_to> my $pack = $Test->exported_to; $Test->exported_to($pack);Tells Test::Builder what package you exported your functions to.This is important for getting TODO tests right.=cutsub exported_to { my($self, $pack) = @_; if( defined $pack ) { $self->{Exported_To} = $pack; } return $self->{Exported_To};}=item B<plan> $Test->plan('no_plan'); $Test->plan( skip_all => $reason ); $Test->plan( tests => $num_tests );A convenient way to set up your tests. Call this and Test::Builderwill print the appropriate headers and take the appropriate actions.If you call plan(), don't call any of the other methods below.=cutsub plan { my($self, $cmd, $arg) = @_; return unless $cmd; local $Level = $Level + 1; if( $self->{Have_Plan} ) { $self->croak("You tried to plan twice"); } if( $cmd eq 'no_plan' ) { $self->no_plan; } elsif( $cmd eq 'skip_all' ) { return $self->skip_all($arg); } elsif( $cmd eq 'tests' ) { if( $arg ) { local $Level = $Level + 1; return $self->expected_tests($arg); } elsif( !defined $arg ) { $self->croak("Got an undefined number of tests"); } elsif( !$arg ) { $self->croak("You said to run 0 tests"); } } else { my @args = grep { defined } ($cmd, $arg); $self->croak("plan() doesn't understand @args"); } return 1;}=item B<expected_tests> my $max = $Test->expected_tests; $Test->expected_tests($max);Gets/sets the # of tests we expect this test to run and prints outthe appropriate headers.=cutsub expected_tests { my $self = shift; my($max) = @_; if( @_ ) { $self->croak("Number of tests must be a positive integer. You gave it '$max'") unless $max =~ /^\+?\d+$/ and $max > 0; $self->{Expected_Tests} = $max; $self->{Have_Plan} = 1; $self->_print("1..$max\n") unless $self->no_header; } return $self->{Expected_Tests};}=item B<no_plan> $Test->no_plan;Declares that this test will run an indeterminate # of tests.=cutsub no_plan { my $self = shift; $self->{No_Plan} = 1; $self->{Have_Plan} = 1;}=item B<has_plan> $plan = $Test->has_planFind out whether a plan has been defined. $plan is either C<undef> (no plan has been set), C<no_plan> (indeterminate # of tests) or an integer (the number of expected tests).=cutsub has_plan { my $self = shift; return($self->{Expected_Tests}) if $self->{Expected_Tests}; return('no_plan') if $self->{No_Plan}; return(undef);};=item B<skip_all> $Test->skip_all; $Test->skip_all($reason);Skips all the tests, using the given $reason. Exits immediately with 0.=cutsub skip_all { my($self, $reason) = @_; my $out = "1..0"; $out .= " # Skip $reason" if $reason; $out .= "\n"; $self->{Skip_All} = 1; $self->_print($out) unless $self->no_header; exit(0);}=back=head2 Running testsThese actually run the tests, analogous to the functions in Test::More.They all return true if the test passed, false if the test failed.$name is always optional.=over 4=item B<ok> $Test->ok($test, $name);Your basic test. Pass if $test is true, fail if $test is false. Justlike Test::Simple's ok().=cutsub ok { my($self, $test, $name) = @_; # $test might contain an object which we don't want to accidentally # store, so we turn it into a boolean. $test = $test ? 1 : 0; $self->_plan_check; lock $self->{Curr_Test}; $self->{Curr_Test}++; # In case $name is a string overloaded object, force it to stringify. $self->_unoverload_str(\$name); $self->diag(<<ERR) if defined $name and $name =~ /^[\d\s]+$/; You named your test '$name'. You shouldn't use numbers for your test names. Very confusing.ERR my($pack, $file, $line) = $self->caller; my $todo = $self->todo($pack); $self->_unoverload_str(\$todo); my $out; my $result = &share({}); unless( $test ) { $out .= "not "; @$result{ 'ok', 'actual_ok' } = ( ( $todo ? 1 : 0 ), 0 ); } else { @$result{ 'ok', 'actual_ok' } = ( 1, $test ); } $out .= "ok"; $out .= " $self->{Curr_Test}" if $self->use_numbers; if( defined $name ) { $name =~ s|#|\\#|g; # # in a name can confuse Test::Harness. $out .= " - $name"; $result->{name} = $name; } else { $result->{name} = ''; } if( $todo ) { $out .= " # TODO $todo"; $result->{reason} = $todo; $result->{type} = 'todo'; } else { $result->{reason} = ''; $result->{type} = ''; } $self->{Test_Results}[$self->{Curr_Test}-1] = $result; $out .= "\n"; $self->_print($out); unless( $test ) { my $msg = $todo ? "Failed (TODO)" : "Failed"; $self->_print_diag("\n") if $ENV{HARNESS_ACTIVE}; if( defined $name ) { $self->diag(qq[ $msg test '$name'\n]); $self->diag(qq[ at $file line $line.\n]); } else { $self->diag(qq[ $msg test at $file line $line.\n]); } } return $test ? 1 : 0;}sub _unoverload { my $self = shift; my $type = shift; $self->_try(sub { require overload } ) || return; foreach my $thing (@_) { if( $self->_is_object($$thing) ) { if( my $string_meth = overload::Method($$thing, $type) ) { $$thing = $$thing->$string_meth(); } } }}sub _is_object { my($self, $thing) = @_; return $self->_try(sub { ref $thing && $thing->isa('UNIVERSAL') }) ? 1 : 0;}sub _unoverload_str { my $self = shift; $self->_unoverload(q[""], @_);} sub _unoverload_num { my $self = shift; $self->_unoverload('0+', @_); for my $val (@_) { next unless $self->_is_dualvar($$val); $$val = $$val+0; }}# This is a hack to detect a dualvar such as $!sub _is_dualvar { my($self, $val) = @_; local $^W = 0; my $numval = $val+0; return 1 if $numval != 0 and $numval ne $val;}=item B<is_eq> $Test->is_eq($got, $expected, $name);Like Test::More's is(). Checks if $got eq $expected. This is thestring version.=item B<is_num> $Test->is_num($got, $expected, $name);Like Test::More's is(). Checks if $got == $expected. This is thenumeric version.=cutsub is_eq { my($self, $got, $expect, $name) = @_; local $Level = $Level + 1; $self->_unoverload_str(\$got, \$expect); if( !defined $got || !defined $expect ) { # undef only matches undef and nothing else my $test = !defined $got && !defined $expect; $self->ok($test, $name); $self->_is_diag($got, 'eq', $expect) unless $test; return $test; } return $self->cmp_ok($got, 'eq', $expect, $name);}sub is_num { my($self, $got, $expect, $name) = @_; local $Level = $Level + 1; $self->_unoverload_num(\$got, \$expect); if( !defined $got || !defined $expect ) { # undef only matches undef and nothing else my $test = !defined $got && !defined $expect; $self->ok($test, $name); $self->_is_diag($got, '==', $expect) unless $test; return $test; } return $self->cmp_ok($got, '==', $expect, $name);}sub _is_diag { my($self, $got, $type, $expect) = @_; foreach my $val (\$got, \$expect) { if( defined $$val ) { if( $type eq 'eq' ) { # quote and force string context $$val = "'$$val'" } else { # force numeric context $self->_unoverload_num($val); } } else { $$val = 'undef'; } } return $self->diag(sprintf <<DIAGNOSTIC, $got, $expect); got: %s expected: %sDIAGNOSTIC} =item B<isnt_eq> $Test->isnt_eq($got, $dont_expect, $name);Like Test::More's isnt(). Checks if $got ne $dont_expect. This isthe string version.=item B<isnt_num> $Test->isnt_num($got, $dont_expect, $name);Like Test::More's isnt(). Checks if $got ne $dont_expect. This isthe numeric version.=cutsub isnt_eq { my($self, $got, $dont_expect, $name) = @_; local $Level = $Level + 1; if( !defined $got || !defined $dont_expect ) { # undef only matches undef and nothing else my $test = defined $got || defined $dont_expect;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -