1package OptreeCheck; 2use parent 'Exporter'; 3use strict; 4use warnings; 5our ($TODO, $Level, $using_open); 6require "test.pl"; 7 8our $VERSION = '0.17'; 9 10# now export checkOptree, and those test.pl functions used by tests 11our @EXPORT = qw( checkOptree plan skip skip_all pass is like unlike 12 require_ok runperl tempfile); 13 14 15# The hints flags will differ if ${^OPEN} is set. 16# The approach taken is to put the hints-with-open in the golden results, and 17# flag that they need to be taken out if ${^OPEN} is set. 18 19if (((caller 0)[10]||{})->{'open<'}) { 20 $using_open = 1; 21} 22 23=head1 NAME 24 25OptreeCheck - check optrees as rendered by B::Concise 26 27=head1 SYNOPSIS 28 29OptreeCheck supports 'golden-sample' regression testing of perl's 30parser, optimizer, bytecode generator, via a single function: 31checkOptree(%in). 32 33It invokes B::Concise upon the sample code, checks that the rendering 34'agrees' with the golden sample, and reports mismatches. 35 36Additionally, the module processes @ARGV (which is typically unused in 37the Core test harness), and thus provides a means to run the tests in 38various modes. 39 40=head1 EXAMPLE 41 42 # your test file 43 use OptreeCheck; 44 plan tests => 1; 45 46 checkOptree ( 47 name => "test-name', # optional, made from others if not given 48 49 # code-under-test: must provide 1 of them 50 code => sub {my $a}, # coderef, or source (wrapped and evald) 51 prog => 'sort @a', # run in subprocess, aka -MO=Concise 52 bcopts => '-exec', # $opt or \@opts, passed to BC::compile 53 54 errs => 'Name "main::a" used only once: possible typo at -e line 1.', 55 # str, regex, [str+] [regex+], 56 57 # various test options 58 # errs => '.*', # match against any emitted errs, -w warnings 59 # skip => 1, # skips test 60 # todo => 'excuse', # anticipated failures 61 # fail => 1 # force fail (by redirecting result) 62 63 # the 'golden-sample's, (must provide both) 64 65 expect => <<'EOT_EOT', expect_nt => <<'EONT_EONT' ); # start HERE-DOCS 66 # 1 <;> nextstate(main 45 optree.t:23) v 67 # 2 <0> padsv[$a:45,46] M/LVINTRO 68 # 3 <1> leavesub[1 ref] K/REFC,1 69 EOT_EOT 70 # 1 <;> nextstate(main 45 optree.t:23) v 71 # 2 <0> padsv[$a:45,46] M/LVINTRO 72 # 3 <1> leavesub[1 ref] K/REFC,1 73 EONT_EONT 74 75 __END__ 76 77=head2 Failure Reports 78 79 Heres a sample failure, as induced by the following command. 80 Note the argument; option=value, after the test-file, more on that later 81 82 $> PERL_CORE=1 ./perl ext/B/t/optree_check.t testmode=cross 83 ... 84 ok 19 - canonical example w -basic 85 not ok 20 - -exec code: $a=$b+42 86 # Failed at test.pl line 249 87 # got '1 <;> nextstate(main 600 optree_check.t:208) v 88 # 2 <#> gvsv[*b] s 89 # 3 <$> const[IV 42] s 90 # 4 <2> add[t3] sK/2 91 # 5 <#> gvsv[*a] s 92 # 6 <2> sassign sKS/2 93 # 7 <1> leavesub[1 ref] K/REFC,1 94 # ' 95 # expected /(?ms-xi:^1 <;> (?:next|db)state(.*?) v 96 # 2 <\$> gvsv\(\*b\) s 97 # 3 <\$> const\(IV 42\) s 98 # 4 <2> add\[t\d+\] sK/2 99 # 5 <\$> gvsv\(\*a\) s 100 # 6 <2> sassign sKS/2 101 # 7 <1> leavesub\[\d+ refs?\] K/REFC,1 102 # $)/ 103 # got: '2 <#> gvsv[*b] s' 104 # want: (?^:2 <\$> gvsv\(\*b\) s) 105 # got: '3 <$> const[IV 42] s' 106 # want: (?^:3 <\$> const\(IV 42\) s) 107 # got: '5 <#> gvsv[*a] s' 108 # want: (?^:5 <\$> gvsv\(\*a\) s) 109 # remainder: 110 # 2 <#> gvsv[*b] s 111 # 3 <$> const[IV 42] s 112 # 5 <#> gvsv[*a] s 113 # these lines not matched: 114 # 2 <#> gvsv[*b] s 115 # 3 <$> const[IV 42] s 116 # 5 <#> gvsv[*a] s 117 118Errors are reported 3 different ways; 119 120The 1st form is directly from test.pl's like() and unlike(). Note 121that this form is used as input, so you can easily cut-paste results 122into test-files you are developing. Just make sure you recognize 123insane results, to avoid canonizing them as golden samples. 124 125The 2nd and 3rd forms show only the unexpected results and opcodes. 126This is done because it's blindingly tedious to find a single opcode 127causing the failure. 2 different ways are done in case one is 128unhelpful. 129 130=head1 TestCase Overview 131 132checkOptree(%tc) constructs a testcase object from %tc, and then calls 133methods which eventually call test.pl's like() to produce test 134results. 135 136=head2 getRendering 137 138getRendering() runs code or prog or progfile through B::Concise, and 139captures its rendering. Errors emitted during rendering are checked 140against expected errors, and are reported as diagnostics by default, 141or as failures if 'report=fail' cmdline-option is given. 142 143prog is run in a sub-shell, with $bcopts passed through. This is the way 144to run code intended for main. The code arg in contrast, is always a 145CODEREF, either because it starts that way as an arg, or because it's 146wrapped and eval'd as $sub = sub {$code}; 147 148=head2 mkCheckRex 149 150mkCheckRex() selects the golden-sample for the threaded-ness of the 151platform, and produces a regex which matches the expected rendering, 152and fails when it doesn't match. 153 154The regex includes 'workarounds' which accommodate expected rendering 155variations. These include: 156 157 string constants # avoid injection 158 line numbers, etc # args of nexstate() 159 hexadecimal-numbers 160 161 pad-slot-assignments # for 5.8 compat, and testmode=cross 162 (map|grep)(start|while) # for 5.8 compat 163 164=head2 mylike 165 166mylike() calls either unlike() or like(), depending on 167expectations. Mismatch reports are massaged, because the actual 168difference can easily be lost in the forest of opcodes. 169 170=head1 checkOptree API and Operation 171 172Since the arg is a hash, the api is wide-open, and this really is 173about what elements must be or are in the hash, and what they do. %tc 174is passed to newTestCase(), the ctor, which adds in %proto, a global 175prototype object. 176 177=head2 name => STRING 178 179If name property is not provided, it is synthesized from these params: 180bcopts, note, prog, code. This is more convenient than trying to do 181it manually. 182 183=head2 code or prog or progfile 184 185Either code or prog or progfile must be present. 186 187=head2 prog => $perl_source_string 188 189prog => $src provides a snippet of code, which is run in a sub-process, 190via test.pl:runperl, and through B::Concise like so: 191 192 './perl -w -MO=Concise,$bcopts_massaged -e $src' 193 194=head2 progfile => $perl_script 195 196progfile => $file provides a file containing a snippet of code which is 197run as per the prog => $src example above. 198 199=head2 code => $perl_source_string || CODEREF 200 201The $code arg is passed to B::Concise::compile(), and run in-process. 202If $code is a string, it's first wrapped and eval'd into a $coderef. 203In either case, $coderef is then passed to B::Concise::compile(): 204 205 $subref = eval "sub{$code}"; 206 $render = B::Concise::compile($subref)->(); 207 208=head2 expect and expect_nt 209 210expect and expect_nt args are the B<golden-sample> renderings, and are 211sampled from known-ok threaded and un-threaded bleadperl builds. 212They're both required, and the correct one is selected for the platform 213being tested, and saved into the synthesized property B<wanted>. 214 215=head2 bcopts => $bcopts || [ @bcopts ] 216 217When getRendering() runs, it passes bcopts into B::Concise::compile(). 218The bcopts arg can be a single string, or an array of strings. 219 220=head2 errs => $err_str_regex || [ @err_str_regexs ] 221 222getRendering() processes the code or prog or progfile arg under warnings, 223and both parsing and optree-traversal errors are collected. These are 224validated against the one or more errors you specify. 225 226=head1 testcase modifier properties 227 228These properties are set as %tc parameters to change test behavior. 229 230=head2 skip => 'reason' 231 232invokes skip('reason'), causing test to skip. 233 234=head2 todo => 'reason' 235 236invokes todo('reason') 237 238=head2 fail => 1 239 240For code arguments, this option causes getRendering to redirect the 241rendering operation to STDERR, which causes the regex match to fail. 242 243=head2 noanchors => 1 244 245If set, this relaxes the regex check, which is normally pretty strict. 246It's used primarily to validate checkOptree via tests in optree_check. 247 248 249=head1 Synthesized object properties 250 251These properties are added into the test object during execution. 252 253=head2 wanted 254 255This stores the chosen expect expect_nt string. The OptreeCheck 256object may in the future delete the raw strings once wanted is set, 257thus saving space. 258 259=head2 cross => 1 260 261This tag is added if testmode=cross is passed in as argument. 262It causes test-harness to purposely use the wrong string. 263 264 265=head2 checkErrs 266 267checkErrs() is a getRendering helper that verifies that expected errs 268against those found when rendering the code on the platform. It is 269run after rendering, and before mkCheckRex. 270 271=cut 272 273use Config; 274use Carp; 275use B::Concise qw(walk_output); 276 277BEGIN { 278 $SIG{__WARN__} = sub { 279 my $err = shift; 280 $err =~ m/Subroutine re::(un)?install redefined/ and return; 281 }; 282} 283 284sub import { 285 my $pkg = shift; 286 $pkg->export_to_level(1,'checkOptree', @EXPORT); 287 getCmdLine(); # process @ARGV 288} 289 290 291# %gOpts params comprise a global test-state. Initial values here are 292# HELP strings, they MUST BE REPLACED by runtime values before use, as 293# is done by getCmdLine(), via import 294 295our %gOpts = # values are replaced at runtime !! 296 ( 297 # scalar values are help string 298 selftest => 'self-tests mkCheckRex vs the reference rendering', 299 300 fail => 'force all test to fail, print to stdout', 301 dump => 'dump cmdline arg processing', 302 noanchors => 'dont anchor match rex', 303 304 # array values are one-of selections, with 1st value as default 305 # array: 2nd value is used as help-str, 1st val (still) default 306 help => [0, 'provides help and exits', 0], 307 testmode => [qw/ native cross both /], 308 309 # fixup for VMS, cygwin, which don't have stderr b4 stdout 310 rxnoorder => [1, 'if 1, dont req match on -e lines, and -banner',0], 311 strip => [1, 'if 1, catch errs and remove from renderings',0], 312 stripv => 'if strip&&1, be verbose about it', 313 errs => 'expected compile errs, array if several', 314 ); 315 316 317our $threaded = 1 if $Config::Config{usethreads}; 318our $platform = ($threaded) ? "threaded" : "plain"; 319our $thrstat = ($threaded) ? "threaded" : "nonthreaded"; 320 321our %modes = ( 322 both => [ 'expect', 'expect_nt'], 323 native => [ ($threaded) ? 'expect' : 'expect_nt'], 324 cross => [ !($threaded) ? 'expect' : 'expect_nt'], 325 expect => [ 'expect' ], 326 expect_nt => [ 'expect_nt' ], 327 ); 328 329our %msgs # announce cross-testing. 330 = ( 331 # cross-platform 332 'expect_nt-threaded' => " (nT on T) ", 333 'expect-nonthreaded' => " (T on nT) ", 334 # native - nothing to say (must stay empty - used for $crosstesting) 335 'expect_nt-nonthreaded' => '', 336 'expect-threaded' => '', 337 ); 338 339####### 340sub getCmdLine { # import assistant 341 # offer help 342 print(qq{\n$0 accepts args to update these state-vars: 343 turn on a flag by typing its name, 344 select a value from list by typing name=val.\n }, 345 mydumper(\%gOpts)) 346 if grep /help/, @ARGV; 347 348 # replace values for each key !! MUST MARK UP %gOpts 349 foreach my $opt (keys %gOpts) { 350 351 # scan ARGV for known params 352 if (ref $gOpts{$opt} eq 'ARRAY') { 353 354 # $opt is a One-Of construct 355 # replace with valid selection from the list 356 357 # uhh this WORKS. but it's inscrutable 358 # grep s/$opt=(\w+)/grep {$_ eq $1} @ARGV and $gOpts{$opt}=$1/e, @ARGV; 359 my $tval; # temp 360 if (grep s/$opt=(\w+)/$tval=$1/e, @ARGV) { 361 # check val before accepting 362 my @allowed = @{$gOpts{$opt}}; 363 if (grep { $_ eq $tval } @allowed) { 364 $gOpts{$opt} = $tval; 365 } 366 else {die "invalid value: '$tval' for $opt\n"} 367 } 368 369 # take 1st val as default 370 $gOpts{$opt} = ${$gOpts{$opt}}[0] 371 if ref $gOpts{$opt} eq 'ARRAY'; 372 } 373 else { # handle scalars 374 375 # if 'opt' is present, true 376 $gOpts{$opt} = (grep /^$opt/, @ARGV) ? 1 : 0; 377 378 # override with 'foo' if 'opt=foo' appears 379 grep s/$opt=(.*)/$gOpts{$opt}=$1/e, @ARGV; 380 } 381 } 382 print("$0 heres current state:\n", mydumper(\%gOpts)) 383 if $gOpts{help} or $gOpts{dump}; 384 385 exit if $gOpts{help}; 386} 387# the above arg-handling cruft should be replaced by a Getopt call 388 389############################## 390# the API (1 function) 391 392sub checkOptree { 393 my $tc = newTestCases(@_); # ctor 394 my ($rendering); 395 396 print "checkOptree args: ",mydumper($tc) if $tc->{dump}; 397 SKIP: { 398 if ($tc->{skip}) { 399 skip("$tc->{skip} $tc->{name}", 400 ($gOpts{selftest} 401 ? 1 402 : 1 + @{$modes{$gOpts{testmode}}} 403 ) 404 ); 405 } 406 407 return runSelftest($tc) if $gOpts{selftest}; 408 409 $tc->getRendering(); # get the actual output 410 $tc->checkErrs(); 411 412 local $Level = $Level + 2; 413 TODO: 414 foreach my $want (@{$modes{$gOpts{testmode}}}) { 415 local $TODO = $tc->{todo} if $tc->{todo}; 416 417 $tc->{cross} = $msgs{"$want-$thrstat"}; 418 419 $tc->mkCheckRex($want); 420 $tc->mylike(); 421 } 422 } 423 return; 424} 425 426sub newTestCases { 427 # make test objects (currently 1) from args (passed to checkOptree) 428 my $tc = bless { @_ }, __PACKAGE__ 429 or die "test cases are hashes"; 430 431 $tc->label(); 432 433 # cpy globals into each test 434 foreach my $k (keys %gOpts) { 435 if ($gOpts{$k}) { 436 $tc->{$k} = $gOpts{$k} unless defined $tc->{$k}; 437 } 438 } 439 if ($tc->{errs}) { 440 $tc->{errs} = [$tc->{errs}] unless ref $tc->{errs} eq 'ARRAY'; 441 } 442 return $tc; 443} 444 445sub label { 446 # may help get/keep test output consistent 447 my ($tc) = @_; 448 return $tc->{name} if $tc->{name}; 449 450 my $buf = (ref $tc->{bcopts}) 451 ? join(',', @{$tc->{bcopts}}) : $tc->{bcopts}; 452 453 foreach (qw( note prog code )) { 454 $buf .= " $_: $tc->{$_}" if $tc->{$_} and not ref $tc->{$_}; 455 } 456 return $tc->{name} = $buf; 457} 458 459################# 460# render and its helpers 461 462sub getRendering { 463 my $tc = shift; 464 fail("getRendering: code or prog or progfile is required") 465 unless $tc->{code} or $tc->{prog} or $tc->{progfile}; 466 467 my @opts = get_bcopts($tc); 468 my $rendering = ''; # suppress "Use of uninitialized value in open" 469 my @errs; # collect errs via 470 471 472 if ($tc->{prog}) { 473 $rendering = runperl( switches => ['-w',join(',',"-MO=Concise",@opts)], 474 prog => $tc->{prog}, stderr => 1, 475 ); # verbose => 1); 476 } elsif ($tc->{progfile}) { 477 $rendering = runperl( switches => ['-w',join(',',"-MO=Concise",@opts)], 478 progfile => $tc->{progfile}, stderr => 1, 479 ); # verbose => 1); 480 } else { 481 my $code = $tc->{code}; 482 unless (ref $code eq 'CODE') { 483 # treat as source, and wrap into subref 484 # in caller's package ( to test arg-fixup, comment next line) 485 my $pkg = '{ package '.caller(1) .';'; 486 { 487 BEGIN { $^H = 0 } 488 no warnings; 489 $code = eval "$pkg sub { $code } }"; 490 } 491 # return errors 492 if ($@) { chomp $@; push @errs, $@ } 493 } 494 # set walk-output b4 compiling, which writes 'announce' line 495 walk_output(\$rendering); 496 497 my $opwalker = B::Concise::compile(@opts, $code); 498 die "bad BC::compile retval" unless ref $opwalker eq 'CODE'; 499 500 B::Concise::reset_sequence(); 501 $opwalker->(); 502 503 # kludge error into rendering if its empty. 504 $rendering = $@ if $@ and ! $rendering; 505 } 506 # separate banner, other stuff whose printing order isnt guaranteed 507 if ($tc->{strip}) { 508 $rendering =~ s/(B::Concise::compile.*?\n)//; 509 print "stripped from rendering <$1>\n" if $1 and $tc->{stripv}; 510 511 #while ($rendering =~ s/^(.*?(-e) line \d+\.)\n//g) { 512 while ($rendering =~ s/^(.*?(-e|\(eval \d+\).*?) line \d+\.)\n//g) { 513 print "stripped <$1> $2\n" if $tc->{stripv}; 514 push @errs, $1; 515 } 516 $rendering =~ s/-e syntax OK\n//; 517 $rendering =~ s/-e had compilation errors\.\n//; 518 } 519 $tc->{got} = $rendering; 520 $tc->{goterrs} = \@errs if @errs; 521 return $rendering, @errs; 522} 523 524sub get_bcopts { 525 # collect concise passthru-options if any 526 my ($tc) = shift; 527 my @opts = (); 528 if ($tc->{bcopts}) { 529 @opts = (ref $tc->{bcopts} eq 'ARRAY') 530 ? @{$tc->{bcopts}} : ($tc->{bcopts}); 531 } 532 return @opts; 533} 534 535sub checkErrs { 536 # check rendering errs against expected errors, reduce and report 537 my $tc = shift; 538 539 # check for agreement (order not important) 540 my (%goterrs, @missed); 541 @goterrs{@{$tc->{goterrs}}} = (1) x scalar @{$tc->{goterrs}} 542 if $tc->{goterrs}; 543 544 foreach my $want (@{$tc->{errs}}) { 545 if (ref $want) { 546 my $seen; 547 foreach my $k (keys %goterrs) { 548 next unless $k =~ $want; 549 delete $goterrs{$k}; 550 ++$seen; 551 } 552 push @missed, $want unless $seen; 553 } else { 554 push @missed, $want unless defined delete $goterrs{$want}; 555 } 556 } 557 558 @missed = sort @missed; 559 my @got = sort keys %goterrs; 560 561 if (@{$tc->{errs}}) { 562 is(@missed + @got, 0, "Only got expected errors for $tc->{name}") 563 } else { 564 # @missed must be 0 here. 565 is(scalar @got, 0, "Got no errors for $tc->{name}") 566 } 567 _diag(join "\n", "got unexpected:", @got) if @got; 568 _diag(join "\n", "missed expected:", @missed) if @missed; 569} 570 571=head1 mkCheckRex ($tc) 572 573It selects the correct golden-sample from the test-case object, and 574converts it into a Regexp which should match against the original 575golden-sample (used in selftest, see below), and on the renderings 576obtained by applying the code on the perl being tested. 577 578The selection is driven by platform mostly, but also by test-mode, 579which rather complicates the code. This is worsened by the potential 580need to make platform specific conversions on the reftext. 581 582but is otherwise as strict as possible. For example, it should *not* 583match when opcode flags change, or when optimizations convert an op to 584an ex-op. 585 586 587=head2 match criteria 588 589The selected golden-sample is massaged to eliminate various match 590irrelevancies. This is done so that the tests don't fail just because 591you added a line to the top of the test file. (Recall that the 592renderings contain the program's line numbers). Similar cleanups are 593done on "strings", hex-constants, etc. 594 595The need to massage is reflected in the 2 golden-sample approach of 596the test-cases; we want the match to be as rigorous as possible, and 597thats easier to achieve when matching against 1 input than 2. 598 599Opcode arguments (text within braces) are disregarded for matching 600purposes. This loses some info in 'add[t5]', but greatly simplifies 601matching 'nextstate(main 22 (eval 10):1)'. Besides, we are testing 602for regressions, not for complete accuracy. 603 604The regex is anchored by default, but can be suppressed with 605'noanchors', allowing 1-liner tests to succeed if opcode is found. 606 607=cut 608 609# needless complexity due to 'too much info' from B::Concise v.60 610my $announce = 'B::Concise::compile\(CODE\(0x[0-9a-f]+\)\)';; 611 612sub mkCheckRex { 613 # converts expected text into Regexp which should match against 614 # unaltered version. also adjusts threaded => non-threaded 615 my ($tc, $want) = @_; 616 617 my $str = $tc->{expect} || $tc->{expect_nt}; # standard bias 618 $str = $tc->{$want} if $want && $tc->{$want}; # stated pref 619 620 die("no '$want' golden-sample found: $tc->{name}") unless $str; 621 622 $str =~ s/^\# //mg; # ease cut-paste testcase authoring 623 624 $tc->{wantstr} = $str; 625 626 # make UNOP_AUX flag type literal 627 $str =~ s/<\+>/<\\+>/; 628 # make targ args wild 629 $str =~ s/\[t\d+\]/[t\\d+]/msg; 630 631 # escape bracing, etc.. manual \Q (doesn't escape '+') 632 $str =~ s/([\[\]()*.\$\@\#\|{}])/\\$1/msg; 633 # $str =~ s/(?<!\\)([\[\]\(\)*.\$\@\#\|{}])/\\$1/msg; 634 635 # treat dbstate like nextstate (no in-debugger false reports) 636 # Note also that there may be 1 level of () nexting, if there's an eval 637 # Seems easiest to explicitly match the eval, rather than trying to parse 638 # for full balancing and then substitute .*? 639 # In which case, we can continue to match for the eval in the rexexp built 640 # from the golden result. 641 642 $str =~ s!(?:next|db)state 643 \\\( # opening literal ( (backslash escaped) 644 [^()]*? # not () 645 (\\\(eval\ \d+\\\) # maybe /eval \d+/ in () 646 [^()]*? # which might be followed by something 647 )? 648 \\\) # closing literal ) 649 !'(?:next|db)state\\([^()]*?' . 650 ($1 && '\\(eval \\d+\\)[^()]*') # Match the eval if present 651 . '\\)'!msgxe; 652 # widened for -terse mode 653 $str =~ s/(?:next|db)state/(?:next|db)state/msg; 654 if (!$using_open && $tc->{strip_open_hints}) { 655 $str =~ s[( # capture 656 \(\?:next\|db\)state # the regexp matching next/db state 657 .* # all sorts of things follow it 658 v # The opening v 659 ) 660 (?:(:>,<,%,\\\{) # hints when open.pm is in force 661 |(:>,<,%)) # (two variations) 662 (\ ->(?:-|[0-9a-z]+))? 663 $ 664 ] 665 [$1 . ($2 && ':\{') . $4]xegm; # change to the hints without open.pm 666 } 667 668 669 # don't care about: 670 $str =~ s/:-?\d+,-?\d+/:-?\\d+,-?\\d+/msg; # FAKE line numbers 671 $str =~ s/match\\\(.*?\\\)/match\(.*?\)/msg; # match args 672 $str =~ s/(0x[0-9A-Fa-f]+)/0x[0-9A-Fa-f]+/msg; # hexnum values 673 $str =~ s/".*?"/".*?"/msg; # quoted strings 674 $str =~ s/FAKE:(\w):\d+/FAKE:$1:\\d+/msg; # parent pad index 675 676 $str =~ s/(\d refs?)/\\d+ refs?/msg; # 1 ref, 2+ refs (plural) 677 $str =~ s/leavesub \[\d\]/leavesub [\\d]/msg; # for -terse 678 #$str =~ s/(\s*)\n/\n/msg; # trailing spaces 679 680 croak "whitespace only reftext found for '$want': $tc->{name}" 681 unless $str =~ /\w+/; # fail unless a real test 682 683 # $str = '.*' if 1; # sanity test 684 # $str .= 'FAIL' if 1; # sanity test 685 686 # allow -eval, banner at beginning of anchored matches 687 $str = "(-e .*?)?(B::Concise::compile.*?)?\n" . $str 688 unless $tc->{noanchors} or $tc->{rxnoorder}; 689 690 my $qr = ($tc->{noanchors}) ? qr/$str/ms : qr/^$str$/ms ; 691 692 $tc->{rex} = $qr; 693 $tc->{rexstr} = $str; 694 $tc; 695} 696 697############## 698# compare and report 699 700sub mylike { 701 # reworked mylike to use hash-obj 702 my $tc = shift; 703 my $got = $tc->{got}; 704 my $want = $tc->{rex}; 705 my $cmnt = $tc->{name}; 706 my $cross = $tc->{cross}; 707 708 # bad is anticipated failure 709 my $bad = ($cross && $tc->{crossfail}) || (!$cross && $tc->{fail}); 710 711 my $ok = $bad ? unlike ($got, $want, $cmnt) : like ($got, $want, $cmnt); 712 713 reduceDiffs ($tc) if not $ok; 714 715 return $ok; 716} 717 718sub reduceDiffs { 719 # isolate the real diffs and report them. 720 # i.e. these kinds of errs: 721 # 1. missing or extra ops. this skews all following op-sequences 722 # 2. single op diff, the rest of the chain is unaltered 723 # in either case, std err report is inadequate; 724 725 my $tc = shift; 726 my $got = $tc->{got}; 727 my @got = split(/\n/, $got); 728 my $want = $tc->{wantstr}; 729 my @want = split(/\n/, $want); 730 731 # split rexstr into units that should eat leading lines. 732 my @rexs = map qr/$_/, split (/\n/, $tc->{rexstr}); 733 734 foreach my $rex (@rexs) { 735 my $exp = shift @want; 736 my $line = shift @got; 737 # remove matches, and report 738 unless ($got =~ s/^($rex\n)//ms) { 739 _diag("got:\t\t'$line'\nwant:\t $rex\n"); 740 last; 741 } 742 } 743 _diag("remainder:\n$got"); 744 _diag("these lines not matched:\n$got\n"); 745} 746 747=head1 Global modes 748 749Unusually, this module also processes @ARGV for command-line arguments 750which set global modes. These 'options' change the way the tests run, 751essentially reusing the tests for different purposes. 752 753 754 755Additionally, there's an experimental control-arg interface (i.e. 756subject to change) which allows the user to set global modes. 757 758 759=head1 Testing Method 760 761At 1st, optreeCheck used one reference-text, but the differences 762between Threaded and Non-threaded renderings meant that a single 763reference (sampled from say, threaded) would be tricky and iterative 764to convert for testing on a non-threaded build. Worse, this conflicts 765with making tests both strict and precise. 766 767We now use 2 reference texts, the right one is used based upon the 768build's threaded-ness. This has several benefits: 769 770 1. native reference data allows closer/easier matching by regex. 771 2. samples can be eyeballed to grok T-nT differences. 772 3. data can help to validate mkCheckRex() operation. 773 4. can develop regexes which accommodate T-nT differences. 774 5. can test with both native and cross-converted regexes. 775 776Cross-testing (expect_nt on threaded, expect on non-threaded) exposes 777differences in B::Concise output, so mkCheckRex has code to do some 778cross-test manipulations. This area needs more work. 779 780=head1 Test Modes 781 782One consequence of a single-function API is difficulty controlling 783test-mode. I've chosen for now to use a package hash, %gOpts, to store 784test-state. These properties alter checkOptree() function, either 785short-circuiting to selftest, or running a loop that runs the testcase 7862^N times, varying conditions each time. (current N is 2 only). 787 788So Test-mode is controlled with cmdline args, also called options below. 789Run with 'help' to see the test-state, and how to change it. 790 791=head2 selftest 792 793This argument invokes runSelftest(), which tests a regex against the 794reference renderings that they're made from. Failure of a regex match 795its 'mold' is a strong indicator that mkCheckRex is buggy. 796 797That said, selftest mode currently runs a cross-test too, they're not 798completely orthogonal yet. See below. 799 800=head2 testmode=cross 801 802Cross-testing is purposely creating a T-NT mismatch, looking at the 803fallout, which helps to understand the T-NT differences. 804 805The tweaking appears contrary to the 2-refs philosophy, but the tweaks 806will be made in conversion-specific code, which (will) handles T->NT 807and NT->T separately. The tweaking is incomplete. 808 809A reasonable 1st step is to add tags to indicate when TonNT or NTonT 810is known to fail. This needs an option to force failure, so the 811test.pl reporting mechanics show results to aid the user. 812 813=head2 testmode=native 814 815This is normal mode. Other valid values are: native, cross, both. 816 817=head2 checkOptree Notes 818 819Accepts test code, renders its optree using B::Concise, and matches 820that rendering against a regex built from one of 2 reference 821renderings %tc data. 822 823The regex is built by mkCheckRex(\%tc), which scrubs %tc data to 824remove match-irrelevancies, such as (args) and [args]. For example, 825it strips leading '# ', making it easy to cut-paste new tests into 826your test-file, run it, and cut-paste actual results into place. You 827then retest and reedit until all 'errors' are gone. (now make sure you 828haven't 'enshrined' a bug). 829 830name: The test name. May be augmented by a label, which is built from 831important params, and which helps keep names in sync with whats being 832tested. 833 834=cut 835 836sub runSelftest { 837 # tests the regex produced by mkCheckRex() 838 # by using on the expect* text it was created with 839 # failures indicate a code bug, 840 # OR regexs plugged into the expect* text (which defeat conversions) 841 my $tc = shift; 842 843 for my $provenance (qw/ expect expect_nt /) { 844 #next unless $tc->{$provenance}; 845 846 $tc->mkCheckRex($provenance); 847 $tc->{got} = $tc->{wantstr}; # fake the rendering 848 $tc->mylike(); 849 } 850} 851 852my $dumploaded = 0; 853 854sub mydumper { 855 856 do { Dumper(@_); return } if $dumploaded; 857 858 eval "require Data::Dumper" 859 or do{ 860 print "Sorry, Data::Dumper is not available\n"; 861 print "half hearted attempt:\n"; 862 foreach my $it (@_) { 863 if (ref $it eq 'HASH') { 864 print " $_ => $it->{$_}\n" foreach sort keys %$it; 865 } 866 } 867 return; 868 }; 869 870 Data::Dumper->import; 871 $Data::Dumper::Sortkeys = 1; 872 $dumploaded++; 873 Dumper(@_); 874} 875 876############################ 877# support for test writing 878 879sub preamble { 880 my $testct = shift || 1; 881 return <<EO_HEADER; 882#!perl 883 884BEGIN { 885 chdir q(t); 886 \@INC = qw(../lib ../ext/B/t); 887 require q(./test.pl); 888} 889use OptreeCheck; 890plan tests => $testct; 891 892EO_HEADER 893 894} 895 896sub OptreeCheck::wrap { 897 my $code = shift; 898 $code =~ s/(?:(\#.*?)\n)//gsm; 899 $code =~ s/\s+/ /mgs; 900 chomp $code; 901 return unless $code =~ /\S/; 902 my $comment = $1; 903 904 my $testcode = qq{ 905 906checkOptree(note => q{$comment}, 907 bcopts => q{-exec}, 908 code => q{$code}, 909 expect => <<EOT_EOT, expect_nt => <<EONT_EONT); 910ThreadedRef 911 paste your 'golden-example' here, then retest 912EOT_EOT 913NonThreadedRef 914 paste your 'golden-example' here, then retest 915EONT_EONT 916 917}; 918 return $testcode; 919} 920 921sub OptreeCheck::gentest { 922 my ($code,$opts) = @_; 923 my $rendering = getRendering({code => $code}); 924 my $testcode = OptreeCheck::wrap($code); 925 return unless $testcode; 926 927 # run the prog, capture 'reference' concise output 928 my $preamble = preamble(1); 929 my $got = runperl( prog => "$preamble $testcode", stderr => 1, 930 #switches => ["-I../ext/B/t", "-MOptreeCheck"], 931 ); #verbose => 1); 932 933 # extract the 'reftext' ie the got 'block' 934 if ($got =~ m/got \'.*?\n(.*)\n\# \'\n\# expected/s) { 935 my $goldentxt = $1; 936 #and plug it into the test-src 937 if ($threaded) { 938 $testcode =~ s/ThreadedRef/$goldentxt/; 939 } else { 940 $testcode =~ s/NonThreadRef/$goldentxt/; 941 } 942 my $b4 = q{expect => <<EOT_EOT, expect_nt => <<EONT_EONT}; 943 my $af = q{expect => <<'EOT_EOT', expect_nt => <<'EONT_EONT'}; 944 $testcode =~ s/$b4/$af/; 945 946 return $testcode; 947 } 948 return ''; 949} 950 951 952sub OptreeCheck::processExamples { 953 my @files = @_; 954 955 # gets array of paragraphs, which should be code-samples. They're 956 # turned into optreeCheck tests, 957 958 foreach my $file (@files) { 959 open (my $fh, '<', $file) or die "cant open $file: $!\n"; 960 $/ = ""; 961 my @chunks = <$fh>; 962 print preamble (scalar @chunks); 963 foreach my $t (@chunks) { 964 print "\n\n=for gentest\n\n# chunk: $t=cut\n\n"; 965 print OptreeCheck::gentest ($t); 966 } 967 } 968} 969 970# OK - now for the final insult to your good taste... 971 972if ($0 =~ /OptreeCheck\.pm/) { 973 974 #use lib 't'; 975 require './t/test.pl'; 976 977 # invoked as program. Work like former gentest.pl, 978 # ie read files given as cmdline args, 979 # convert them to usable test files. 980 981 require Getopt::Std; 982 Getopt::Std::getopts('') or 983 die qq{ $0 sample-files* # no options 984 985 expecting filenames as args. Each should have paragraphs, 986 these are converted to checkOptree() tests, and printed to 987 stdout. Redirect to file then edit for test. \n}; 988 989 OptreeCheck::processExamples(@ARGV); 990} 991 9921; 993 994__END__ 995 996=head1 TEST DEVELOPMENT SUPPORT 997 998This optree regression testing framework needs tests in order to find 999bugs. To that end, OptreeCheck has support for developing new tests, 1000according to the following model: 1001 1002 1. write a set of sample code into a single file, one per 1003 paragraph. Add <=for gentest> blocks if you care to, or just look at 1004 f_map and f_sort in ext/B/t/ for examples. 1005 1006 2. run OptreeCheck as a program on the file 1007 1008 ./perl -Ilib ext/B/t/OptreeCheck.pm -w ext/B/t/f_map 1009 ./perl -Ilib ext/B/t/OptreeCheck.pm -w ext/B/t/f_sort 1010 1011 gentest reads the sample code, runs each to generate a reference 1012 rendering, folds this rendering into an optreeCheck() statement, 1013 and prints it to stdout. 1014 1015 3. run the output file as above, redirect to files, then rerun on 1016 same build (for sanity check), and on thread-opposite build. With 1017 editor in 1 window, and cmd in other, it's fairly easy to cut-paste 1018 the gots into the expects, easier than running step 2 on both 1019 builds then trying to sdiff them together. 1020 1021=head1 CAVEATS 1022 1023This code is purely for testing core. While checkOptree feels flexible 1024enough to be stable, the whole selftest framework is subject to change 1025w/o notice. 1026 1027=cut 1028