published on in Perl Programming perlimports

Detective Work with perlimports

Today I was working on a test which uses Test::WWW::Mechanize. I was looking at the source of the text_contains method, which currently looks like:

sub text_contains {
    my $self = shift;
    my $str  = shift;
    my $desc = shift || qq{Text contains "$str"};

    local $Test::Builder::Level = $Test::Builder::Level + 1;
    if ( ref($str) ) {
        return $TB->ok( 0, 'Test::WWW::Mechanize->text_contains called incorrectly.  It requires a scalar, not a reference.' );
    }

    return contains_string( $self->text, $str, $desc );
}

I was interested in the return value, so I grepped the file for sub contains_string. That search came up empty. I then had a very quick look at the module imports and I didn’t see any reference to contains_string there either. As a next step, I thought it would be fun to use perlimports to solve the problem for me.

git clone git@github.com:petdance/test-www-mechanize.git
cd test-www-mechanize
cpm install -g --cpanfile cpanfile
perlimports -f Mechanize.pm -i
git diff

Note that I used cpm to ensure that all of the modules prerequisites were installed locally. perlimports needs them installed in order to inspect them for possible exports.

The output of the diff is:

diff --git a/Mechanize.pm b/Mechanize.pm
index 8c57431..04c7191 100644
--- a/Mechanize.pm
+++ b/Mechanize.pm
@@ -66,10 +66,22 @@ results in

 use HTML::TokeParser ();
 use WWW::Mechanize ();
-use Test::LongString;
+use Test::LongString qw(
+    contains_string
+    is_string
+    lacks_string
+    like_string
+    unlike_string
+);
 use Test::Builder ();
 use Carp ();
-use Carp::Assert::More;
+use Carp::Assert::More qw(
+    assert_arrayref
+    assert_in
+    assert_is
+    assert_isa
+    assert_nonblank
+);

 use parent 'WWW::Mechanize';

There we go! https://metacpan.org/pod/Test::LongString exports contains_string.

Doing this the old fashioned way would have involved

  • grepping the code for modules with implicit imports. A good starting place would have been the use statements at the top of the file.
use HTML::TokeParser ();
use WWW::Mechanize ();
use Test::LongString;
use Test::Builder ();
use Carp ();
use Carp::Assert::More;
  • There I would have seen that Test::LongString and Carp::Assert::More don’t have any parens, so they might import some symbols.
  • I would have checked the documentation of these modules until I found what I was looking for.

In this case there’s not a huge amount of overhead in doing this manually, but if you’re in a hurry, you may miss that Test::LongString allows for an implicit import. Using perlimports takes some of the eyeball work out of the equation.

As a bonus, I can take the diff and submit it as a pull request. (The following example uses gh to manage the interactions with GitHub).

gh repo fork
git add -p
git commit -m "Make implicit imports explicit"
git push origin
gh pr create

Thanks to Andy Lester for the lightning fast merge on the pull request. 🚀

Edit: After this post was first published, Matt Trout pointed out that he had written Devel::Wherefore to solve this same problem. It’s good to have options!