📄 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 $CLASS);$VERSION = '0.17';$CLASS = __PACKAGE__;my $IsVMS = $^O eq 'VMS';# Make Test::Builder thread-safe for ithreads.BEGIN { use Config; if( $] >= 5.008 && $Config{useithreads} ) { require threads; require threads::shared; threads::shared->import; } else { *share = sub { 0 }; *lock = sub { 0 }; }}use vars qw($Level);my($Test_Died) = 0;my($Have_Plan) = 0;my $Original_Pid = $$;my $Curr_Test = 0; share($Curr_Test);my @Test_Results = (); share(@Test_Results);my @Test_Details = (); share(@Test_Details);=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, there is B<one and only one>Test::Builder object. No matter how many times you call new(), you'regetting the same object. (This is called a singleton).=cutmy $Test;sub new { my($class) = shift; $Test ||= bless ['Move along, nothing to see here'], $class; return $Test;}=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.=cutmy $Exported_To;sub exported_to { my($self, $pack) = @_; if( defined $pack ) { $Exported_To = $pack; } return $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; if( $Have_Plan ) { die sprintf "You tried to plan twice! Second plan at %s line %d\n", ($self->caller)[1,2]; } if( $cmd eq 'no_plan' ) { $self->no_plan; } elsif( $cmd eq 'skip_all' ) { return $self->skip_all($arg); } elsif( $cmd eq 'tests' ) { if( $arg ) { return $self->expected_tests($arg); } elsif( !defined $arg ) { die "Got an undefined number of tests. Looks like you tried to ". "say how many tests you plan to run but made a mistake.\n"; } elsif( !$arg ) { die "You said to run 0 tests! You've got to run something.\n"; } } else { require Carp; my @args = grep { defined } ($cmd, $arg); Carp::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.=cutmy $Expected_Tests = 0;sub expected_tests { my($self, $max) = @_; if( defined $max ) { $Expected_Tests = $max; $Have_Plan = 1; $self->_print("1..$max\n") unless $self->no_header; } return $Expected_Tests;}=item B<no_plan> $Test->no_plan;Declares that this test will run an indeterminate # of tests.=cutmy($No_Plan) = 0;sub no_plan { $No_Plan = 1; $Have_Plan = 1;}=item B<has_plan> $plan = $Test->has_plan Find 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 { return($Expected_Tests) if $Expected_Tests; return('no_plan') if $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.=cutmy $Skip_All = 0;sub skip_all { my($self, $reason) = @_; my $out = "1..0"; $out .= " # Skip $reason" if $reason; $out .= "\n"; $Skip_All = 1; $self->_print($out) unless $self->no_header; exit(0);}=back=head2 Running testsThese actually run the tests, analogous to the functions inTest::More.$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; unless( $Have_Plan ) { require Carp; Carp::croak("You tried to run a test without a plan! Gotta have a plan."); } lock $Curr_Test; $Curr_Test++; $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); my $out; my $result = {}; share($result); unless( $test ) { $out .= "not "; @$result{ 'ok', 'actual_ok' } = ( ( $todo ? 1 : 0 ), 0 ); } else { @$result{ 'ok', 'actual_ok' } = ( 1, $test ); } $out .= "ok"; $out .= " $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 ) { my $what_todo = $todo; $out .= " # TODO $what_todo"; $result->{reason} = $what_todo; $result->{type} = 'todo'; } else { $result->{reason} = ''; $result->{type} = ''; } $Test_Results[$Curr_Test-1] = $result; $out .= "\n"; $self->_print($out); unless( $test ) { my $msg = $todo ? "Failed (TODO)" : "Failed"; $self->diag(" $msg test ($file at line $line)\n"); } return $test ? 1 : 0;}=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; 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; 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 $$val = $$val+0; } } 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->is_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; $self->ok($test, $name); $self->_cmp_diag('ne', $got, $dont_expect) unless $test; return $test; } return $self->cmp_ok($got, 'ne', $dont_expect, $name);}sub isnt_num { 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; $self->ok($test, $name); $self->_cmp_diag('!=', $got, $dont_expect) unless $test; return $test; } return $self->cmp_ok($got, '!=', $dont_expect, $name);}=item B<like> $Test->like($this, qr/$regex/, $name); $Test->like($this, '/$regex/', $name);Like Test::More's like(). Checks if $this matches the given $regex.You'll want to avoid qr// if you want your tests to work before 5.005.=item B<unlike> $Test->unlike($this, qr/$regex/, $name); $Test->unlike($this, '/$regex/', $name);Like Test::More's unlike(). Checks if $this B<does not match> thegiven $regex.=cutsub like { my($self, $this, $regex, $name) = @_; local $Level = $Level + 1; $self->_regex_ok($this, $regex, '=~', $name);}sub unlike { my($self, $this, $regex, $name) = @_; local $Level = $Level + 1; $self->_regex_ok($this, $regex, '!~', $name);}=item B<maybe_regex> $Test->maybe_regex(qr/$regex/); $Test->maybe_regex('/$regex/');Convenience method for building testing functions that take regularexpressions as arguments, but need to work before perl 5.005.Takes a quoted regular expression produced by qr//, or a stringrepresenting a regular expression.Returns a Perl value which may be used instead of the correspondingregular expression, or undef if it's argument is not recognised.For example, a version of like(), sans the useful diagnostic messages,could be written as: sub laconic_like { my ($self, $this, $regex, $name) = @_; my $usable_regex = $self->maybe_regex($regex); die "expecting regex, found '$regex'\n" unless $usable_regex; $self->ok($this =~ m/$usable_regex/, $name); }=cutsub maybe_regex { my ($self, $regex) = @_; my $usable_regex = undef; if( ref $regex eq 'Regexp' ) { $usable_regex = $regex; } # Check if it looks like '/foo/' elsif( my($re, $opts) = $regex =~ m{^ /(.*)/ (\w*) $ }sx ) { $usable_regex = length $opts ? "(?$opts)$re" : $re; }; return($usable_regex)};sub _regex_ok { my($self, $this, $regex, $cmp, $name) = @_; local $Level = $Level + 1; my $ok = 0; my $usable_regex = $self->maybe_regex($regex); unless (defined $usable_regex) { $ok = $self->ok( 0, $name ); $self->diag(" '$regex' doesn't look much like a regex to me."); return $ok; } { local $^W = 0; my $test = $this =~ /$usable_regex/ ? 1 : 0; $test = !$test if $cmp eq '!~'; $ok = $self->ok( $test, $name ); } unless( $ok ) { $this = defined $this ? "'$this'" : 'undef'; my $match = $cmp eq '=~' ? "doesn't match" : "matches"; $self->diag(sprintf <<DIAGNOSTIC, $this, $match, $regex); %s %13s '%s'DIAGNOSTIC } return $ok;}=item B<cmp_ok> $Test->cmp_ok($this, $type, $that, $name);Works just like Test::More's cmp_ok(). $Test->cmp_ok($big_num, '!=', $other_big_num);=cutsub cmp_ok { my($self, $got, $type, $expect, $name) = @_; my $test; { local $^W = 0; local($@,$!); # don't interfere with $@ # eval() sometimes resets $! $test = eval "\$got $type \$expect"; } local $Level = $Level + 1; my $ok = $self->ok($test, $name); unless( $ok ) { if( $type =~ /^(eq|==)$/ ) { $self->_is_diag($got, $type, $expect); } else { $self->_cmp_diag($got, $type, $expect); } } return $ok;}sub _cmp_diag { my($self, $got, $type, $expect) = @_; $got = defined $got ? "'$got'" : 'undef'; $expect = defined $expect ? "'$expect'" : 'undef'; return $self->diag(sprintf <<DIAGNOSTIC, $got, $type, $expect); %s %s %sDIAGNOSTIC}=item B<BAILOUT> $Test->BAILOUT($reason);Indicates to the Test::Harness that things are going so badly alltesting should terminate. This includes running any additional testscripts.It will exit with 255.=cutsub BAILOUT { my($self, $reason) = @_; $self->_print("Bail out! $reason"); exit 255;}=item B<skip> $Test->skip; $Test->skip($why);Skips the current test, reporting $why.=cutsub skip { my($self, $why) = @_; $why ||= ''; unless( $Have_Plan ) { require Carp; Carp::croak("You tried to run tests without a plan! Gotta have a plan."); } lock($Curr_Test); $Curr_Test++; my %result; share(%result); %result = ( 'ok' => 1, actual_ok => 1, name => '', type => 'skip', reason => $why, ); $Test_Results[$Curr_Test-1] = \%result; my $out = "ok"; $out .= " $Curr_Test" if $self->use_numbers; $out .= " # skip $why\n"; $Test->_print($out); return 1;}=item B<todo_skip> $Test->todo_skip; $Test->todo_skip($why);Like skip(), only it will declare the test as failing and TODO. Similarto print "not ok $tnum # TODO $why\n";=cutsub todo_skip { my($self, $why) = @_; $why ||= ''; unless( $Have_Plan ) { require Carp; Carp::croak("You tried to run tests without a plan! Gotta have a plan."); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -