xref: /openbsd/gnu/usr.bin/perl/ext/B/t/OptreeCheck.pm (revision eac174f2)
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