Following up on my threat of last week, I released Test::Wrapper on CPAN.
If you read my previous blog entry, you know that one of the big gotchas of the wrapping gymnastics I was doing was that it was utterly #@$%$# up Test::Builder’s internal states. Thus, at that point, it was either run TAP tests, or use Test::Wrapper
, but don’t do both at the same time. Not the most God-awful limitation ever, perhaps, but still not very cool.
Since then, I’ve taken a second look at the problem, and realized that this limitation can not only be overcome, but in a surprisingly easy manner.
The trick is to know that Test::Builder states are kept in a global object, $Test::Builder::Test
. Since all the information is kept there, for our meddling to become benign all we have to do is a classic “distract, switch, butcher & reinstate” maneuver:
# slightly simplified from Test::Wrapper's guts # $original_test is the full name of the test. # E.g. 'Test::More::like' my $wrapped = $original_test; # get the original test's coderef my $original_test_ref = eval '\&' . $original_test; # get its prototype my $proto = prototype $original_test_ref; $proto &&= "($proto)"; # okay, let's wrap that test... eval <<"END_EVAL"; sub $wrapped $proto { # magic! we make a local copy of $Test::Builder::Test # that we can mangle in every way we want local \$Test::Builder::Test = { %\$Test::Builder::Test }; my \$builder = bless \$Test::Builder::Test, 'Test::Builder'; # we change the testing plan to a single test (this one) \$builder->{Have_Plan} = 1; \$builder->{Have_Output_Plan} = 1; \$builder->{Expected_Tests} = 1; # we capture all the output channels my ( \$output, \$failure, \$todo ); \$builder->output( \\\$output ); \$builder->failure_output( \\\$failure); \$builder->todo_output( \\\$todo ); # call the original test, which will interact with our # modified $Test::Builder::Test \$original_test_ref->( \@_ ); # ... and harvest the output and populate an object with it return Test::Wrapper->new( output => \$output, diag => \$failure, todo => \$todo, ); # that's it! leaving the subroutine will make our # $Test::Builder::Test get out of scope, which # will un-hide the original $Test::Builder::Test # unaltered by what happened here } END_EVAL
And that, boys and girls, is the crux of Test::Wrapper
. The rest is just window-dressing and API goodness.
No comments