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