1package t::Common; 2 3use strict; 4use lib qw {blib/lib}, "."; 5use vars qw /@ISA @EXPORT @EXPORT_OK $DEBUG/; 6 7use Regexp::Common; 8use Exporter (); 9 10use warnings; 11 12@ISA = qw /Exporter/; 13@EXPORT = qw /run_tests run_new_tests NORMAL_PASS NORMAL_FAIL FAIL $DEBUG/; 14@EXPORT_OK = qw /cross criss_cross pass fail 15 d pd dd pdd l ll L LL a aa w ww _x xx X XX h hh 16 gimme sample/; 17 18my @STATES = qw /pass fail/; 19 20our $SKIP; 21 22use constant NORMAL_PASS => 0x01; # Normal test, should pass. 23use constant NORMAL_FAIL => 0x02; # Normal test, should fail. 24use constant NORMAL => NORMAL_PASS | NORMAL_FAIL; 25use constant FAIL => 0x04; # Test for failure. 26 27sub run_test; 28sub run_old_keep; 29sub run_fail; 30sub count_me; 31sub is_skipped; 32 33 34my $count; 35 36sub stringify; 37sub stringify { 38 my $arg = shift; 39 40 if (!defined $arg) {return ""} 41 elsif (!ref $arg) {$arg =~ s/\\/\\\\/g; 42 $arg =~ s/\n/\\n/g; 43 $arg =~ s/\t/\\t/g; 44 return "$arg"} 45 elsif ( ref $arg eq "ARRAY") { 46 local $" = ", "; 47 return "[@{[map {q{'} . stringify ($_) . q{'}} @$arg]}]"; 48 } 49 else {return ref $arg} 50} 51 52sub mess { 53 my $str = stringify $_; 54 my $com = join " " => map {stringify $_} @_; 55 $count ++; 56 if ($SKIP) {printf qq !%4d # SKIP: %s\n! => $count, $SKIP;} 57 else {printf qq !%4d - %-40s (%s)\n! => $count, qq !"$str"!, $com;} 58} 59 60sub pass {print "ok "; &mess} 61sub fail {print +$SKIP ? "ok " : "not ok "; &mess} 62 63sub Fail { 64 my $mess = shift; 65 my %args = @_; 66 67 if ($args {got} && $args {expected}) { 68 printf "# Expected: '%s'\n", stringify $args {expected}; 69 printf "# Got: '%s'\n", stringify $args {got}; 70 } 71 72 fail $mess; 73} 74 75 76sub import { 77 if (@_ > 1 && $_ [-1] =~ /^\d+\.\d+$/) { 78 my $version = pop; 79 if ($version > $]) { 80 print "1..1\n"; 81 print "ok 1\n"; 82 exit; 83 } 84 } 85 __PACKAGE__ -> export_to_level (1, @_); 86} 87 88# 89# Return a cross product from its arguments. Arguments are array refs. 90# Result is a list of array refs. 91# 92sub cross { 93 my @r = []; 94 @r = map {my $s = $_; map {[@$_ => $s]} @r} @$_ for @_; 95 @r 96} 97sub criss_cross { 98 my ($f, $s) = @_; 99 my @r; 100 101 push @r => cross @$f [0 .. $_ - 1], $$s [$_], @$f [$_ + 1 .. $#$f] 102 for 0 .. $#$f; 103 104 @r; 105} 106 107sub __ {map {defined () ? $_ : "UNDEF"} @_} 108 109sub count_test_runs { 110 my ($tests, $passes, $failures) = @_; 111 112 my $keep = 0; 113 my $normal = 0; 114 my $fail = 0; 115 116 foreach my $test (@$tests) { 117 while (my ($name, $mask) = each %{$test -> [2]}) { 118 $normal += @{$passes -> {$name}} if $mask & NORMAL; 119 $keep += @{$passes -> {$name}} if $mask & NORMAL_PASS; 120 $fail += @{$failures -> {$name}} if $mask & FAIL; 121 } 122 } 123 124 1 + $normal + $keep + $fail; 125} 126 127# Arguments: 128# tests: hash ref with the re's, names, and when to (not)match. 129# good: ref to array with arrays, parts making patterns. 130# bad: ref to array with arrays, parts not making patterns. 131# query: code ref, creates query strings. 132# wanted: code ref, creates list what keep should return. 133# 134# Filter arguments are used to filter chunks before trying them. 135# All of them are code refs. 136# filter: filter everything. 137# filter_passes: filter passes. 138# filter_failures: filter failures. 139# filter_test: filter called with testname. 140sub run_tests { 141 my %args = @_; 142 143 my $tests = $args {tests}; 144 145 # Collect the names of all tags. 146 my %tag_names; 147 @tag_names {keys %{$_ -> [2]}} = () foreach @$tests; 148 149 my (@passes, @failures); 150 151 if ($args {good}) { 152 @passes = cross @{$args {good}}; 153 154 @failures = (); 155 foreach my $i (0 .. $#{$args {good}}) { 156 push @failures => cross @{$args {good}} [0 .. $i - 1], 157 $args {bad} [$i], 158 @{$args {good}} [$i + 1 .. $#{$args {good}}] 159 } 160 } 161 elsif ($args {good_list}) { 162 @passes = @{$args {good_list}}; 163 } 164 165 # General filters. 166 @passes = grep {$args {filter_passes} -> ($_)} @passes 167 if defined $args {filter_passes}; 168 @passes = grep {$args {filter} -> ($_)} @passes 169 if defined $args {filter}; 170 171 @failures = grep {$args {filter_failures} -> ($_)} @failures 172 if defined $args {filter_failures}; 173 @failures = grep {$args {filter} -> ($_)} @failures 174 if defined $args {filter}; 175 176 my (%passes, %failures); 177 # Specific filters. 178 if (defined $args {filter_test}) { 179 foreach my $name (keys %tag_names) { 180 $passes {$name} = [grep {$args {filter_test} -> 181 (pass => $name, $_)} @passes]; 182 $failures {$name} = [grep {$args {filter_test} -> 183 (failure => $name, $_)} @failures]; 184 } 185 } 186 else { 187 foreach my $name (keys %tag_names) { 188 $passes {$name} = [@passes]; 189 $failures {$name} = [@failures]; 190 } 191 } 192 193 my $runs = count_test_runs $tests, \%passes, \%failures; 194 print "1..$runs\n"; 195 196 print "ok ", ++ $count, "\n"; 197 198 my @test_names = map {$_ -> [1]} @$tests; 199 my @tag_names = keys %tag_names; 200 201 my $wanted = $args {wanted}; 202 foreach my $test (@$tests) { 203 my ($name, $re, $matches) = @$test; 204 205 while (my ($tag, $match) = each %$matches) { 206 if ($match & NORMAL) { 207 foreach my $pass (@{$passes {$tag}}) { 208 local $_ = $args {query} -> ($tag => $pass); 209 210 run_test re => $re, 211 name => $name, 212 match => $match & NORMAL_PASS; 213 214 run_old_keep re => $re, 215 name => $name, 216 tag => $tag, 217 parts => $pass, 218 wanted => $wanted if $match & NORMAL_PASS; 219 } 220 } 221 if ($match & FAIL) { 222 foreach my $failure (@{$failures {$tag}}) { 223 local $_ = $args {query} -> ($tag => $failure); 224 225 run_fail re => $re, 226 name => $name; 227 } 228 } 229 } 230 } 231} 232 233 234 235 236sub run_test { 237 my %args = @_; 238 239 my $re = $args {re}; 240 my $name = $args {name}; 241 my $should_match = $args {match}; 242 243 my $match = /^$re/; # Not anchored at the end on purpose. 244 my $good = $match && $_ eq $&; 245 my $line = $good ? "match" : $match ? "wrong match (got: $&)" : "no match"; 246 $line .= "; $name"; 247 if ($should_match) {$good ? pass $line : fail $line} 248 else {$match ? fail $line : pass $line} 249} 250 251sub array_cmp { 252 my ($a1, $a2) = @_; 253 return 0 unless @$a1 eq @$a2; 254 foreach my $i (0 .. $#$a1) { 255 # !defined $$a1 [$i] && !defined $$a2 [$i] || 256 # defined $$a1 [$i] && defined $$a2 [$i] && $$a1 [$i] eq $$a2 [$i] 257 (!defined $$a1 [$i] || $$a1 [$i] eq "") && 258 (!defined $$a2 [$i] || $$a2 [$i] eq "") || 259 defined $$a1 [$i] && defined $$a2 [$i] && $$a1 [$i] eq $$a2 [$i] 260 or return 0; 261 } 262 return 1; 263} 264 265sub run_old_keep { 266 my %args = @_; 267 268 my $re = $args {re}; # Regexp that's being tried. 269 my $name = $args {name}; # Name of the test. 270 my $tag = $args {tag}; # Tag to pass to wanted sub. 271 my $parts = $args {parts}; # Parts to construct string from. 272 my $wanted_sub = $args {wanted}; # Sub to contruct wanted array from. 273 274 my @chunks = /^$re->{-keep}$/; 275 unless (@chunks) {fail "no match; $name - keep"; return} 276 277 my $wanted = $wanted_sub -> ($tag => $parts); 278 279 local $" = ", "; 280 array_cmp (\@chunks, $wanted) 281 ? pass "match; $name - keep" 282 : $DEBUG ? fail "wrong match,\n# got [@{[__ @chunks]}]\n" . 283 "# expected [@{[__ @$wanted]}]" 284 : fail "wrong match [@{[__ @chunks]}]" 285} 286 287################## 288# # 289# New style subs # 290# # 291################## 292 293# 294# Messages printed at end are of the form: 295# [XX/Y/ZZ], with XX denoting the type of match, Y the expected result, 296# and ZZ the result. 297# 298# XX: - RE: Regular expression 299# - SB: Subroutine call 300# - OM: OO -> match 301# - OS: OO -> subs 302# - KP: Regular expression with -keep 303# 304# Y: - P: Expected to pass 305# - F: Expected to fail 306# 307# ZZ: - MT: Pattern matched correctly 308# - NM: Pattern did not match 309# - WM: Pattern matched, but incorrectly. 310 311 312# 313# Given a regex and a string, test whether the regex fails to match. 314# Matching anything other than the entire string is a pass (as it regex 315# fails to match the entire string) 316# 317sub run_fail { 318 my %args = @_; 319 320 my $re = $args {re}; 321 my $name = $args {name}; 322 323 /^$re/ && $_ eq $& ? fail "[RE/F/MT] $name" 324 : pass "[RE/F/NM] $name"; 325} 326 327 328# 329# Same as 'run_fail', except now not a regex, but a subroutine is given. 330# 331sub run_sub_fail { 332 my %args = @_; 333 334 my $sub = $args {sub}; 335 my $name = $args {name}; 336 my @args = $args {sub_args} ? ref $args {sub_args} ? @{$args {sub_args}} 337 : $args {sub_args} 338 : (); 339 340 $_ =~ $sub -> (@args) && $_ eq $& ? fail "[SB/F/MT] $name" 341 : pass "[SB/F/NM] $name"; 342} 343 344# 345# We can test whether it matched, but we can't really test whether 346# it matched the entire string. $& relates to the last successful 347# match in the current scope, but the match done in $re -> matches() 348# is done in a subscope. @-/@+ are equally useless. 349# 350sub run_OO_pass { 351 my %args = @_; 352 353 my $re = $args {re}; 354 my $name = $args {name}; 355 356 my $match = $re -> matches ($_); 357 358 if ($match) {pass "[OM/P/MT] $name"} 359 else {fail "[OM/P/NM] $name"} 360 361} 362 363 364# 365# Test whether the subroutine gives the right answer. 366# 367sub run_sub_pass { 368 my %args = @_; 369 370 my $sub = $args {sub}; 371 my $name = $args {name}; 372 my @args = $args {sub_args} ? ref $args {sub_args} ? @{$args {sub_args}} 373 : $args {sub_args} 374 : (); 375 376 my $match = $_ =~ $sub -> (@args); 377 my $good = $match && $_ eq $&; 378 379 if ($good) {pass "[SB/P/MT] $name"} 380 elsif ($match) {Fail "[SB/P/WM] $name", got => $&, expected => $_} 381 else {fail "[SB/P/NM] $name"} 382} 383 384 385# 386# Check whether the substitution (only for OO) works correctly. 387# 388sub run_OO_substitution_pass { 389 my %args = @_; 390 391 my $re = $args {re}; 392 my $name = $args {name}; 393 my $token = $args {token} || "---"; 394 395 my $sub = $re -> subs ($_, $token); 396 my $good = $sub eq $token; 397 398 if ($good) {pass "[OS/P/MT] $name"} 399 elsif ($sub ne $_) {Fail "[OS/P/NM] $name", got => $sub, expected => $token} 400 else {fail "[OS/P/WM] $name"} 401} 402 403 404sub run_pass { 405 my %args = @_; 406 407 my $re = $args {re}; 408 my $name = $args {name}; 409 410 my $match = /^$re/; # Not anchored at the end on purpose. 411 my $good = $match && $_ eq $&; 412 my $perfect = $good && !defined $1; # Should *not* set $1 and friends. 413 414 if ($perfect) {pass "[RE/P/MT] $name"} 415 elsif ($good) {fail "[RE/P/MT], sets \$1; $name"} 416 elsif ($match) {Fail "[RE/P/WM] $name", got => $&, expected => $_} 417 else {fail "[RE/P/NM] $name"} 418} 419 420 421sub run_keep { 422 my %args = @_; 423 424 my $re = $args {re}; # Regexp that's being tried. 425 my $name = $args {name}; # Name of the test. 426 my $wanted = $args {wanted}; # Wanted list. 427 428 my @chunks = /^$re->{-keep}/; 429 unless (@chunks) {fail "[KP/P/NM] $name"; return} 430 431 array_cmp (\@chunks, $wanted) 432 ? pass "[KP/P/MT] $name" 433 : Fail "[KP/P/WM] $name", got => \@chunks, expected => $wanted; 434} 435 436sub get_args { 437 my $key = shift; 438 foreach my $ref (@_) { 439 next unless exists $$ref {$key}; 440 return ref $$ref {$key} eq 'ARRAY' ? @{$$ref {$key}} : $$ref {$key} 441 } 442 return; 443} 444 445sub run_new_test_set { 446 my %args = @_; 447 448 my $test_set = $args {test_set}; 449 my $targets = $args {targets}; 450 my $name = $$test_set {name}; 451 my $regex = $$test_set {regex} || $$test_set {re}; # Getting tired of 452 # getting this wrong. 453 my $sub = $$test_set {sub}; 454 my $sub_args = $$test_set {sub_args}; 455 my $keep = $regex -> {-keep}; 456 457 my $pass = $$test_set {pass}; 458 my $fail = $$test_set {fail}; 459 460 my $skip_sub = $$test_set {skip_sub}; 461 462 # 463 # Run the passes. 464 # 465 foreach my $target_info (@$pass) { 466 my $target_name = $$target_info {name}; 467 my $query = $$targets {$target_name} {query}; 468 next unless $$targets {$target_name} {list} && 469 @{$$targets {$target_name} {list}}; 470 my $un_seen = @{$$targets {$target_name} {list}}; 471 my $samples = count_me $$targets {$target_name} {list}, 472 $$target_info {limit}, 473 $$test_set {limit}; 474 foreach my $parts (@{$$targets {$target_name} {list}}) { 475 next unless $samples > rand $un_seen --; 476 $samples --; 477 478 # 479 # Calculate the sections we're going to skip. 480 # 481 my %skips; 482 foreach my $skip (qw /RE SB OO OM OS KP/) { 483 $skips {$skip} = is_skipped $skip => $target_info, $test_set; 484 } 485 $skips {OM} ||= $skips {OO}; 486 $skips {OS} ||= $skips {OO}; 487 488 # 489 # Find the thing we need to match against. 490 # Note that we're going to match against $_. 491 # 492 my @args = ref $parts ? @$parts : $parts; 493 my @qargs = get_args query_args => $target_info, $test_set; 494 local $_ = $query ? $query -> (@qargs, @args) : 495 ref $parts ? join "" => @$parts : $parts; 496 497 # 498 # See whether we want to skip the test 499 # 500 local $SKIP = $skip_sub && $skip_sub -> (pass => $_); 501 502 # 503 # Find out the things {-keep} should return. 504 # The thing we match agains is in $_. 505 # 506 my @wanted; 507 unless ($skips {KP}) { 508 my @wargs = get_args wanted_args => $target_info, $test_set; 509 my $w_sub = $$target_info {wanted} || 510 $$targets {$target_name} {wanted}; 511 @wanted = $w_sub ? $w_sub -> (@wargs, @args) : $_; 512 if (@wanted == 1 && ref $wanted [0] eq "ARRAY") { 513 @wanted = @{$wanted [0]}; 514 } 515 } 516 517 run_pass name => $name, 518 re => $regex unless $skips {RE}; 519 run_OO_pass name => $name, 520 re => $regex unless $skips {OM}; 521 run_OO_substitution_pass name => $name, 522 re => $regex unless $skips {OS}; 523 run_sub_pass name => $name, 524 sub_args => $sub_args, 525 sub => $sub if $sub && !$skips {SB}; 526 run_keep name => $name, 527 re => $keep, 528 wanted => \@wanted unless $skips {KP}; 529 } 530 } 531 532 # 533 # Run the failures. 534 # 535 foreach my $target_info (@$fail) { 536 my $target_name = $$target_info {name}; 537 my $query = $$targets {$target_name} {query}; 538 next unless $$targets {$target_name} {list} && 539 @{$$targets {$target_name} {list}}; 540 my $un_seen = @{$$targets {$target_name} {list}}; 541 my $samples = count_me $$targets {$target_name} {list}, 542 $$target_info {limit}, 543 $$test_set {limit}; 544 foreach my $parts (@{$$targets {$target_name} {list}}) { 545 next unless $samples > rand $un_seen --; 546 $samples --; 547 548 my @args = ref $parts ? @$parts : $parts; 549 my @qargs = get_args query_args => $target_info, $test_set; 550 local $_ = $query ? $query -> (@qargs, @args) 551 : ref $parts ? join "" => @$parts : $parts; 552 553 local $SKIP = $skip_sub && $skip_sub -> (fail => $_); 554 555 my %skips; 556 foreach my $skip (qw /RE SB/) { 557 $skips {$skip} = is_skipped $skip => $target_info, $test_set; 558 } 559 560 run_fail name => $name, 561 re => $regex unless $skips {RE}; 562 run_sub_fail name => $name, 563 sub_args => $sub_args, 564 sub => $sub if $sub && !$skips {SB}; 565 } 566 } 567} 568 569# 570# If there's no list, or an empty list, 0 tests have to be run. 571# If no limits are given, return the size of the list. 572# Else, for the first defined limit, 573# if the limit is negative, return the size of the list, 574# else if the limit is 0, return 0, 575# else if the limit is less than 1, treat it as a fraction, 576# else, return the smaller of the limit and the size of the list. 577# 578sub count_me { 579 my ($list, @limits) = @_; 580 581 return 0 unless $list && @$list; 582 foreach my $limit (@limits) { 583 if (defined $limit) { 584 return @$list if $limit < 0; 585 return int (@$list * $limit) if $limit < 1; 586 return $limit if $limit < @$list; 587 return @$list; 588 } 589 } 590 @$list; 591} 592 593 594# 595# Normify any 'pass','fail' and 'skip' entries in a test. 596# What we want is a 'pass' and a 'fail' pointing to an array of hashes, 597# each hash being a 'target'. 598# 599# Since we are passed a reference, the modification is done in situ. 600# 601sub normify { 602 my $test = shift; 603 foreach my $state (@STATES) { 604 my @list; 605 606 foreach my $postfix ("", "_arg") { 607 my $key = "$state$postfix"; 608 next unless exists $$test {$key}; 609 my $targets = $$test {$key}; 610 if (ref $targets eq 'ARRAY') { 611 foreach my $thingy (@$targets) { 612 if (ref $thingy eq 'HASH') { 613 push @list => $thingy; 614 } 615 elsif (!ref $thingy) { 616 push @list => {name => $thingy} 617 } 618 } 619 } 620 elsif (ref $targets eq 'HASH') { 621 push @list => $targets; 622 } 623 else { 624 push @list => {name => $targets}; 625 } 626 delete $$test {$key}; 627 } 628 629 $$test {$state} = \@list; 630 } 631 632 # 633 # Skips. 634 # 635 if (!exists $$test {skip}) {$$test {skip} = {}} 636 elsif (ref $$test {skip} eq 'ARRAY') { 637 $$test {skip} = {map {$_ => 1} @{$$test {skip}}} 638 } 639 640 foreach my $state (@STATES) { 641 foreach my $target (@{$$test {state}}) { 642 if (!exists $$target {skip}) {$$target {skip} = {}} 643 elsif (ref $$target {skip}) { 644 $$target {skip} = {map {$_ => 1} @{$$target {skip}}} 645 } 646 } 647 } 648} 649 650sub is_skipped { 651 my ($type, @things) = @_; 652 foreach my $thingy (@things) { 653 return $$thingy {skip} {$type} if defined $$thingy {skip} {$type}; 654 } 655 return; 656} 657 658sub mult { 659 my ($state, $has_sub, @things) = @_; 660 661 my $mult; 662 663 # Regular expression test. 664 $mult ++ unless is_skipped RE => @things; 665 666 # Subroutine check. 667 $mult ++ if $has_sub && !is_skipped SB => @things; 668 669 if ($state eq "pass") { 670 # OO checks. 671 $mult ++ unless is_skipped OO => @things or is_skipped OM => @things; 672 $mult ++ unless is_skipped OO => @things or is_skipped OS => @things; 673 # Keep check. 674 $mult ++ unless is_skipped RE => @things or is_skipped KP => @things; 675 } 676 677 return $mult; 678} 679 680sub run_new_tests { 681 my %args = @_; 682 683 my ($tests, $targets, $version, $version_from, 684 $extra_runs, $extra_runs_sub) = 685 @args {qw /tests targets version version_from 686 extra_runs extra_runs_sub/}; 687 688 # 689 # Modify any 'pass' and 'fail' entries to arrays of hashes. 690 # 691 foreach my $test (@$tests) { 692 normify $test; 693 } 694 695 # 696 # Count the number of runs. 697 # 698 my $runs = defined $version_from; # VERSION test. 699 my $no_tests; 700 if ($extra_runs) { 701 $runs += $extra_runs; 702 $count += $extra_runs; 703 } 704 705 if (defined $version && $version > $]) { 706 $no_tests = 1; 707 } 708 else { 709 # Count the tests to be run. 710 foreach my $test (@$tests) { 711 # Test: pass: regex, regex/keep, OO, OO-substitution, sub (if given) 712 # fail: regex, sub (if given). 713 my $has_sub = $$test {sub} ? 1 : 0; 714 715 for my $state (@STATES) { 716 foreach my $target (@{$$test {$state}}) { 717 my $size = count_me $$targets {$$target {name}} {list}, 718 $$target {limit}, 719 $$test {limit}; 720 $runs += $size * mult $state, $has_sub => $target, $test; 721 } 722 } 723 } 724 } 725 726 print "1..$runs\n"; 727 728 # Check whether a version is defined. 729 if (defined $version_from) { 730 print "ok ", ++ $count, "\n"; 731 } 732 733 if ($extra_runs_sub) { 734 $extra_runs_sub -> (\$count) 735 } 736 737 unless ($no_tests) { 738 foreach my $test (@$tests) { 739 run_new_test_set test_set => $test, 740 targets => $targets; 741 } 742 } 743} 744 745# 746# Function to produce random strings. 747# 748 749# Digit. 750sub d {int rand 10} 751# Positive digit. 752sub pd {1 + int rand 9} 753# String of digits. 754sub dd {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 755 join "" => map {d} 1 .. $min + int rand ($max - $min)} 756# String of digits, not all 0. 757sub pdd {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 758 TRY: my $dd = join "" => map {d} 1 .. $min + int rand ($max - $min); 759 goto TRY unless $dd =~ /[^0]/; 760 $dd} 761# Lowercase letter. 762sub l {chr (ord ('a') + int rand 26)} 763# String of lowercase letters. 764sub ll {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 765 join "" => map {l} 1 .. $min + int rand ($max - $min)} 766# Uppercase letter. 767sub L {chr (ord ('a') + int rand 26)} 768# String of uppercase letters. 769sub LL {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 770 join "" => map {L} 1 .. $min + int rand ($max - $min)} 771# Alpha. 772sub a {50 < rand (100) ? l : L} 773# String of alphas. 774sub aa {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 775 join "" => map {a} 1 .. $min + int rand ($max - $min)} 776# Alphanum. 777sub w {52 < rand (62) ? d : a} 778# String of alphanums. 779sub ww {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 780 join "" => map {w} 1 .. $min + int rand ($max - $min)} 781# Lowercase hex digit. 782sub _x {(0 .. 9, 'a' .. 'f') [int rand 16]} 783# String of lowercase hex digits. 784sub xx {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 785 join "" => map {_x} 1 .. $min + int rand ($max - $min)} 786# Uppercase hex digit. 787sub X {(0 .. 9, 'A' .. 'F') [int rand 16]} 788# String of uppercase hex digits. 789sub XX {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 790 join "" => map {X} 1 .. $min + int rand ($max - $min)} 791# Any case hex digit 792sub h {(0 .. 9, 'A' .. 'F', 'a' .. 'f') [int rand 22]} 793# String of anycase hex digits 794sub hh {my ($min, $max) = @_ > 1 ? (@_) : ($_ [0], $_ [0]); 795 join "" => map {h} 1 .. $min + int rand ($max - $min)} 796 797 798# 799# Pass a number N and a callback C. Return N different results from C. 800# Will do at most 100 * N tries. 801# 802sub gimme { 803 my ($count, $call) = @_; 804 my %cache; 805 foreach (1 .. 100 * $count) { 806 $cache {$call -> ()} = 1; 807 last if keys %cache >= $count; 808 } 809 keys %cache; 810} 811 812# 813# Given a number N, and a list of things, return a sample of N 814# 815sub sample { 816 my $N = shift; 817 return @_ if @_ <= $N; 818 819 my @cache = splice @_ => 0, $N; 820 my $count = $N; 821 map {rand ++ $count < $N and splice @cache, rand @cache, 1, $_} @_; 822 823 @cache; 824} 825 826 827 8281; 829 830__END__ 831 832=head1 DESCRIPTION 833 834C<run_new_tests> is called with three (named) parameters: 835 836=over 4 837 838=item C<tests> 839 840A references to an array of I<tests> (explained below). 841 842=item C<targets> 843 844A reference to a hash of I<targets> (explained below). 845 846=item C<version_from> 847 848The name of the file that is checked for a version number. 849 850=back 851 852=head2 Targets 853 854Targets provide a set of strings to match against. Targets are 855indexed by name. Each target is a hash, with the following keys: 856 857=over 4 858 859=item C<list> 860 861Required. This is a reference to an array that will act as building 862blocks to build strings to match against. In the simplest form, this 863is just an array with strings - but typically, this is an array of 864arrays, each subarray used to create a string. 865 866=item C<query> 867 868A coderef. For each entry in array given above, this coderef is called. 869It takes a set of arguments and returns a string to match against. If 870the corresponding entry in C<list> is reference to an array, all its 871elements are passed - otherwise, the entry is passed as a whole. Extra 872arguments provided with C<query_args> below are prepended. If no coderef 873is given, C<sub {$_ [0]}> is assumed. 874 875=item C<wanted> 876 877A coderef. If the target is used for positive matches (that is, it's 878expected to match), this sub is called with the same arguments as C<query> 879- except that C<wanted_args> are prepended. It should return a list of 880strings as if the regular expression was called with C<{-keep}>. The 881string to match against may be assumed to be C<$_>. If no coderef is given, 882C<sub {$_}> is assumed. 883 884=back 885 886=head2 Tests 887 888The tests to run are put in an array, and run in that order. Each test 889tests a specific pattern. Up to seven types of tests are performed, depending 890whether the tests includes expected failures, expected passes or both. 891Expected passes are tested as a regular expression, as a regular expression 892with the C<{-keep}> option, as a subroutine, as an object using the C<match> 893method, and as an object using the C<subs> method. Expected failures are 894tested as a regular expression, and as a subroutine. Each test is a hash 895with the following keys: 896 897=over 4 898 899=item C<name> 900 901The name of this test - mostly used in the test output. 902 903=item C<regex> 904 905The pattern to test with. 906 907=item C<sub> 908 909The subroutine to test with, if any. 910 911=item C<sub_args> 912 913Any arguments that need to be passed into the subroutine. If more than 914one argument needs to be passed, use a reference to an array - the array 915will be flattened when calling the subroutine. 916 917=item C<query_args> 918 919Extra arguments to pass into the C<query> coderef for all the targets 920belonging to this tests, if not overriden as discussed below. 921 922=item C<wanted_args> 923 924Extra arguments to pass into the C<wanted> coderef for all the targets 925belonging to this tests, if not overriden as discussed below. 926 927=item C<pass> 928 929Indicates which targets (discussed above) should be run with expected 930passes. The value of C<pass> is either a reference to an array - the 931array containing the names of the targets to run, or a reference to a 932hash. In the latter case, the keys are the targets to be run, while the 933keys are hash references, containing more configuration options for the 934target. Values allowed: 935 936=over 4 937 938=item C<query_args> 939 940Extra arguments to pass into the C<query> coderef belonging to this test. 941See discussion above. 942 943=item C<wanted_args> 944 945Extra arguments to pass into the C<wanted> coderef belonging to this test. 946See discussion above. 947 948=back 949 950=item C<fail> 951 952As C<pass>, except that it will list targets with an expected failure. 953 954=back 955