STDERR tests in Test::Warn?

2003-02-27 Thread schwern
As comes up pretty often, people want to trap stuff on STDERR.  I've got
adhoc stuff to do that in TieOut.pm, but I've never really found a good 
place to put it in a module.

Test::Warn seems like its a good spot.  Warnings and stuff going directly
to STDERR are related beasts.  So if Janek wants it, I can patch Test::Warn
to trap STDERR was well as normal warnings.

The question is the interface.  Should warning* just trap STDERR like
any other warning or should there be seperate functions?

I'd argue for the former.  First from a DWIM standpoint, and second
because sometimes you'll be getting both warnings and output to STDERR
from the same code (as just happened to me, prompting this train of
thought) and it would be nice to trap both.

The downside is the two streams are mixed and can't be seperated by
the test without unnecessarily complicating the arguments to warning*.
Dunno how important this is, don't think its very.



Re: Test::More::is_deeply problems with blessings and stringified refs

2003-02-27 Thread Fergal Daly
On Thursday 27 February 2003 21:35, [EMAIL PROTECTED] wrote:
> On Thu, Feb 27, 2003 at 09:21:09PM +, Fergal Daly wrote:
>
> Or even better, cmp_objects().

Yep, sounds better.

> > - let _deep_check take it's cue from the second argument. If the second
> > argument  is blessed then be strict about the classes, if it's unblessed
> > then ignore the classes. This should happen at all levels in the
> > structures.
>
> This sounds too magical.

Magical how? It may be a bit too hard to explain and so it's probably a bad 
idea. It's not that magical to implement though, it just requires something 
like this in _deep_check

my ($class1, $base1) = ref($e1) =~ /^(.*)=(.*?)$/;
my ($class2, $base2) = ref($e2) =~ /^(.*)=(.*?)$/;

if ($base1 eq $base2 and ($class2 and $class1 eq $class2))
{
check the contents of $e1 and $e2
}
else
{
fail
}

Then you could get rid of all the UNIVERSAL::isa calls too.

Anyway, I think it's better to explicitly request full checking.

> Either way, Test::More's is_deeply() behavior isn't likely to change, so
> Test::Set will have to take care of it.

Would it be acceptable to add a third argument to _deep_check to switch on/off 
bless checking, rather than having to reimplement the whole thing?

Test::Set is going to have to be renamed Test::EvenMore,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html



Test::More::is_deeply problems with blessings and stringified refs

2003-02-27 Thread Fergal Daly
On Thursday 27 February 2003 16:40, [EMAIL PROTECTED] wrote:
> > is_deeply() ignores the classes of blessed refs. So
> >
> > perl -MTest::More=no_plan -e 'is_deeply(bless([], "a"), bless([], "b"))'
> >
> > passes,
>
> Oh.  Not sure if that's a bug or a feature.  Discuss it on perl-qa.

I think that although a test that ignores blessed classes could be handy in 
some circumstances (ie programming in general), I reckon in the context of 
test suites it's a bug.

If fixing it makes some tests fail then it means either the tests weren't 
really correct or a genuine bug has been discovered.

While I'm at it, a definite problem is that a ref and the stringified version 
of the ref are currently considered equal, so

perl -MTest::More=no_plan -e '$a=[];is_deeply($a, $a."", "should fail")'

passes.

The first patch below fixes the second problem. The second patch fixes the 
first problem (if you think it is a problem) and will only apply cleanly 
after patch1,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html

--- ./lib/Test/More.pm.orig 2002-08-26 17:20:41.0 +0100
+++ ./lib/Test/More.pm  2003-02-21 03:37:56.0 +
@@ -937,7 +937,7 @@
 my($this, $that, $name) = @_;
 
 my $ok;
