1# 2# t/test.pl - most of Test::More functionality without the fuss 3 4 5# NOTE: 6# 7# Increment ($x++) has a certain amount of cleverness for things like 8# 9# $x = 'zz'; 10# $x++; # $x eq 'aaa'; 11# 12# stands more chance of breaking than just a simple 13# 14# $x = $x + 1 15# 16# In this file, we use the latter "Baby Perl" approach, and increment 17# will be worked over by t/op/inc.t 18 19$Level = 1; 20my $test = 1; 21my $planned; 22my $noplan; 23 24$TODO = 0; 25$NO_ENDING = 0; 26 27sub plan { 28 my $n; 29 if (@_ == 1) { 30 $n = shift; 31 if ($n eq 'no_plan') { 32 undef $n; 33 $noplan = 1; 34 } 35 } else { 36 my %plan = @_; 37 $n = $plan{tests}; 38 } 39 print STDOUT "1..$n\n" unless $noplan; 40 $planned = $n; 41} 42 43END { 44 my $ran = $test - 1; 45 if (!$NO_ENDING) { 46 if (defined $planned && $planned != $ran) { 47 print STDERR 48 "# Looks like you planned $planned tests but ran $ran.\n"; 49 } elsif ($noplan) { 50 print "1..$ran\n"; 51 } 52 } 53} 54 55# Use this instead of "print STDERR" when outputing failure diagnostic 56# messages 57sub _diag { 58 return unless @_; 59 my @mess = map { /^#/ ? "$_\n" : "# $_\n" } 60 map { split /\n/ } @_; 61 my $fh = $TODO ? *STDOUT : *STDERR; 62 print $fh @mess; 63 64} 65 66sub diag { 67 _diag(@_); 68} 69 70sub skip_all { 71 if (@_) { 72 print STDOUT "1..0 # Skipped: @_\n"; 73 } else { 74 print STDOUT "1..0\n"; 75 } 76 exit(0); 77} 78 79sub _ok { 80 my ($pass, $where, $name, @mess) = @_; 81 # Do not try to microoptimize by factoring out the "not ". 82 # VMS will avenge. 83 my $out; 84 if ($name) { 85 # escape out '#' or it will interfere with '# skip' and such 86 $name =~ s/#/\\#/g; 87 $out = $pass ? "ok $test - $name" : "not ok $test - $name"; 88 } else { 89 $out = $pass ? "ok $test" : "not ok $test"; 90 } 91 92 $out .= " # TODO $TODO" if $TODO; 93 print STDOUT "$out\n"; 94 95 unless ($pass) { 96 _diag "# Failed $where\n"; 97 } 98 99 # Ensure that the message is properly escaped. 100 _diag @mess; 101 102 $test = $test + 1; # don't use ++ 103 104 return $pass; 105} 106 107sub _where { 108 my @caller = caller($Level); 109 return "at $caller[1] line $caller[2]"; 110} 111 112# DON'T use this for matches. Use like() instead. 113sub ok ($@) { 114 my ($pass, $name, @mess) = @_; 115 _ok($pass, _where(), $name, @mess); 116} 117 118sub _q { 119 my $x = shift; 120 return 'undef' unless defined $x; 121 my $q = $x; 122 $q =~ s/\\/\\\\/g; 123 $q =~ s/'/\\'/g; 124 return "'$q'"; 125} 126 127sub _qq { 128 my $x = shift; 129 return defined $x ? '"' . display ($x) . '"' : 'undef'; 130}; 131 132# keys are the codes \n etc map to, values are 2 char strings such as \n 133my %backslash_escape; 134foreach my $x (split //, 'nrtfa\\\'"') { 135 $backslash_escape{ord eval "\"\\$x\""} = "\\$x"; 136} 137# A way to display scalars containing control characters and Unicode. 138# Trying to avoid setting $_, or relying on local $_ to work. 139sub display { 140 my @result; 141 foreach my $x (@_) { 142 if (defined $x and not ref $x) { 143 my $y = ''; 144 foreach my $c (unpack("U*", $x)) { 145 if ($c > 255) { 146 $y .= sprintf "\\x{%x}", $c; 147 } elsif ($backslash_escape{$c}) { 148 $y .= $backslash_escape{$c}; 149 } else { 150 my $z = chr $c; # Maybe we can get away with a literal... 151 $z = sprintf "\\%03o", $c if $z =~ /[[:^print:]]/; 152 $y .= $z; 153 } 154 } 155 $x = $y; 156 } 157 return $x unless wantarray; 158 push @result, $x; 159 } 160 return @result; 161} 162 163sub is ($$@) { 164 my ($got, $expected, $name, @mess) = @_; 165 166 my $pass; 167 if( !defined $got || !defined $expected ) { 168 # undef only matches undef 169 $pass = !defined $got && !defined $expected; 170 } 171 else { 172 $pass = $got eq $expected; 173 } 174 175 unless ($pass) { 176 unshift(@mess, "# got "._q($got)."\n", 177 "# expected "._q($expected)."\n"); 178 } 179 _ok($pass, _where(), $name, @mess); 180} 181 182sub isnt ($$@) { 183 my ($got, $isnt, $name, @mess) = @_; 184 185 my $pass; 186 if( !defined $got || !defined $isnt ) { 187 # undef only matches undef 188 $pass = defined $got || defined $isnt; 189 } 190 else { 191 $pass = $got ne $isnt; 192 } 193 194 unless( $pass ) { 195 unshift(@mess, "# it should not be "._q($got)."\n", 196 "# but it is.\n"); 197 } 198 _ok($pass, _where(), $name, @mess); 199} 200 201sub cmp_ok ($$$@) { 202 my($got, $type, $expected, $name, @mess) = @_; 203 204 my $pass; 205 { 206 local $^W = 0; 207 local($@,$!); # don't interfere with $@ 208 # eval() sometimes resets $! 209 $pass = eval "\$got $type \$expected"; 210 } 211 unless ($pass) { 212 # It seems Irix long doubles can have 2147483648 and 2147483648 213 # that stringify to the same thing but are acutally numerically 214 # different. Display the numbers if $type isn't a string operator, 215 # and the numbers are stringwise the same. 216 # (all string operators have alphabetic names, so tr/a-z// is true) 217 # This will also show numbers for some uneeded cases, but will 218 # definately be helpful for things such as == and <= that fail 219 if ($got eq $expected and $type !~ tr/a-z//) { 220 unshift @mess, "# $got - $expected = " . ($got - $expected) . "\n"; 221 } 222 unshift(@mess, "# got "._q($got)."\n", 223 "# expected $type "._q($expected)."\n"); 224 } 225 _ok($pass, _where(), $name, @mess); 226} 227 228# Check that $got is within $range of $expected 229# if $range is 0, then check it's exact 230# else if $expected is 0, then $range is an absolute value 231# otherwise $range is a fractional error. 232# Here $range must be numeric, >= 0 233# Non numeric ranges might be a useful future extension. (eg %) 234sub within ($$$@) { 235 my ($got, $expected, $range, $name, @mess) = @_; 236 my $pass; 237 if (!defined $got or !defined $expected or !defined $range) { 238 # This is a fail, but doesn't need extra diagnostics 239 } elsif ($got !~ tr/0-9// or $expected !~ tr/0-9// or $range !~ tr/0-9//) { 240 # This is a fail 241 unshift @mess, "# got, expected and range must be numeric\n"; 242 } elsif ($range < 0) { 243 # This is also a fail 244 unshift @mess, "# range must not be negative\n"; 245 } elsif ($range == 0) { 246 # Within 0 is == 247 $pass = $got == $expected; 248 } elsif ($expected == 0) { 249 # If expected is 0, treat range as absolute 250 $pass = ($got <= $range) && ($got >= - $range); 251 } else { 252 my $diff = $got - $expected; 253 $pass = abs ($diff / $expected) < $range; 254 } 255 unless ($pass) { 256 if ($got eq $expected) { 257 unshift @mess, "# $got - $expected = " . ($got - $expected) . "\n"; 258 } 259 unshift@mess, "# got "._q($got)."\n", 260 "# expected "._q($expected)." (within "._q($range).")\n"; 261 } 262 _ok($pass, _where(), $name, @mess); 263} 264 265# Note: this isn't quite as fancy as Test::More::like(). 266 267sub like ($$@) { like_yn (0,@_) }; # 0 for - 268sub unlike ($$@) { like_yn (1,@_) }; # 1 for un- 269 270sub like_yn ($$$@) { 271 my ($flip, $got, $expected, $name, @mess) = @_; 272 my $pass; 273 $pass = $got =~ /$expected/ if !$flip; 274 $pass = $got !~ /$expected/ if $flip; 275 unless ($pass) { 276 unshift(@mess, "# got '$got'\n", 277 $flip 278 ? "# expected !~ /$expected/\n" : "# expected /$expected/\n"); 279 } 280 local $Level = $Level + 1; 281 _ok($pass, _where(), $name, @mess); 282} 283 284sub pass { 285 _ok(1, '', @_); 286} 287 288sub fail { 289 _ok(0, _where(), @_); 290} 291 292sub curr_test { 293 $test = shift if @_; 294 return $test; 295} 296 297sub next_test { 298 my $retval = $test; 299 $test = $test + 1; # don't use ++ 300 $retval; 301} 302 303# Note: can't pass multipart messages since we try to 304# be compatible with Test::More::skip(). 305sub skip { 306 my $why = shift; 307 my $n = @_ ? shift : 1; 308 for (1..$n) { 309 print STDOUT "ok $test # skip: $why\n"; 310 $test = $test + 1; 311 } 312 local $^W = 0; 313 last SKIP; 314} 315 316sub todo_skip { 317 my $why = shift; 318 my $n = @_ ? shift : 1; 319 320 for (1..$n) { 321 print STDOUT "not ok $test # TODO & SKIP: $why\n"; 322 $test = $test + 1; 323 } 324 local $^W = 0; 325 last TODO; 326} 327 328sub eq_array { 329 my ($ra, $rb) = @_; 330 return 0 unless $#$ra == $#$rb; 331 for my $i (0..$#$ra) { 332 next if !defined $ra->[$i] && !defined $rb->[$i]; 333 return 0 if !defined $ra->[$i]; 334 return 0 if !defined $rb->[$i]; 335 return 0 unless $ra->[$i] eq $rb->[$i]; 336 } 337 return 1; 338} 339 340sub eq_hash { 341 my ($orig, $suspect) = @_; 342 my $fail; 343 while (my ($key, $value) = each %$suspect) { 344 # Force a hash recompute if this perl's internals can cache the hash key. 345 $key = "" . $key; 346 if (exists $orig->{$key}) { 347 if ($orig->{$key} ne $value) { 348 print STDOUT "# key ", _qq($key), " was ", _qq($orig->{$key}), 349 " now ", _qq($value), "\n"; 350 $fail = 1; 351 } 352 } else { 353 print STDOUT "# key ", _qq($key), " is ", _qq($value), 354 ", not in original.\n"; 355 $fail = 1; 356 } 357 } 358 foreach (keys %$orig) { 359 # Force a hash recompute if this perl's internals can cache the hash key. 360 $_ = "" . $_; 361 next if (exists $suspect->{$_}); 362 print STDOUT "# key ", _qq($_), " was ", _qq($orig->{$_}), " now missing.\n"; 363 $fail = 1; 364 } 365 !$fail; 366} 367 368sub require_ok ($) { 369 my ($require) = @_; 370 eval <<REQUIRE_OK; 371require $require; 372REQUIRE_OK 373 _ok(!$@, _where(), "require $require"); 374} 375 376sub use_ok ($) { 377 my ($use) = @_; 378 eval <<USE_OK; 379use $use; 380USE_OK 381 _ok(!$@, _where(), "use $use"); 382} 383 384# runperl - Runs a separate perl interpreter. 385# Arguments : 386# switches => [ command-line switches ] 387# nolib => 1 # don't use -I../lib (included by default) 388# prog => one-liner (avoid quotes) 389# progs => [ multi-liner (avoid quotes) ] 390# progfile => perl script 391# stdin => string to feed the stdin 392# stderr => redirect stderr to stdout 393# args => [ command-line arguments to the perl program ] 394# verbose => print the command line 395 396my $is_mswin = $^O eq 'MSWin32'; 397my $is_netware = $^O eq 'NetWare'; 398my $is_macos = $^O eq 'MacOS'; 399my $is_vms = $^O eq 'VMS'; 400my $is_cygwin = $^O eq 'cygwin'; 401 402sub _quote_args { 403 my ($runperl, $args) = @_; 404 405 foreach (@$args) { 406 # In VMS protect with doublequotes because otherwise 407 # DCL will lowercase -- unless already doublequoted. 408 $_ = q(").$_.q(") if $is_vms && !/^\"/ && length($_) > 0; 409 $$runperl .= ' ' . $_; 410 } 411} 412 413sub _create_runperl { # Create the string to qx in runperl(). 414 my %args = @_; 415 my $runperl = $^X =~ m/\s/ ? qq{"$^X"} : $^X; 416 #- this allows, for example, to set PERL_RUNPERL_DEBUG=/usr/bin/valgrind 417 if ($ENV{PERL_RUNPERL_DEBUG}) { 418 $runperl = "$ENV{PERL_RUNPERL_DEBUG} $runperl"; 419 } 420 unless ($args{nolib}) { 421 if ($is_macos) { 422 $runperl .= ' -I::lib'; 423 # Use UNIX style error messages instead of MPW style. 424 $runperl .= ' -MMac::err=unix' if $args{stderr}; 425 } 426 else { 427 $runperl .= ' "-I../lib"'; # doublequotes because of VMS 428 } 429 } 430 if ($args{switches}) { 431 local $Level = 2; 432 die "test.pl:runperl(): 'switches' must be an ARRAYREF " . _where() 433 unless ref $args{switches} eq "ARRAY"; 434 _quote_args(\$runperl, $args{switches}); 435 } 436 if (defined $args{prog}) { 437 die "test.pl:runperl(): both 'prog' and 'progs' cannot be used " . _where() 438 if defined $args{progs}; 439 $args{progs} = [$args{prog}] 440 } 441 if (defined $args{progs}) { 442 die "test.pl:runperl(): 'progs' must be an ARRAYREF " . _where() 443 unless ref $args{progs} eq "ARRAY"; 444 foreach my $prog (@{$args{progs}}) { 445 if ($is_mswin || $is_netware || $is_vms) { 446 $runperl .= qq ( -e "$prog" ); 447 } 448 else { 449 $runperl .= qq ( -e '$prog' ); 450 } 451 } 452 } elsif (defined $args{progfile}) { 453 $runperl .= qq( "$args{progfile}"); 454 } else { 455 # You probaby didn't want to be sucking in from the upstream stdin 456 die "test.pl:runperl(): none of prog, progs, progfile, args, " 457 . " switches or stdin specified" 458 unless defined $args{args} or defined $args{switches} 459 or defined $args{stdin}; 460 } 461 if (defined $args{stdin}) { 462 # so we don't try to put literal newlines and crs onto the 463 # command line. 464 $args{stdin} =~ s/\n/\\n/g; 465 $args{stdin} =~ s/\r/\\r/g; 466 467 if ($is_mswin || $is_netware || $is_vms) { 468 $runperl = qq{$^X -e "print qq(} . 469 $args{stdin} . q{)" | } . $runperl; 470 } 471 elsif ($is_macos) { 472 # MacOS can only do two processes under MPW at once; 473 # the test itself is one; we can't do two more, so 474 # write to temp file 475 my $stdin = qq{$^X -e 'print qq(} . $args{stdin} . qq{)' > teststdin; }; 476 if ($args{verbose}) { 477 my $stdindisplay = $stdin; 478 $stdindisplay =~ s/\n/\n\#/g; 479 print STDERR "# $stdindisplay\n"; 480 } 481 `$stdin`; 482 $runperl .= q{ < teststdin }; 483 } 484 else { 485 $runperl = qq{$^X -e 'print qq(} . 486 $args{stdin} . q{)' | } . $runperl; 487 } 488 } 489 if (defined $args{args}) { 490 _quote_args(\$runperl, $args{args}); 491 } 492 $runperl .= ' 2>&1' if $args{stderr} && !$is_macos; 493 $runperl .= " \xB3 Dev:Null" if !$args{stderr} && $is_macos; 494 if ($args{verbose}) { 495 my $runperldisplay = $runperl; 496 $runperldisplay =~ s/\n/\n\#/g; 497 print STDERR "# $runperldisplay\n"; 498 } 499 return $runperl; 500} 501 502sub runperl { 503 die "test.pl:runperl() does not take a hashref" 504 if ref $_[0] and ref $_[0] eq 'HASH'; 505 my $runperl = &_create_runperl; 506 my $result; 507 508 my $tainted = ${^TAINT}; 509 my %args = @_; 510 exists $args{switches} && grep m/^-T$/, @{$args{switches}} and $tainted = $tainted + 1; 511 512 if ($tainted) { 513 # We will assume that if you're running under -T, you really mean to 514 # run a fresh perl, so we'll brute force launder everything for you 515 my $sep; 516 517 eval "require Config; Config->import"; 518 if ($@) { 519 warn "test.pl had problems loading Config: $@"; 520 $sep = ':'; 521 } else { 522 $sep = $Config{path_sep}; 523 } 524 525 my @keys = grep {exists $ENV{$_}} qw(CDPATH IFS ENV BASH_ENV); 526 local @ENV{@keys} = (); 527 # Untaint, plus take out . and empty string: 528 local $ENV{'DCL$PATH'} = $1 if $is_vms && ($ENV{'DCL$PATH'} =~ /(.*)/s); 529 $ENV{PATH} =~ /(.*)/s; 530 local $ENV{PATH} = 531 join $sep, grep { $_ ne "" and $_ ne "." and -d $_ and 532 ($is_mswin or $is_vms or !(stat && (stat _)[2]&0022)) } 533 split quotemeta ($sep), $1; 534 $ENV{PATH} .= "$sep/bin" if $is_cygwin; # Must have /bin under Cygwin 535 536 $runperl =~ /(.*)/s; 537 $runperl = $1; 538 539 $result = `$runperl`; 540 } else { 541 $result = `$runperl`; 542 } 543 $result =~ s/\n\n/\n/ if $is_vms; # XXX pipes sometimes double these 544 return $result; 545} 546 547*run_perl = \&runperl; # Nice alias. 548 549sub DIE { 550 print STDERR "# @_\n"; 551 exit 1; 552} 553 554# A somewhat safer version of the sometimes wrong $^X. 555my $Perl; 556sub which_perl { 557 unless (defined $Perl) { 558 $Perl = $^X; 559 560 # VMS should have 'perl' aliased properly 561 return $Perl if $^O eq 'VMS'; 562 563 my $exe; 564 eval "require Config; Config->import"; 565 if ($@) { 566 warn "test.pl had problems loading Config: $@"; 567 $exe = ''; 568 } else { 569 $exe = $Config{_exe}; 570 } 571 $exe = '' unless defined $exe; 572 573 # This doesn't absolutize the path: beware of future chdirs(). 574 # We could do File::Spec->abs2rel() but that does getcwd()s, 575 # which is a bit heavyweight to do here. 576 577 if ($Perl =~ /^perl\Q$exe\E$/i) { 578 my $perl = "perl$exe"; 579 eval "require File::Spec"; 580 if ($@) { 581 warn "test.pl had problems loading File::Spec: $@"; 582 $Perl = "./$perl"; 583 } else { 584 $Perl = File::Spec->catfile(File::Spec->curdir(), $perl); 585 } 586 } 587 588 # Build up the name of the executable file from the name of 589 # the command. 590 591 if ($Perl !~ /\Q$exe\E$/i) { 592 $Perl .= $exe; 593 } 594 595 warn "which_perl: cannot find $Perl from $^X" unless -f $Perl; 596 597 # For subcommands to use. 598 $ENV{PERLEXE} = $Perl; 599 } 600 return $Perl; 601} 602 603sub unlink_all { 604 foreach my $file (@_) { 605 1 while unlink $file; 606 print STDERR "# Couldn't unlink '$file': $!\n" if -f $file; 607 } 608} 609 610 611my $tmpfile = "misctmp000"; 6121 while -f ++$tmpfile; 613END { unlink_all $tmpfile } 614 615# 616# _fresh_perl 617# 618# The $resolve must be a subref that tests the first argument 619# for success, or returns the definition of success (e.g. the 620# expected scalar) if given no arguments. 621# 622 623sub _fresh_perl { 624 my($prog, $resolve, $runperl_args, $name) = @_; 625 626 $runperl_args ||= {}; 627 $runperl_args->{progfile} = $tmpfile; 628 $runperl_args->{stderr} = 1; 629 630 open TEST, ">$tmpfile" or die "Cannot open $tmpfile: $!"; 631 632 # VMS adjustments 633 if( $^O eq 'VMS' ) { 634 $prog =~ s#/dev/null#NL:#; 635 636 # VMS file locking 637 $prog =~ s{if \(-e _ and -f _ and -r _\)} 638 {if (-e _ and -f _)} 639 } 640 641 print TEST $prog; 642 close TEST or die "Cannot close $tmpfile: $!"; 643 644 my $results = runperl(%$runperl_args); 645 my $status = $?; 646 647 # Clean up the results into something a bit more predictable. 648 $results =~ s/\n+$//; 649 $results =~ s/at\s+misctmp\d+\s+line/at - line/g; 650 $results =~ s/of\s+misctmp\d+\s+aborted/of - aborted/g; 651 652 # bison says 'parse error' instead of 'syntax error', 653 # various yaccs may or may not capitalize 'syntax'. 654 $results =~ s/^(syntax|parse) error/syntax error/mig; 655 656 if ($^O eq 'VMS') { 657 # some tests will trigger VMS messages that won't be expected 658 $results =~ s/\n?%[A-Z]+-[SIWEF]-[A-Z]+,.*//; 659 660 # pipes double these sometimes 661 $results =~ s/\n\n/\n/g; 662 } 663 664 my $pass = $resolve->($results); 665 unless ($pass) { 666 _diag "# PROG: \n$prog\n"; 667 _diag "# EXPECTED:\n", $resolve->(), "\n"; 668 _diag "# GOT:\n$results\n"; 669 _diag "# STATUS: $status\n"; 670 } 671 672 # Use the first line of the program as a name if none was given 673 unless( $name ) { 674 ($first_line, $name) = $prog =~ /^((.{1,50}).*)/; 675 $name .= '...' if length $first_line > length $name; 676 } 677 678 _ok($pass, _where(), "fresh_perl - $name"); 679} 680 681# 682# fresh_perl_is 683# 684# Combination of run_perl() and is(). 685# 686 687sub fresh_perl_is { 688 my($prog, $expected, $runperl_args, $name) = @_; 689 local $Level = 2; 690 _fresh_perl($prog, 691 sub { @_ ? $_[0] eq $expected : $expected }, 692 $runperl_args, $name); 693} 694 695# 696# fresh_perl_like 697# 698# Combination of run_perl() and like(). 699# 700 701sub fresh_perl_like { 702 my($prog, $expected, $runperl_args, $name) = @_; 703 local $Level = 2; 704 _fresh_perl($prog, 705 sub { @_ ? 706 $_[0] =~ (ref $expected ? $expected : /$expected/) : 707 $expected }, 708 $runperl_args, $name); 709} 710 711sub can_ok ($@) { 712 my($proto, @methods) = @_; 713 my $class = ref $proto || $proto; 714 715 unless( @methods ) { 716 return _ok( 0, _where(), "$class->can(...)" ); 717 } 718 719 my @nok = (); 720 foreach my $method (@methods) { 721 local($!, $@); # don't interfere with caller's $@ 722 # eval sometimes resets $! 723 eval { $proto->can($method) } || push @nok, $method; 724 } 725 726 my $name; 727 $name = @methods == 1 ? "$class->can('$methods[0]')" 728 : "$class->can(...)"; 729 730 _ok( !@nok, _where(), $name ); 731} 732 733sub isa_ok ($$;$) { 734 my($object, $class, $obj_name) = @_; 735 736 my $diag; 737 $obj_name = 'The object' unless defined $obj_name; 738 my $name = "$obj_name isa $class"; 739 if( !defined $object ) { 740 $diag = "$obj_name isn't defined"; 741 } 742 elsif( !ref $object ) { 743 $diag = "$obj_name isn't a reference"; 744 } 745 else { 746 # We can't use UNIVERSAL::isa because we want to honor isa() overrides 747 local($@, $!); # eval sometimes resets $! 748 my $rslt = eval { $object->isa($class) }; 749 if( $@ ) { 750 if( $@ =~ /^Can't call method "isa" on unblessed reference/ ) { 751 if( !UNIVERSAL::isa($object, $class) ) { 752 my $ref = ref $object; 753 $diag = "$obj_name isn't a '$class' it's a '$ref'"; 754 } 755 } else { 756 die <<WHOA; 757WHOA! I tried to call ->isa on your object and got some weird error. 758This should never happen. Please contact the author immediately. 759Here's the error. 760$@ 761WHOA 762 } 763 } 764 elsif( !$rslt ) { 765 my $ref = ref $object; 766 $diag = "$obj_name isn't a '$class' it's a '$ref'"; 767 } 768 } 769 770 _ok( !$diag, _where(), $name ); 771} 772 7731; 774