Devel::Eval 0.02 to now use sekrit new Perl 5.8.9+ feature!

Alias on 2009-12-31T00:50:52

I take (apparently) an unusual approach to testing.

Apart from regression tests for replicating and fixing reported bugs, I find that my main reason for writing test scripts is to feed the debugger, since I probably spend as much time in the debugger than I do in my editor.

It's just so much more convenient to see what your code is doing, rather than having it tell you (via print statements or logging/tracing etc).

I guess you could call it TDD of a different kind. Test Driven Debugging, rather than Test Driven Development.

However, one of the biggest weaknesses of this debugger-centric development process for me has been string evals and code generation.

Things like ORLite can generate thousands of lines of code, with half a dozen levels of methods nested inside each other. Debugging this is a pain, because unlike regular code the debugger can't show you this eval'ed code.

For this reason, I created Devel::Eval, which exports a 'dval' function that is a (temporary) drop in replacement for eval that writes the code to a temp file, and then does a 'do' of that file. You get the code visible in the debugger, AND you can take a copy of the generated code for later if you want to read through it in more depth.

This has worked great for ORLite, which generates at a package granularity.

But it ran out of steam when I tried to use it on Hook::LexWrap (which does some amazingly crazy/scary things, like using DESTROY-time self-executing blessed code references that modify lexical variables as a way of altering the behaviour of anonymous closures after we've handed them off and don't know where they are any more.

After the first 2 weeks of work, I've managed to drop the Hook::LexWrap dependency and added some specialisation so that optimisable cases work differently (for example, the before { } advice is optimised to use goto internally, while the after { } advice uses Sub::UpLevel instead of Hook::LexWrap's private caller() clone.

But I digress...

The problem with all this, is that the internals now rely on string-eval generated closures that use variables created OUTSIDE the string eval. So the "do $filename" approach is now untenable.

Figuring that the temp file approach was tapped out, I headed to #p5p as my "Perl help channel of last resort" to see if there were any REALLY exotic solutions to my problem.

To my great relief, not only is this problem fixed, it's fixed properly and at the source (and it has been for a while). It's just that someone forgot to document a feature that has been in Perl since 5.8.9/5.10.0!

In the debugger, the $^P variable is a bit mask used to control behaviour and flag which Perl content the debugger should save the content of for later reference.

The perlvar documentation lists 11 bits this variable (0x1 through to 0x400).

But this not true!

There's actually two more undocumented control bits (and a third in 5.10.1), and the first of these (0x800, which is off by default) is exactly what I need.

Adding the 0x800 flag to $^P will cause the debugger to save a copy of everything that it does a string eval on, so that when you later step into the generated code, you can see it in the debugger as you would any other code.

The idiomatic usage of the flag looks like this. do { local $^P = $^P | 0x800; eval $perl_string; }; The new Devel::Eval 0.02 release will continue to use the "do" approach on older Perls, but when it detects that it is on Perl 5.8.9 or later, it will automatically switch to this $^P approach.

Both approaches can be accessed directly via Devel::Eval::fval (for the file approach) and Devel::Eval::pval (for the $^P approach).


Hooray!

tgape on 2010-01-01T03:15:52

1. Thanks for the pointer on Devel::Eval. For those of us developing under rocks, that will come in very useful (as those extra bits aren't around in 5.6.1.)

2. Thanks for the info on those extra bits, as that'll be useful for my home development. I'm attempting to stay away from string evals, but they're so useful, I don't know how long I can hold out.

3. Thanks for the suggestion on how to sneak in a few more automated regression tests under a manager who doesn't want to "waste" time on them.