Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Monday November 24, 2008
05:32 AM

Test::Most::explain: need steroid advice

[ #37929 ]

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!)

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • I think Test::Most is now replacing my default use Test::More :-)

    • 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 [cpan.org] with it!

  • i prefer to use Data::Dump because it avoids the ugly $VAR1 cruft
  • I like the elegance of show with the source filter.
    • 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 [cpan.org] 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,

      • 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.