#
# Regression test support for OOPS
#
package OOPS::OOPS1004::TestCommon;
@EXPORT = qw($debug
$r1 $r2
$fe
$okay
$ocut
$bbs
$basecount
$dbms
$tcTODO
notied
nodata
test
samesame
docompare
qcheck
showpos
max
rowcount
resetall
groupmangle
check_refcount
nocon
rcon
rcon12
nukevar
runtests
runtest
russian_roulette
wa
db_drop
supercross1
supercross7
rvsamesame
ref2string
modern_data_compare
check_resources
safeout
betterkeys
$test_dsn
$test_user
$test_pass
$multiread
$Npossible
$Nvert
$prefix
%args
);
@ISA = qw(Exporter);
BEGIN {
for my $m (qw(Data::Compare Clone::PP BSD::Resource)) {
unless ( eval " require $m " ) {
print "1..0 # Skipped: this test requires the $m module\n";
exit;
}
$m->import();
}
}
use OOPS::OOPS1004;
use OOPS::OOPS1004::Setup;
import Clone::PP qw(clone);
use Carp::Heavy; # weird error sometimes w/o this
use Carp qw(confess);
use Scalar::Util qw(reftype refaddr);
use Data::Dumper;
use strict;
require Exporter;
select(STDOUT);
$| = 1;
our $debug = ! $ENV{HARNESS_ACTIVE};
print STDERR "# Debugging on\n" if $debug;
our $multiread = 0;
our $test_dsn;
our $tcTODO;
BEGIN {
$test_dsn = $ENV{OOPSTEST_DSN};
unless ($test_dsn || eval { require DBD::SQLite } ) {
print "1..0 # Skipped: this test requires \$OOPSTEST_DSN to be set or DBD::SQLite to be installed\n";
exit 0;
}
}
our $test_user = $ENV{OOPSTEST_USER};
our $test_pass = $ENV{OOPSTEST_PASS};
unless ($test_dsn) {
# we know we've got SQLite..
$test_dsn = "DBI:SQLite:dbname=/tmp/OOPStest.$$.db"
}
our (%args) = (
dbi_dsn => $test_dsn,
user => $test_user,
password => $test_pass,
table_prefix => $ENV{OOPSTEST_PREFIX} || "TEST",
no_front_end => 1,
);
$args{default_synchronous} = 'OFF' if $test_dsn =~ /^DBI:SQLite:/i;
our $fe;
our ($r1, $r2);
our $prefix;
our $okay = 1;
$Data::Dumper::Sortkeys = 1;
$Data::Dumper::Useperl = 1;
$Data::Dumper::Purity = 1;
delete $SIG{__WARN__};
delete $SIG{__DIE__};
our $basecount;
our $dbms = OOPS::OOPS1004->initial_setup(%args) || confess;
$OOPS::OOPS1004::bigcutoff = 50;
$OOPS::OOPS1004::sqlite::big_blob_size = 60;
our $ocut = $OOPS::OOPS1004::bigcutoff;
our $bbs = $OOPS::OOPS1004::sqlite::big_blob_size;
sub eline
{
my $i = 0;
my $l = '';
for (;;) {
my ($package, $filename, $line) = caller($i++);
$l .= "/" if $l;
$l .= $line;
return($debug ? $l : $line) if $filename ne __FILE__;
return '???' unless $filename;
}
}
sub safeout
{
my $x = join('', @_);
$x =~ s/\n([^\#])/\n#$1/g;
print STDERR $x;
}
sub rvnotied
{
my $bad = 0;
for my $i (keys %OOPS::OOPS1004::tiedvars) {
next if $i eq $r1;
next if $i eq $r2;
next if $r1 && $i eq tied %{$r1->{named_objects}};
next if $r2 && $i eq tied %{$r2->{named_objects}};
$bad++;
print "TIED $i FROM $OOPS::OOPS1004::tiedvars{$i}\n" if $debug;
}
return $bad;
}
sub notied
{
my ($msg, $line) = @_;
unless ($OOPS::OOPS1004::debug_tiedvars) {
print "ok $okay # Skip \$OOPS::OOPS1004::debug_tiedvars not set\n";
$okay++;
return;
}
my $bad = rvnotied();
$line ||= eline;
my $pm = $msg || '';
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
print "not " unless $bad == 0;
print "ok $okay - line$line # $pm - $bad tied variables\n";
$okay++;
russian_roulette($bad);
%OOPS::OOPS1004::tiedvars = ();
}
sub russian_roulette
{
my ($chambered, $msg) = @_;
return unless $chambered;
return unless $debug;
print $msg if defined $msg;
print "\nBail out! ... early so the error doesn't scroll away\n";
print Carp::longmess();
select(STDOUT);
$| = 1;
print "";
kill(9,$$);
}
sub nodata
{
my ($msg) = @_;
return unless defined $basecount;
my $count = rowcount();
my $line = eline;
print "not " unless $count eq $basecount;
my $pm = $msg || '';
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
$pm = "database is empty" unless $pm;
print "ok $okay - line$line # $pm\n";
$okay++;
russian_roulette($count ne $basecount, "basecount: $basecount\nnewcount: $count\n");
}
sub todo_test
{
my ($true, $msg) = @_;
my $line = eline;
my $pm = $msg || '';
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
print "not " unless $true;
print "ok $okay - line$line # TODO $pm\n";
$okay++;
}
sub test
{
my ($true, $msg) = @_;
my $line = eline;
my $todo = $tcTODO
? "TODO ($tcTODO) "
: "";
my $pm = $msg || '';
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
print "not " unless $true;
print "ok $okay - line$line # $todo$pm\n";
$okay++;
russian_roulette(! $true)
unless $tcTODO;
}
sub rvsamesame
{
my ($o1, $o2, $msg) = @_;
my $x1 = Dumper($o1);
my $x2 = Dumper($o2);
$msg .= "\n" if $msg && $msg !~ /\n$/;
$msg = '' unless defined $msg;
return "x1=$x1\nx2=$x2\n$msg"
if $x1 ne $x2;
return undef;
}
sub samesame
{
my ($o1, $o2, $msg) = @_;
my $err = rvsamesame($o1, $o2);
my $line = eline;
print "not " if $err;
my $pm = $msg || '';
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
print "ok $okay - line$line # $pm\n";
$okay++;
russian_roulette($err, $err);
}
# http://search.cpan.org/~fdaly/Test-Deep-0.084/lib/Test/Deep.pod
# http://search.cpan.org/~mgraham/Palm-Progect-2.0.1/mlib/Test/More.pm
# http://search.cpan.org/~pdenis/Data-Structure-Util-0.09/lib/Data/Structure/Util.pm
# http://search.cpan.org/~ilyaz/etext.1.6.3/eText/utils/FreezeThaw.pm
# http://search.cpan.org/~xmath/Data-XDumper-1.03/XDumper.pm
# http://search.cpan.org/~jzucker/SQL-Statement-1.005/lib/SQL/Parser.pm
# http://search.cpan.org/~gaas/Data-Dump-1.02/lib/Data/Dump.pm
# http://search.cpan.org/~clkao/Data-Hierarchy-0.17/Hierarchy.pm
# http://search.cpan.org/~jbrown/PHP-Serialization-0.27/lib/PHP/Serialization.pm
# http://search.cpan.org/~ingy/Data-Denter-0.15/Denter.pod
# http://search.cpan.org/~ingy/YAML-0.30/YAML.pod
sub docompare
{
my ($x, $y) = @_;
my $r = Data::Compare::Compare($x, $y);
return $r if $r;
# my $x1 = Dumper($x);
# my $x2 = Dumper($y);
# return 1 if $x1 eq $x2;
# safeout "# x1=$x1\nx2=$x2\n" if $debug;
#
# require YAML;
# my $y1 = YAML::Dump($x);
# my $y2 = YAML::Dump($y);
# return 1 if $y1 eq $y2;
# safeout "# y1=\n$y1\ny2=\n$y2\n" if $debug;
#
# require Data::Dump;
# my $b1 = Data::Dump::dump($x);
# my $b2 = Data::Dump::dump($y);
# return 1 if $b1 eq $b2;
# safeout "# b1=$b1\nb2=$b2\n" if $debug;
#
my $c1 = ref2string($x);
my $c2 = ref2string($y);
return 1 if $c1 eq $c2;
safeout "# c1=$c1\n# c2=$c2\n" if $debug;
#
# use Data::XDumper;
# my $z1 = Data::XDumper::Dump($x);
# my $z2 = Data::XDumper::Dump($y);
# safeout "# z1=$z1\nz2=$z2\n" if $debug;
return 0;
}
sub modern_data_compare
{
# 0.02 bad, 0.10 good, don't know about in-betwen
return 1 if $Data::Compare::VERSION >= 0.10;
print "1..0 # Skipped: Data::Compare too out-of-date, may recurse forever\n";
exit 0;
}
our $resource_check_magic_number = 100;
our $resource_check_count;
our $resource_check_first;
our $resource_check_last;
our $resource_check_last_okay;
our $resource_check_okay;
sub check_resources
{
my (undef, undef, $maxrss) = getrusage(RUSAGE_SELF);
if ($resource_check_count++ == $resource_check_magic_number) {
$resource_check_okay = $okay;
$resource_check_first = $maxrss;
}
if ($resource_check_first && $maxrss > $resource_check_last) {
printf "# memory leaked since last resrouce check: %d\n",
$maxrss - $resource_check_last;
}
$resource_check_last = $maxrss;
$resource_check_last_okay = $okay;
}
END {
if ($resource_check_first) {
printf "# Memory leaked between test $resource_check_last_okay and $resource_check_okay: %d",
$resource_check_last - $resource_check_first;
}
}
END {
print "# DBI bug workaround invoked: $OOPS::OOPS1004::dbi_bug_workaround_count_debug\n"
if $OOPS::OOPS1004::dbi_bug_workaround_count_debug;
}
sub qcheck
{
my ($q, $picture, @extra) = @_;
$q =~ s/TP_/$args{table_prefix}/g;
confess "pict:'$picture'" unless $picture =~ /\A(\s+)\+([-+]+)\+\n/;
my $ws = $1;
my $fp = $2;
my (@fl) = map { length($_) - 1} split(/\+/, $fp);
my $wsl = length($ws);
my $pos = ($wsl + length($fp) + 3) * 3;
my %rows;
while ($pos < length($picture)) {
$pos += $wsl + 2;
last if substr($picture, $pos-2, 1) eq '+';
#showpos($pos, $picture);
my @row;
for my $i (0..$#fl) {
my $x = substr($picture, $pos, $fl[$i]);
$x =~ s/^\s+//;
$x =~ s/\s+$//;
push(@row, $x);
$pos += $fl[$i]+2;
}
#print "row = @row\n";
$rows{join(",", @row)}++;
}
my $dbh = OOPS::OOPS1004->dbiconnect(%args) || confess;
my $sth = $dbh->prepare($q) || confess;
$sth->execute(@extra) || confess;
my (@a);
while (@a = $sth->fetchrow_array()) {
$rows{join(",", @a)}--;
}
$sth->finish;
my $ok = 1;
for my $r (keys %rows) {
next unless $rows{$r};
$ok = 0;
next unless $debug;
if ($rows{$r} > 0) {
print STDERR "${okay}missing: $r\n";
} else {
print STDERR "${okay}extra : $r\n";
}
}
my $line = eline;
print "not " unless $ok;
my $pm = $q;
$pm =~ s/\A\s*(.*?)\s*\Z/$1/s;
$pm =~ s/\n\s*/\\n /g;
print "ok $okay - line$line # $pm\n";
$okay++;
$dbh->disconnect;
russian_roulette(! $ok);
}
sub showpos
{
my ($pos, $str) = @_;
my $mb = max(200, $pos, length($str));
my $ma = max(200, length($str)-$pos);
print substr($str, $pos-$mb, $mb);
print "*";
print substr($str, $pos, $ma);
print "\n\n\n";
}
sub max
{
my ($n, @n) = @_;
for my $i (@n) {
$n = $i if $n > $i;
}
return $n;
}
sub rowcount
{
my (@tables) = @_;
my $t = "TP_object TP_attribute TP_big";
$t =~ s/TP_/$args{table_prefix}/g;
@tables = split(" ", $t)
unless @tables;
my $dbh = OOPS::OOPS1004->dbiconnect(%args) || confess;
my $c = 0;
my $s = '';
for my $t (@tables) {
my $sth = eval { $dbh->prepare("select count(*) from $t");};
confess $@ if $@;
$sth->execute() || confess $sth->errstr;
my ($count) = $sth->fetchrow_array();
$c += $count;
$s .= " $t:$count";
$sth->finish();
}
$dbh->disconnect;
return "$c$s";
}
sub db_drop
{
if ($test_dsn =~ m{^DBI:SQLite\d*:dbname=(/tmp/OOPStest.\d+.db)$}) {
unlink($1);
} else {
eval {
OOPS::OOPS1004->db_domany(\%OOPS::OOPS1004::TestCommon::args, <<END);
DROP TABLE TP_object;
DROP TABLE TP_attribute;
DROP TABLE TP_big;
END
};
eval { OOPS::OOPS1004->db_domany(\%OOPS::OOPS1004::TestCommon::args, "DROP TABLE TP_counters") }
unless $dbms eq 'sqlite';
}
}
sub resetall
{
my ($msg, $die, $force) = @_;
my $line = eline;
print "# ---------------------------- reset all ---------------------- $line\n" if $debug;
undef $fe;
undef $r1;
check_refcount($msg, $die)
unless $force;
notied('at reset', $line)
unless $force;
no strict 'refs';
my $x;
for my $t (&{"OOPS::OOPS1004::${dbms}::table_list"}()) {
$x .= "DELETE FROM $t;\n";
}
OOPS::OOPS1004->db_domany({ %args },
$x
. OOPS::OOPS1004->db_initial_values()
. &{"OOPS::OOPS1004::${dbms}::db_initial_values"}());
use strict;
$basecount = rowcount() unless defined $basecount;
nodata();
$r1 = OOPS::OOPS1004->new(%args) || confess;
$fe = OOPS::OOPS1004::FrontEnd->new($r1);
$prefix = $r1->{table_prefix};
}
sub groupmangle
{
my ($action) = @_;
my $r = OOPS::OOPS1004->new(%args) || confess;
my $dbh = $r->{dbh};
my $q;
if ($action eq 'manygroups') {
$q = $dbh->prepare(<<END);
UPDATE `${prefix}object`
SET loadgroup = id
END
} elsif ($action eq 'onegroup') {
$q = $dbh->prepare(<<END);
UPDATE `${prefix}object`
SET loadgroup = 5
WHERE virtual = 0
END
} else {
confess;
}
confess $dbh->errstr unless $q;
$q->execute();
}
sub AUTODISC::DESTROY
{
my $self = shift;
my $dbh = $self->{dbh};
$dbh->disconnect();
}
sub check_refcount
{
my ($msg, $die) = @_;
my $dbh = OOPS::OOPS1004->dbiconnect(%args) || confess;
my $autodisc = bless { dbh => $dbh }, 'AUTODISC';
my $error = 0;
my $aq = "SELECT pval, count(*) FROM TP_attribute WHERE ptype = 'R' GROUP BY pval";
$aq =~ s/TP_/$args{table_prefix}/g;
my $rq = "SELECT id, refs FROM TP_object";
$rq =~ s/TP_/$args{table_prefix}/g;
my $actual = $dbh->prepare($aq) || confess $dbh->errstr;
$actual->execute() || confess $actual->errstr;
my (%actual, %recorded);
my ($id, $count);
while (($id, $count) = $actual->fetchrow_array()) {
$actual{$id} = $count;
}
$actual->finish;
my $recorded = $dbh->prepare($rq) || confess $dbh->errstr;
$recorded->execute() || confess $recorded->errstr;
while (($id, $count) = $recorded->fetchrow_array()) {
$recorded{$id} = $count;
}
$recorded->finish;
undef $actual;
undef $recorded;
$dbh->disconnect;
my $err;
for $id (keys %actual) {
no warnings qw(uninitialized);
if ($recorded{$id} != $actual{$id}) {
$err .= "reference count of *$id is off: $recorded{$id} recorded, but should be $actual{$id}\n";
$error++;
}
delete $recorded{$id};
}
for $id (keys %recorded) {
$err .= "reference count of *$id is off: $recorded{$id} recorded, but should be 0\n";
$error++;
}
#my $line = (caller(@_ ? $_[0] : 1))[2];
my $line = eline;
$msg ||= '';
$msg =~ s/\A\s*(.*?)\s*\Z/$1/s;
$msg =~ s/\n\s*/\\n /g;
$msg = "- $msg" if $msg;
if ($err) {
print "not ok $okay - line$line # reference counts are off - $error object $msg\n";
confess "$err " if $die;
print $err if $debug;
russian_roulette(1);
} else {
print "ok $okay - line$line # refcount $msg\n";
}
$okay++;
}
sub nocon
{
undef $fe;
undef $r1;
undef $r2;
}
sub rcon
{
my ($msg, $skipref) = @_;
undef $fe;
undef $r1;
undef $r2;
my $line = eline;
print "# ---------------------------- reconnect ---------------------- $line\n" if $debug;
check_refcount($msg)
unless $skipref;
notied('at reset', $line)
unless $skipref;
$r1 = OOPS::OOPS1004->new(%args) || confess;
$fe = OOPS::OOPS1004::FrontEnd->new($r1);
}
sub rcon12
{
rcon(($_[0] || 0)+1);
$r2 = OOPS::OOPS1004->new(%args) || confess;
}
our $Nvert;
sub runtest
{
my ($setup, $modify, $msg, $virts, $looks, @variables) = @_;
my $line = eline;
my $mult = scalar(@$virts) * scalar(@$looks);
my $count = 1;
for my $virt (@$virts) {
for my $look (@$looks) {
my $x = eval {
my $r;
resetall(undef,'die','force');
my %named;
my $n1 = \%named;
$Nvert = 0;
&$setup($n1, @variables);
return "SETUPFAILURE\n" unless %named;
$Nvert = 1;
&$setup($r1->{named_objects}, @variables);
$r1->virtual_object($r1->{named_objects}{root}, 1)
if exists $n1->{root} && $virt eq 'virt1';
if ($look & 0x2) {
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "PRECOMMIT\n$r" if $r;
}
$r1->commit;
if ($look & 0x4) {
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "POSTCOMMIT\n$r" if $r;
}
nocon;
check_refcount(__LINE__, 'die');
rcon(undef, 'skipref');
if ($look & 0x10) {
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "PREMODIFY\n$r" if $r;
}
$Nvert = 0;
&$modify($n1, @variables);
$Nvert = 1;
&$modify($r1->{named_objects}, @variables);
if ($look & 0x1) {
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "POSTMODIFY\n$r" if $r;
}
$r1->commit;
if ($look & 0x8) {
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "PRERECONNECT\n$r" if $r;
}
nocon;
check_refcount(__LINE__, 'die');
rcon(undef, 'skipref');
$r = rvsamesame($n1->{root}, $r1->{named_objects}{root});
return "POSTRECONNECT\n$r" if $r;
nocon;
check_refcount(undef, 'die');
return 0;
};
my $e = $@;
eval { $r1->{dbh}->commit; }; # for debugging
print "not " if $e || $x;
print "ok $okay - line$line # $virt $look $count/$mult - $msg\n";
$okay++;
$count++;
return "EVAL: $e" if $e;
return "ERROR: $x" if $x;
}
}
return 0;
}
our $Npossible = 0;;
#
# runtests($setupfunc, $modifyfunc, $virtarry, $peekarray, @other_vars)
# $setupfunc(\%hash, @other_vars) - function to set things up
# $modifyfunc(\%hash, @other_vars) - function to modify values
# $virtarray - [ qw(virt0 virt1) ]
# $peekarray - [ 0..31 ]'
# @other_vars - multiple array slices of subtests
#
sub runtests
{
my ($setup, $modify, @variables) = @_;
my $line = eline;
my $sig = '';
$Npossible = 1;
for my $vi (0..$#variables) {
$Npossible *= scalar(@{$variables[$vi]});
$sig .= scalar(@{$variables[$vi]}) == 1
? "<$variables[$vi][0]>"
: "*".scalar(@{$variables[$vi]});
}
$sig .= "-$Npossible";
my $r = runtest($setup, $modify, $sig, @variables);
return unless $r && $debug;
russian_roulette($Npossible == 1, $r);
print "# $r\n";
my $inv = $#variables;
print "# ATTEMPTING TO CUT DOWN POSSIBILITY SPACE\n";
for my $xx (1..3) {
for my $vi (0..$#variables) {
my (@found) = @{$variables[$vi]};
my @tested;
my @test;
my (@nv) = (@variables[0..($vi-1)], \@test, @variables[($vi+1)..$#variables]);
confess unless $#nv == $inv;
confess if grep (! (ref $_ eq 'ARRAY'), @nv);
for(;;) {
last unless @found > 1;
@test = @found[0..int(($#found)/2)];
confess unless @test;
last unless @test;
my (@untest) = @found[int(($#found)/2)+1 .. $#found];
confess unless @untest;
confess unless $#nv == $inv;
confess if grep (! (ref $_ eq 'ARRAY'), @nv);
$r = runtest($setup, $modify, $line, @nv);
if ($r) {
# winner
#print "a winner at variables[$vi]: '@found': $r\n";
@found = @test;
redo;
}
my (@tmp) = @test;
@test = @untest;
last unless @test;
@untest = @tmp;
confess unless @untest;
confess unless $#nv == $inv;
confess if grep (! (ref $_ eq 'ARRAY'), @nv);
$r = runtest($setup, $modify, $line, @nv);
if ($r) {
# winner
#print "a winner at variables[$vi]: '@found': $r\n";
@found = @test;
redo;
}
last;
}
@variables = (@variables[0..($vi-1)], \@found, @variables[($vi+1)..$#variables]);
confess unless $#variables == $inv;
confess if grep (! (ref $_ eq 'ARRAY'), @variables);
}
}
print "----------------------------- simplest run ---------------------------------------------\n";
$r = runtest($setup, $modify, $line, @variables);
print $r;
print "line$line\n\nrussian_roulette(map { \$_, \$_ } runtests(\$t1, \$t2";
for my $vi (0..$#variables) {
print ", [qw( ".join(' ',@{$variables[$vi]})." )]";
}
print "));\n";
russian_roulette(1);
}
sub ref2string
{
my $r = shift;
my $str = '';
bad_dump(\$str, $r);
return $str;
}
sub bad_dump
{
my($sr,$x,$indent,%done) = @_;
my($y);
$indent = 0 unless defined $indent;
if (ref $x) {
$$sr .= "(noting '$x')" if $main::xy;
if ($done{refaddr($x)}++) {
$$sr .= '<CIRCULAR VALUE>';
return;
}
}
my $n;
if (ref $x) {
if (ref($x) ne reftype($x)) {
$$sr .= "(" if $indent;
$$sr .= "bless ";
}
if (reftype($x) eq 'HASH') {
$$sr .= "{";
$n = "\n";
for $y (sort keys %$x) {
$$sr .= $n." ".(" "x$indent);
if (ref $y) {
&bad_dump($sr, $y, $indent+1, %done);
} else {
$$sr .= "e($y);
}
$$sr .= " => ";
&bad_dump($sr,$x->{$y},$indent+1, %done);
$n = ",\n";
}
if ($n ne "\n") { # {
$$sr .= "\n".(" "x$indent)."}";
} else { # {
$$sr .= "}";
}
} elsif (reftype($x) eq 'ARRAY') {
$$sr .= "[";
$n = "\n";
for ($y = 0; $y <= $#{$x}; $y++) {
$$sr .= "$n ".(" "x$indent);
&bad_dump($sr,${$x}[$y],$indent+1, %done);
$n = ", # $y\n";
}
if ($n ne "\n") {
$$sr .= " # ".($y-1)."\n".(" "x$indent)."]";
} else {
$$sr .= "]";
}
} elsif (reftype($x) eq 'SCALAR') {
#$$sr .= "\\"."e($$x);
$$sr .= "\\";
&bad_dump($sr,$$x,$indent+1, %done);
} elsif (reftype($x) eq 'REF') {
$$sr .= "\\";
&bad_dump($sr,$$x,$indent+1, %done);
} else {
confess;
}
if (ref($x) ne reftype($x)) {
$$sr .= ", "."e(ref($x));
$$sr .= ")";
$$sr .= ")" if $indent;
}
} else {
$$sr .= "e($x);
}
}
sub quote {
my($s) = @_;
my($e);
return 'undef' unless defined $s;
$s =~ s/\\/\\\\/g && ($e = 1);
$s =~ s/\n/\\n/g && ($e = 1);
$s =~ s/\t/\\t/g && ($e = 1);
$s =~ s/\r/\\r/g && ($e = 1);
$s =~ s/\f/\\f/g && ($e = 1);
$s =~ s/\$/\\\$/g && ($e = 1);
$s =~ s/\@/\\\@/g && ($e = 1);
$s =~ s/([\0-\37\177-\200])/sprintf("\\x%02x",ord($1))/eg && ($e = 1);
if ($s =~ /^[1-9]\d*(\.\d+)?$/ || $s =~ /^[A-Z]\w*$/) {
return $s;
} elsif (defined $e && $e == 1) {
$s =~ s/"/\\"/g;
return "\"$s\"";
} else {
$s =~ s/'/\\'/g;
return "'$s'";
}
}
sub nukevar
{
for my $x (@_) {
next unless ref($x);
if (reftype($x) eq 'HASH') {
nukevar(keys %$x);
%$x = ();
} elsif (reftype($x) eq 'ARRAY') {
nukevar(@$x);
@$x = ();
} elsif (reftype($x) eq 'SCALAR') {
nukevar($$x);
$x = undef;
} elsif (reftype($x) eq 'REF') {
nukevar($$x);
$x = undef;
} else {
confess;
}
}
}
sub supercross1
{
my ($tests, $baseroot, $selector) = @_;
my $number = 0;
for my $test (split(/^\s*$/m, $tests)) {
$number++;
next unless &$selector($number);
my %conf;
$test =~ s/\A[\n\s]+//;
$conf{$1} = [ split(' ', $2) ]
while $test =~ s/([A-Z])=(.*)\n\s*//;
my (@tests) = split(/\n\s+---\s*\n/, $test);
my (@func);
for my $t (@tests) {
eval "push(\@func, sub { my (\$root, \$subtest, \$subtest2, \$subtest3) = \@_; $t })";
confess "eval test $number: <<$t>>of<$test>: $@" if $@;
}
my $pre;
if ($conf{E}) {
eval "\$pre = sub { my \$root = shift; @{$conf{E}} }";
confess "eval <<@{$conf{E}}>>of<$test>: $@" if $@;
}
my (@virt) = defined $conf{V}
? @{$conf{V}}
: (qw(0 virtual));
my (@commits) = defined $conf{C}
? (grep {$_ <= (2**@tests)} @{$conf{C}})
: (0..2**(@tests));
my (@ss) = defined $conf{S}
? (grep {$_ <= (2**(@tests -1))} @{$conf{S}})
: (0..2**(@tests -1));
my (@subtest) = defined $conf{T}
? @{$conf{T}}
: (0);
my (@subtest2) = defined $conf{U}
? @{$conf{U}}
: (0);
my (@subtest3) = defined $conf{X}
? @{$conf{X}}
: (0);
my $mroot = {};
my $proot;
for my $vobj (@virt) {
for my $subtest (@subtest) {
for my $subtest2 (@subtest2) {
for my $subtest3 (@subtest3) {
for my $docommit (@commits) {
for my $dosamesame (@ss) {
resetall;
my $x = 'rval';
$mroot = clone($baseroot);
&$pre($mroot) if $pre;
$r1->{named_objects}{root} = clone($mroot);
$r1->virtual_object($r1->{named_objects}{root}, $vobj) if $vobj;
$r1->virtual_object($r1->{named_objects}{root}{hkey}, $vobj) if $vobj;
$r1->commit;
rcon;
my $sig = "N=$number.V=$vobj.C=$docommit.S=$dosamesame.T=$subtest.U=$subtest2.X=$subtest3";
print "# $sig\n";
print $test if $debug;
for my $tn (0..$#func) {
my $tf = $func[$tn];
$proot = $r1->{named_objects}{root};
print "# EXECUTING $tests[$tn]\n" if $debug;
&$tf($mroot,$subtest,$subtest2,$subtest3);
&$tf($proot,$subtest,$subtest2,$subtest3);
$r1->commit
if $docommit & 2**$tn;
print "# COMPARING\n"
if $dosamesame & 2**$tn && $debug;
test(docompare($mroot, $proot), "<$tn>$sig")
if $dosamesame & 2**$tn;
rcon
if $tn < $#func && $docommit & 2**$tn;
}
print "# FINAL COMPARE\n" if $debug;
test(docompare($mroot, $proot), "<END>$sig")
}
}
}
}
}
}
rcon;
nukevar($r1->{named_objects}, $mroot);
$r1->commit;
rcon;
notied;
}
}
sub wa
{
$r1->workaround27555(@_);
}
sub supercross7
{
my ($tests, $extra) = @_;
$extra ||= {};
my $funcs = qr/(?:COMMIT|COMPARE|VIRTUAL|TODO_COMPARE|CLEAR_CACHE)/;
for my $test (split(/^\s*$/m, $tests)) {
my @vars;
for(;;) {
if ($test =~ s/^\s*\$?(\w+):(\S*)\s*\n//) {
push(@vars, {
pvar => $1,
list => [ split(',', $2) ],
});
next;
}
last;
}
my $cpx = 1;
while ($test =~ s/\s*CP_($funcs[^;\n]*)/\n\t\t$1 if \$supercross7cp$cpx/) {
push(@vars, {
pvar => "supercross7cp$cpx",
list => [0, 1],
});
$cpx++;
}
$test =~ s/\A\s*\n//s;
$extra->{clean} = $test;
my $vlist = '';
for my $v (@vars) {
$vlist .= "\$$v->{pvar},";
}
chop($vlist);
$test .= "\nDONE\n";
my $parts = [];
my $last = {};
while ($test =~ s/(.*?)($funcs|DONE)(\S+)?([^\n]*)\n//s) {
my ($tpart, $func, $arg, $ifclause) = ($1, $2, $3, $4);
if ($tpart =~ /\S/) {
my $x;
eval "\$x = sub { my (\$root, $vlist) = \@_; $tpart }";
die "eval with vlist='$vlist' and tpart='$tpart': $@" if $@;
my $p = {
code => $x,
source => $tpart,
};
$tpart =~ s/\n\s*/\\n /g;
$p->{source1} = $tpart;
$p->{num} = $#$parts;
$last = $p;
push(@$parts, $p);
}
last if $func eq 'DONE';
my $cond;
if ($ifclause =~ /\S/) {
eval "\$cond = sub { my ($vlist) = \@_; return 1 $ifclause; return 0 }";
die "$@ on eval with $vlist and $ifclause" if $@;
} else {
$cond = sub { 1 };
}
push(@$parts, {
special => $func,
clause => $ifclause,
cond => $cond,
arg => $arg,
source => $last->{source},
source1 => $last->{source1},
num => $#$parts,
});
}
die "test='$test'" if $test =~ /\S/s;
supercross7test($parts, $extra, [], @vars);
}
}
sub betterkeys(\%)
{
my ($kref) = shift;
my $tied = tied(%$kref);
return keys(%$kref) if wantarray;
if ($tied && $tied->can('SCALAR')) {
return scalar(%$kref);
} else {
return keys(%$kref);
}
}
sub supercross7test
{
my ($parts, $extra, $preamble, $v, @vars) = @_;
if ($v) {
my $l = $v->{list};
for my $i (@$l) {
$v->{list} = [ $i ];
push(@$preamble, $v);
supercross7test($parts, $extra, $preamble, @vars);
pop(@$preamble);
}
return;
}
my $vset = '';
my @vlist;
for my $p (@$preamble) {
$vset .= "\t\t\$$p->{pvar}:$p->{list}[0]\n";
push(@vlist, $p->{list}[0]);
}
my $clean .= $vset . $extra->{clean};
my $baseroot = $extra->{baseroot} || {};
resetall;
safeout("# $clean") if $debug;
my $mroot = clone($baseroot);
my $proot = $r1->{named_objects}{root} = clone($mroot);
safeout(sprintf("# %d parts\n", scalar(@$parts))) if $debug;
local($tcTODO) = $tcTODO;
my $pnum = 1;
for my $part (@$parts) {
if ($part->{code}) {
my $code = $part->{code};
&$code($mroot, @vlist);
&$code($proot, @vlist);
safeout("# Running code: $part->{source}\n") if $debug;
} else {
my $ifclause = $part->{cond};
if ($part->{clause} =~ /\S/) {
print STDERR "# ifclause=$part->{clause}\n" if $debug;
}
if (&$ifclause(@vlist)) {
if ($part->{special} eq 'COMMIT') {
print STDERR "# Commit\n" if $debug;
$r1->commit();
undef $proot;
rcon;
$proot = $r1->{named_objects}{root};
} elsif ($part->{special} eq 'TODO_COMPARE') {
print STDERR "# Compare (todo)\n" if $debug;
todo_test(docompare($mroot, $proot), $pnum);
} elsif ($part->{special} eq 'COMPARE') {
print STDERR "# Compare\n" if $debug;
test(docompare($mroot, $proot), $pnum);
} elsif ($part->{special} eq 'VIRTUAL') {
if ($part->{arg}) {
my $arg = $part->{arg};
$arg =~ s/^\((.*)\)$/$1/;
print STDERR "# Virtualize $arg\n" if $debug;
eval "\$r1->virtual_object(\$proot$arg)";
die $@ if $@;
} else {
print STDERR "# Virtualize root\n" if $debug;
eval { $r1->virtual_object($proot, 1) };
die $@ if $@;
}
} elsif ($part->{special} eq 'CLEAR_CACHE') {
eval "\$r1->clear_cache()";
die $@ if $@;
} else {
die;
}
} else {
print STDERR "# Negatory on $part->{special}\n" if $debug;
}
}
$pnum++;
}
}
1;