Git::CPAN::Patch Gets A Bit More Magic

February 11th, 2014
PerlGit::CPAN::Patch

Git::CPAN::Patch Gets A Bit More Magic

When you’ll read this, a new version of Git::CPAN::Patch will soon be arriving on CPAN. This new version adds two new things that will make peeps squeal in glee (and indubitably breaks a few other things, but we’ll deal with that later on).

It’ll Find Git Repositories For You

Watch:

$ git cpan clone Web::Query
Git repository found: git://github.com/tokuhirom/Web-Query.git
creating Web-Query
From git://github.com/tokuhirom/Web-Query
 * [new tag]         0.01       -> 0.01
 * [new tag]         0.02       -> 0.02
 * [new tag]         0.03       -> 0.03
 * [new tag]         0.04       -> 0.04
 * [new tag]         0.05       -> 0.05
 ...

Yeah. That’s right. If there is a git repository associated with the module, git-cpan will clone that by default. If there is none, or if you want to clone from the CPAN releases, you can still do that too (and thanks to Metacpan magic, we don’t have to think about BackPAN either):

$ git cpan clone --norepository Web::Query
creating Web-Query
created tag 'v0.40.0' (c50356722926db2c29437b63d641cab8be89e3ff)
created tag 'v0.60.0' (3a8e599c7ab50216a20700f2b49c115fbb466b32)
...

Only want to create the repository with the latest release? Okay:

$ git cpan clone --latest --norepository Web::Query
creating Web-Query
created tag 'v0.240.0' (bf900cb6133ad2b1c01c5c2a6d04b1367f1c583d)

Tests!

That one is less of a treat for the end-users and more for fellow devs. Git::CPAN::Patch has long been without tests because it almost has everything that is a pain in the butt to interact with in a testing context. It’s a command-line tool, working on local git repositories and interacting with elements on the network. Only interactions with a database is missing to make the misery picture complete. But with the use of judicious tools, that pain can be made bearable.

Take the command line tool out of the command line

Git::CPAN::Patch is based on MooseX::App, which helps a lot with this. Indeed, since everything is classes underneath, we can go straight for that. If we want to run

$ git cpan import --root /tmp Git-CPAN-Patch 

we can forgo the gymnastics of external processes and just stay within the comfy confines of Perl:

my $command = Git::CPAN::Patch::Command::Import->new(
    root => $root,
    thing_to_import => 'Git-CPAN-Patch',
);

# will run the command just as good as from the command line
$command->run;

Bonus points: we now also have the $command object with which we can now test individual methods, inspect internal states, etc.

Fake the Cloud

Again, thanks to the fact that it’s all classes underneath, we can engineer things such that it’s easy to replace our agents to the outside world by things that are more test-friendly. For that kind of subterfuge, I usually like to reach out for Test::MockObject:

use Test::MockObject;

my $metacpan = Test::MockObject->new
    ->set_false( 'module' )
    ->mock( 'release', sub {
        return { hits => {
            hits => [ {
                fields => {
                    name => 'Git-CPAN-Patch',
                    author => 'YANICK',
                    date => '2011-03-06T01:02:03',
                    download_url => './t/corpus/Git-CPAN-Patch-0.4.5.tar.gz',
                    version => '0.4.4',
                }
            } ],
        }}
    } );


my $command = Git::CPAN::Patch::Command::Import->new(
    root => $root,
    thing_to_import => 'Git-CPAN-Patch',
    metacpan => $metacpan,
);

And with that, no network connection to worry about. We also have total control of what the agent passes to the object, ideal to fake errors, special cases and anything nasty we can come up with.

Fake the Ground You Walk In

Well, okay, don’t fake the ground, but at least create a remote island that won’t bother anybody else via File::Temp:

use File::Temp qw/ tempdir /;
use Git::Repository 'AUTOLOAD';

# create a disposable directory under ./t
# no cleanup because I'm still in debug mode
my $root = tempdir( 'repo_XXXX', CLEANUP => 0, DIR => './t' );

# create the test git repo and its handler
Git::Repository->run( init => $root );
my $git = Git::Repository->new( work_tree => $root );

note "git directory: $root";

Git::CPAN::Patch::Command::Import->new(
    root => $root,
    thing_to_import => 'Git-CPAN-Patch',
    metacpan => $metacpan,
)->run;

# the command ran, so now we should be able to see what it did

is_deeply [ $git->branch( '-a' ) ] => [ '  remotes/cpan/master' ], 
    "branch is there";

is_deeply [ $git->tag ] => [ 'v0.4.4' ], "tag is there";

... and so on, and so forth ...

So yeah, from horrible pain in the tuckus to a much more ameniable few lines of fixture fixin’ per test… there’s really no excuse anymore for a total lack of tests.