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