Test::Most::explain: need steroid advice

Ovid on 2008-11-24T10:32:00

Summary: my local version of Test::Most can do this (note the variable names!), but I am pondering the interface:

use Test::Most 'no_plan';

ok 1;
my ( $foo, @bar ) = qw( this is a list );
explain $foo, \@bar;
__END__
# $foo = 'this';
# @bar = (
#          'is',
#          'a',
#          'list'
#        );

For years, people have struggled with tests with the following:

diag $foo

And then they've cursed when they've seen the result:

# HASH(0x80631d0)

So they remember to call Data::Dumper or something similar:

use Data::Dumper;
diag Dumper($foo);
__END__
$VAR1 = {
          'foo' => 'bar'
        };

Of course, that's ugly formatting and grunt work that the programmer doesn't want to do. So I added the explain() function to Test::Most. If it sees a reference, it calls Dumper for you:

use Test::Most 'no_plan';
explain $foo;
__END__
{
    'foo' => 'bar'
};

We've also done away with that annoying $VAR1, but I always thought that was ugly, so I did away with it. Perhaps I shouldn't, but there you go.

It's worth noting that I tried to add explain() to Test::More, but Schwern vetoed the Dumper/diag combination. It's now the relatively broken:

diag explain $foo;

That's why I still support explain() in Test::Most. However, this also means that I can have fun with this. My current idea is to put the $VAR1 back in there, but with a twist: if you don't have Data::Dumper::Names, you see the old explain() behavior. However, if you do have it installed you can see the variable names:

use Test::Most 'no_plan';

ok 1;
my ( $foo, @bar ) = qw( this is a list );
explain $foo, \@bar;
__END__
# $foo = 'this';
# @bar = (
#          'is',
#          'a',
#          'list'
#        );

Now that, I think, is major juju coolness. It also turned out quite handy when I was sprinkling ::explain() statements in a module, trying to work out what was broken. The variables had different names, but carried similar values, making it hard to see which was which. This made it trivial.

Unfortunately, this necessitates a couple of changes. First, Data::Dumper::Names needs to be told how far up the call stack to look. Currently it only looks up one level, the simplistic, but ugly fix is to allow something like this:

sub explain {
    return unless $ENV{TEST_VERBOSE};
    if ($DATA_DUMPER_NAMES_INSTALLED) {

        # Ugly!
        local $Data::Dumper::Names::UpLevel = 2;
        diag(Data::Dumper::Names::Dumper(@_));
    }
    else {
        # normal behavior
    }
}

The other issue is the interface to Test::Most. Just how much magic should I pack into one function? Maybe I should make another function, show, which has this behavior? This is incredibly useful, but I'd like to figure out the best interface here and this could be tricky.

Side note: this trick wouldn't work easily with Data::Dumper::Simple as Simple uses a source filter and I shouldn't be applying that to your code unless you ask. However, Data::Dumper::Simple does a couple of tricks that Data::Dumper::Names cannot. First, it doesn't require a backslash in front of arrays and hashes. Second, it would correctly report the names with this:

print Dumper($foo[3]);

Data::Dumper::Names can't figure out the container for that variable, so it gets reported as $VAR1. If there's a way around that, I'd love to hear it :)

Update: the main problem I'm finding with this approach:

explain "Check that users can be loaded";
__END__
# $VAR1 = 'Check that users can be loaded';

That's not what I want and could get tricky. I think a separate show() function would be better after all (though I could get crazy and if the variable is readonly and I can't determine the variable name, I could omit the $VAR1. Madness I tell you. Madness!)


Okay - that convinces me

Adrian on 2008-11-24T11:26:26

I think Test::Most is now replacing my default use Test::More :-)

Re:Okay - that convinces me

Ovid on 2008-11-24T12:07:45

To be fair, I haven't actually added the default "show variable name" behavior yet. I've got to get the interface issues nailed down first :)

And why weren't you using it beforehand? I even bundle one of your modules with it!

Re:Okay - that convinces me

Adrian on 2008-11-24T12:34:54

"And why weren't you using it beforehand"

Lazy - in the bad way.

Data::Dump

dingo on 2008-11-24T18:57:46

i prefer to use Data::Dump because it avoids the ugly $VAR1 cruft

Yes!

jeremiah on 2008-11-26T10:29:24

I like the elegance of show with the source filter.

Re:Yes!

Ovid on 2008-11-26T10:48:23

Surprise! It's done without a source filter :) It uses Data::Dumper::Names which, in turn, uses PadWalker to fetch the variable names. Test::Most 0.20_01 has a developer implementation. Already I have a couple of issues.

First, since Data::Dumper::Names 0.03 is only recommended and not required, one test fails sporadically. My bad :) Also, because this uses PadWalker, this shows us as $VAR1 instead of the variable name:

show $@;

That's because $@ is not a lexical. I don't know how to get around that, so I need to document this as a limitation.

Re:Yes!

Aristotle on 2008-11-28T13:42:34

I wonder if it’s possible to find your caller’s op tree and find your call site in it, then isolate the parameter expression and deparse it… that would be a rock solid approach.

But I don’t know if the Perl introspection stuff is quite up to that level at this point.