-if( !ref $this || !ref $that ) {
+if( !ref $this && !ref $that ) {
 $ok = $Test->is_eq($this, $that, $name);
 }
 else {
@@ -984,8 +984,9 @@
 foreach my $idx (0..$#vals) {
 my $val = $vals[$idx];
 $vals[$idx] = !defined $val ? 'undef' : 
-  $val eq $DNE  ? "Does not exist"
-: "'$val'";
+  ref $val ? $val eq $DNE  ? "Does not exist"
+   : $val
+   : "'$val'"
 }
 
 $out .= "$vars[0] = $vals[0]\n";
@@ -1008,7 +1009,7 @@
 #'#
 sub eq_array  {
 my($a1, $a2) = @_;
-return 1 if $a1 eq $a2;
+return 1 if !ref $a1 and ! ref $a2 and $a1 eq $a2;
 
 my $ok = 1;
 my $max = $#$a1 > $#$a2 ? $#$a1 : $#$a2;
@@ -1034,7 +1035,7 @@
 # Quiet uninitialized value warnings when comparing undefs.
 local $^W = 0; 
 
-if( $e1 eq $e2 ) {
+if( ! ref $e1 and ! ref $e2 and $e1 eq $e2 ) {
 $ok = 1;
 }
 else {
@@ -1083,7 +1084,7 @@
 
 sub eq_hash {
 my($a1, $a2) = @_;
-return 1 if $a1 eq $a2;
+return 1 if !ref $a1 and ! ref $a2 and $a1 eq $a2;
 
 my $ok = 1;
 my $bigger = keys %$a1 > keys %$a2 ? $a1 : $a2;



--- lib/Test/More.pm.fixed_ref  2003-02-27 17:08:09.0 +
+++ lib/Test/More.pm2003-02-27 17:25:20.0 +
@@ -1035,37 +1035,40 @@
 # Quiet uninitialized value warnings when comparing undefs.
 local $^W = 0; 
 
-if( ! ref $e1 and ! ref $e2 and $e1 eq $e2 ) {
-$ok = 1;
-}
-else {
-if( UNIVERSAL::isa($e1, 'ARRAY') and
-UNIVERSAL::isa($e2, 'ARRAY') )
+if( ref($e1) eq ref($e2)) {
+
+if( !ref($e1)) {
+if ($e1 eq $e2) {
+$ok = 1;
+}
+else {
+push @Data_Stack, { vals => [$e1, $e2] };
+$ok = 0;
+}
+}
+elsif( UNIVERSAL::isa($e1, 'ARRAY') )
 {
 $ok = eq_array($e1, $e2);
 }
-elsif( UNIVERSAL::isa($e1, 'HASH') and
-   UNIVERSAL::isa($e2, 'HASH') )
+elsif( UNIVERSAL::isa($e1, 'HASH') )
 {
 $ok = eq_hash($e1, $e2);
 }
-elsif( UNIVERSAL::isa($e1, 'REF') and
-   UNIVERSAL::isa($e2, 'REF') )
+elsif( UNIVERSAL::isa($e1, 'REF') )
 {
 push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
 $ok = _deep_check($$e1, $$e2);
 pop @Data_Stack if $ok;
 }
-elsif( UNIVERSAL::isa($e1, 'SCALAR') and
-   UNIVERSAL::isa($e2, 'SCALAR') )
+elsif( UNIVERSAL::isa($e1, 'SCALAR') )
 {
 push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
 $ok = _deep_check($$e1, $$e2);
 }
-else {
-push @Data_Stack, { vals => [$e1, $e2] };
-$ok = 0;
-}
+}
+else {
+push @Data_Stack, { vals => [$e1, $e2] };
+$ok = 0;
 }
 }
 




Test::Set

2003-02-27 Thread Fergal Daly
I've been discussing this with Mr Schwern recently but he's a little 
indisposed at the moment so this seemed like a good place for feedback.

Test::More's eq_set() is not a set comparison or a bag comparison but a 
strange array comparison where the order of scalars doesn't matter but the 
order of refs does, except for deeply equalrefs, which can be interchanged. 

So

perl -MTest::More -e 'print eq_set([ [], {} ], [ {}, [] ])."\n"'

prints 0

Attached is Test::Set which provides true deep set and bag comparisons and 
bagset.t some tests.

Any comments welcome,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html
# -*-perl-*-
use strict;
use Test::More (tests => 13);
use Test::Set;

my $refa_1 = ["a"];
my $refa_2 = ["a"];

my $set = [qw(a b a c d), $refa_1];
is_math_set($set, [qw(a b a c d), $refa_1], "math_set equal");
is_math_set($set, [qw(b c), $refa_1, qw(d a)], "math_set reorder");
is_math_set($set, [qw(b c), $refa_1, qw(d a b c ), $refa_1], "math_set reorder dupes");
is_math_set($set, [qw(b c), $refa_1, qw(d a b c ), $refa_2], "math_set reorder deep-dupes");
ok(! eq_math_set($set, [qw(c), $refa_1, qw(d a c ), $refa_1]), "math_set missing");
ok(! eq_math_set($set, [qw(c b e), $refa_1, qw(d a c ), $refa_1]), "math_set extra");

is_bag([[], $refa_2], [$refa_2, []], "eq_bag deep");
ok(! eq_bag([$refa_2, []], [$refa_2, [], []]), "eq_bag extra");
ok(! eq_bag([$refa_2, [], []], [$refa_2, []]), "eq_bag missing");

check_diff(
	[qw(a a b b c)], [qw(e d c d e)],
	[qw(d e)], [qw(a b)],
	"set", 1
);

check_diff(
	[qw(a a b b c)], [qw(e d c d e)],
	[qw(d d e e)], [qw(a a b b)],
	"bag", 0
);

sub check_diff
{
	my ($a1, $a2, $missing, $extra, $name, $is_set) = @_;

	my ($m, $e) = Test::Set::_diff_bagset($a1, $a2, $is_set);

	is_deeply($missing, [sort @$m], "diff $name m");
	is_deeply($extra, [sort @$e], "diff $name e");
}
package Test::Set;

use 5.004;

use strict;
use Test::More;

use vars qw($VERSION @ISA @EXPORT );
$VERSION = '0.01';
@ISA= qw( Exporter );
@EXPORT = qw(
	is_bag is_math_set
	eq_bag eq_math_set
);

my $Test = Test::Builder->new;

sub eq_deeply
{
	# this can be removed when Test::More exports an eq_deeply() function
	local @Test::More::Data_Stack = ();
	return Test::More::_deep_check(@_);
}

=head1 NAME

Test::Set - some tests for set and bag comparisons

=head1 SYNOPSIS

  use Test::More tests => $Num_Tests;
  use Test::Set;

  is_bag([EMAIL PROTECTED], [EMAIL PROTECTED], $test_name);
  is_math_set([EMAIL PROTECTED], [EMAIL PROTECTED], $test_name);

=head1 DESCRIPTION

These are just some extra comaprison functions and tests to add to the ones
already in Test::More. It depends on Test::More for some comparison
functions. You should read the documentation for Test::More before using
these tests.

Test::Set provides a true set comparison and a true bag comparison (a bag is
like a set, the order of elements doesn't matter, however the number of
occurrences of an element does).

It does not provide any way to set up a plan.

=head2 AVAILABLE TESTS

=over 4

=item B

  is_math_set( $this, $that, $test_name );

Similar to Test::More's is_deeply(), except that $this and $that must be
array references. Because it is a set comparison, it ignores the order of
elements in $this and $that and it also ignores duplicate elements. This is
a deep check so duplicates are found by deep comparison. If the two sets are
different, it will list the scalar elements in $this that are missing and
that are extra. It will also count the missing and extra reference elements
but will not display their structure as that could get very messy.

=cut

sub _converge_arrays
{
	# this takes a master array and some slave arrays.

	# it returns one array for each slave array with all references replaced
	# by the first deeply equal reference from the master array.
	
	my $all = shift;

	# because we'll be do lots of deep_checks against this list, it's best if
	# we throw out the refs and the duplicates before we start.

	my @all = grep {ref($_)} @$all;
	my %all;
	@[EMAIL PROTECTED] = @all;
	@all = values %all;

	my @converged;

	foreach my $array (@_)
	{
		my @array = @$array;

		foreach my $elem (@array)
		{
			next unless ref $elem;
			if (my ($master) = grep { eq_deeply($elem, $_) } @all)
			{
$elem = $master;
			}
		}

		push(@converged, [EMAIL PROTECTED]);
	}

	return @converged;
}

sub _diff_bagset
{
	# this takes 2 arrays and finds out what is in $a1 but not in $a2 and vice
	# versa

	# the arrays must contains all refs or all scalars to guarantee the
	# correct answer

	# a third (boolean) parameter specifies whether it's a bag or set
	# comparison. True for set, false for bag

	my ($a1, $a2, $is_set) = @_;

	if (ref($a1->[0]))
	{
		# we are dealing with refs so we need to converge them to cope with
		# deepness

		($a1, $a2) = _converge_arrays([EMAIL PROTECTED], @$a2], $a1, $a2);
	}

	my (%a1, %a2, %values

Test::More diagostics problem

2003-02-27 Thread Fergal Daly
There's a line missing from Test::More::_deep_check(). It results in funny  
diagnostics after comparing scalar refs, so 

perl -MTest::More=no_plan -e 'is_deeply([(\"a") x 5, "b"], [(\"a") x 5, "c"])'

gives

# Structures begin differing at:
#  $got->[0][1][2][3][4][5] = 'b'
# $expected->[0][1][2][3][4][5] = 'c'

instead of

# Structures begin differing at:
#  $got->[5] = 'b'
# $expected->[5] = 'c'

attached patch fixes it,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html
--- ./lib/Test/More.pm.ref	2003-02-27 17:08:09.0 +
+++ ./lib/Test/More.pm	2003-02-27 20:54:34.0 +
@@ -1061,6 +1061,7 @@
 {
 push @Data_Stack, { type => 'REF', vals => [$e1, $e2] };
 $ok = _deep_check($$e1, $$e2);
+pop @Data_Stack if $ok;
 }
 else {
 push @Data_Stack, { vals => [$e1, $e2] };


Re: Test::More::is_deeply problems with blessings and stringified refs

2003-02-27 Thread Fergal Daly
On Thursday 27 February 2003 20:54, [EMAIL PROTECTED] wrote:
> On Thu, Feb 27, 2003 at 05:32:42PM +, Fergal Daly wrote:
> > I think that although a test that ignores blessed classes could be handy
> > in some circumstances (ie programming in general), I reckon in the
> > context of test suites it's a bug.
>
> I am already not yet convinced.  In particular, it makes this sort of test
> more difficult than it needs be:
>
> is_deeply($obj, { foo => 42, bar => 23 });

Absolutely, but there is currently no way to do this

is_deeply($obj, bless({ foo => 42, bar => 23 }, "MyClass"));

and get a fail if $obj is not in MyClass - as I found out today ;-(

2 solutions spring to mind

- create is_deeply_blessed

- let _deep_check take it's cue from the second argument. If the second 
argument  is blessed then be strict about the classes, if it's unblessed then 
ignore the classes. This should happen at all levels in the structures.

The second option allows your test above to succeed as normal but also allows 
mine to be strict. The only tests which will suddenly start failing are ones 
where the tester has specified the class, in which case they probably wanted 
it to fail. This sounds a little over-complicated but in theory I think it's 
good,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html



Re: Test::More::is_deeply problems with blessings and stringified refs

2003-02-27 Thread Fergal Daly
On Thursday 27 February 2003 22:03, Fergal Daly wrote:
> Would it be acceptable to add a third argument to _deep_check to switch
> on/off bless checking, rather than having to reimplement the whole thing?

Below is a very simple patch to do that. That makes cmp_object very easy

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html

--- More.pm.scalar  2003-02-27 22:08:37.0 +
+++ More.pm 2003-02-27 22:22:46.0 +
@@ -1027,7 +1027,7 @@
 }
 
 sub _deep_check {
-my($e1, $e2) = @_;
+my($e1, $e2, $check_bless) = @_;
 my $ok = 0;
 
 my $eq;
@@ -1035,7 +1035,11 @@
 # Quiet uninitialized value warnings when comparing undefs.
 local $^W = 0; 
 
-if( ! ref $e1 and ! ref $e2 and $e1 eq $e2 ) {
+if ($check_bless and ref($e1) ne ref($e2)) {
+push @Data_Stack, { vals => [$e1, $e2] };
+$ok = 0;
+}
+elsif( ! ref $e1 and ! ref $e2 and $e1 eq $e2 ) {
 $ok = 1;
 }
 else {



One more Test::More problem

2003-02-27 Thread Fergal Daly
Final one tonight!

eq_array and eq_hash don't tidy up after themselves. You would have to be very 
unfortunate to be stung by this problem but potentially you could end up with 
extra references to some of your variables which could effect tests further 
on due to DESTROYs not being called or whatever.

use Test::More;
for(1..10)
{
  eq_array( [[]], [{}] )
}
print join(", ", @Test::More::Data_Stack)."\n";

The solution is to make eq_array and eq_hash wrappers around the original 
functions. This is needed for the circular structures patch anyway.

Actually, I've gone a little further and made them wrappers around a new 
eq_deeply(). All they do is check that the second argument is of the correct 
type. This means that all the localling of @Data_Stack is only done in 
eq_deeply.

It also means they won't die anymore if the wrong kind of arguments are passed 
in. Although, I'm not sure if this is a good idea, maybe dying is preferable.

Patch attached.

Also attached is a test script that exercises the various comparison 
functions. It's missing a few cases but it covers a fair bit,

F

-- 
Do you need someone with lots of Unix sysadmin and/or lots of OO software 
development experience? Go on, giz a job.
My CV - http://www.fergaldaly.com/cv.html
--- lib/Test/More.pm.orig	2003-02-27 20:54:34.0 +
+++ lib/Test/More.pm	2003-02-28 00:11:47.0 +
@@ -25,7 +25,7 @@
  cmp_ok
  skip todo todo_skip
  pass fail
- eq_array eq_hash eq_set
+ eq_array eq_hash eq_set eq_deeply
  $TODO
  plan
  can_ok  isa_ok
@@ -996,6 +996,12 @@
 return $out;
 }
 
+sub eq_deeply {
+my ($a1, $a2) = @_;
+
+local @Data_Stack = ();
+return _deep_check($a1, $a2);
+}
 
 =item B
 
@@ -1007,7 +1013,14 @@
 =cut
 
 #'#
-sub eq_array  {
+
+sub eq_array {
+my ($a1, $a2) = @_;
+
+return UNIVERSAL::isa($a2, "ARRAY") ? return eq_deeply($a1, $a2) : 0;
+}
+
+sub _eq_array  {
 my($a1, $a2) = @_;
 return 1 if !ref $a1 and ! ref $a2 and $a1 eq $a2;
 
@@ -1042,12 +1055,12 @@
 if( UNIVERSAL::isa($e1, 'ARRAY') and
 UNIVERSAL::isa($e2, 'ARRAY') )
 {
-$ok = eq_array($e1, $e2);
+$ok = _eq_array($e1, $e2);
 }
 elsif( UNIVERSAL::isa($e1, 'HASH') and
UNIVERSAL::isa($e2, 'HASH') )
 {
-$ok = eq_hash($e1, $e2);
+$ok = _eq_hash($e1, $e2);
 }
 elsif( UNIVERSAL::isa($e1, 'REF') and
UNIVERSAL::isa($e2, 'REF') )
@@ -1084,6 +1097,12 @@
 =cut
 
 sub eq_hash {
+my ($a1, $a2) = @_;
+
+return UNIVERSAL::isa($a2, "HASH") ? return eq_deeply($a1, $a2) : 0;
+}
+
+sub _eq_hash {
 my($a1, $a2) = @_;
 return 1 if !ref $a1 and ! ref $a2 and $a1 eq $a2;
 
# -*-perl-*-
use strict;
use Test::More (tests => 18);

{
	my $a = [];
	my $b = $a."";

	ok(! eq_deeply([$a], [$b]), "stringified");
}

{
	# take an array full of a variety of things and break each one

	my @a1 = an_array();
	my @a2 = an_array();

	is_deeply([EMAIL PROTECTED], [EMAIL PROTECTED], "is array");
	ok(eq_array([EMAIL PROTECTED], [EMAIL PROTECTED]), "eq array");

	for (my $i = 0; $i < @a1; $i++)
	{
		my @new = an_array();
		my $it = $new[$i];
		my $broken = break_it($it);

		$new[$i] = $broken;
		ok(! eq_array([EMAIL PROTECTED], [EMAIL PROTECTED]), "array broken ref='".ref($broken)."'");
	}

	my @new = an_array();
	break_it([EMAIL PROTECTED]);
	ok(! eq_array([EMAIL PROTECTED], [EMAIL PROTECTED]), "array broken extra");
}

{
	# take a hash full of a variety of things and break each one

	my %a1 = a_hash();
	my %a2 = a_hash();

	is_deeply(\%a1, \%a2, "is hash");
	ok(eq_hash(\%a1, \%a2), "eq hash");

	foreach my $key (keys %a1)
	{
		my %new = a_hash();
		my $it = $new{$key};
		my $broken = break_it($it);

		$new{$key} = $broken;
		ok(! eq_hash(\%new, \%a1), "hash broken ref='".ref($broken)."'");
	}

	my %new = a_hash();
	break_it(\%new);
	ok(! eq_hash(\%new, \%a1), "hash broken extra");
}

# test a messy complex thing
is_deeply(complex_thing(), complex_thing(), "complex");

sub an_array
{
	return ( [], {}, "hello", \"ref", \['hello'] );
}

sub a_hash
{
	return (
		key1 => [],
		key2 => {},
		key3 => "hello",
		key4 => \"ref",
		key5  => \['hello']
	);
}

sub break_it
{
	my $it = shift;

	if (UNIVERSAL::isa($it, 'ARRAY'))
	{
		push(@$it, "extra elem");
	}
	elsif (UNIVERSAL::isa($it, 'HASH'))
	{
		$it->{"extra key"} = "extra value";
	}
	elsif (UNIVERSAL::isa($it, 'REF'))
	{
		$it = \['a different ref'];
	}
	elsif (UNIVERSAL::isa($it, 'SCALAR'))
	{
		$it = \"$$it and something else";
	}
	else
	{
		$it = "$it something else";
	}
	return $it
}

sub complex_thing
{
	my $b = [qw( hello world)];
	my $a = [
		{
			key1 => \$b,
			key2 => [{}],
			key3 => \'scalarref',
			key4 => 'scalar',
		},
		[\"scalarref"],
	];

	return $a;
}