Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Overriding a module that is used by a program I'm testing

by Narveson (Chaplain)
on Dec 08, 2010 at 05:29 UTC ( [id://875943]=perlquestion: print w/replies, xml ) Need Help??

Narveson has asked for the wisdom of the Perl Monks concerning the following question:

I am revising a Perl program and I wanted a test harness that could run the original version of the program (call it launch_rockets.pl) and collect the standard output, but somehow skip the system calls that occur inside launch_rockets.pl. The following code successfully overrides system inside launch_rockets.pl:

use subs qw(system); my $SYSTEM_SUCCESS = 0; sub system { print "***\n"; print "system @_\n"; print "***\n\n"; return $SYSTEM_SUCCESS; } local @ARGV = @test_args; do 'launch_rockets.pl';

So far so good. But launch_rockets.pl also contains

use Proc::Background;
and later
Proc::Background->new('perl', 'launch_missiles.pl');

I could copy launch_rockets.pl into a sandbox where Proc::Background is replaced by a stub, but I was wondering if there was any override strategy that would be effective inside a do FILE call in the file's original environment.

Replies are listed 'Best First'.
Re: Overriding a module that is used by a program I'm testing
by cdarke (Prior) on Dec 08, 2010 at 08:40 UTC
    One way would be to place a hook (subroutine reference) as the first element of @INC. This is called when use is executed, and passed two parameters: the reference itself and the module name. See require in perlfunc.

    There are also other possibilities, such as a blessed object.
Re: Overriding a module that is used by a program I'm testing
by dsheroh (Monsignor) on Dec 08, 2010 at 10:26 UTC
    You could also stomp around in Proc::Background's namespace. Generally a bad idea, but it can be useful in cases like this.
    use Proc::Background; # Make sure it's loaded at compile-time package Proc::Background; no warnings 'redefine'; sub new { do_stubby_stuff(@_) } package main; # Go about your testing business
    This will replace Proc::Background::new with your own version, which can do whatever works best for your tests. Note, though, that this doesn't allow for using SUPER to access the original version of the replaced sub(s) because it's redefining the existing sub, not overriding them in proper OO fashion.

      Wow! The called program is going to load the module, so pre-empt it by loading it myself! I liked this and used it. Thanks!

      As for your warning about how this isn't a proper OO override: noted, but in this particular setting I am aiming for total suppression. In fact I went and condensed your suggestion to the following:

      # pre-empt the loading use Proc::Background; { no warnings 'redefine'; *Proc::Background::new = sub {}; }

      having realized that my stub of choice was a no-op.

        I don't get how that works. use() isn't require(), that is, won't the test code reload Proc::Background and install its version of Proc::Background::new in the symbol table, overwriting the sub reference you installed in the symbol table for Proc::Background::new?

        edit: Never mind. The following are equivalent:

        use Proc::Background #and BEGIN { require Proc::Background; Proc::Background->import; }

        So the second use/require won't do anything.

        Also, couldn't you simply perform a substituion (s///) on launch_rockets.pl and delete the offending lines?

      I like this solution. I needed to do something similar today as it happens. I wanted to replace only certain methods, so I put a copy of the CPAN module in a Replacement subdirectory, complete with its parent directory, amended it and:
      use library '/home/simon/lib/Replacement'; use Replacement::Some::Module; ...

      One world, one people

        I needed to do something similar myself about a month ago, too... The result was LocalOverride, which hooks into the require mechanism (by putting a coderef into @INC, as cdarke suggested above) to accomplish what you've described transparently. With the default configuration, LocalOverride would cause
        use Some::Module;
        to more-or-less turn into
        use Some::Module; use Local::Some::Module;
        ("More-or-less" because Some::Module::import runs only after both versions are loaded, not once after the base module loads and again after the local modifications load.)

        Also note that, with this technique, you don't need to use a complete copy of the base module as a baseline. So long as you load both Some::Module and then Replacement::Some::Module, you only need to include changed subs in Replacement::Some::Module.

Re: Overriding a module that is used by a program I'm testing
by belden (Friar) on Dec 09, 2010 at 22:06 UTC
    Test::Resub handles this. We use this extensively in our test suites at work. In your test:
    #!/usr/bin/perl use Your::Module; use Proc::Background; use Test::Resub qw(resub); # disable Proc::Background->new for the scope of this block { my $fake_new = sub { return bless {}, 'Proc::Background'; # or whatever }; my $resub = resub 'Proc::Background::new', $fake_new; # continue on your merry way, content knowing that # your $fake_new is getting called. } # scope of $resub object ends, its DESTROYER restores # the original Proc::Background::new.
    Probably you'd want to have a single resub ofProc::Background::new up high in your test, so you don't need to keep creating the resub.

    Test::Resub allows you to capture the arguments that go to your resubbed code. For example, consider this helper function

    package My::Module; use IO::Socket::INET; ... sub open_socket_to { my ($class, %args) = @_; return IO::Socket::INET->new( PeerAddr => $args{machine}, PeerPort => $args{port}, Proto => $args{protocol} || 'tcp', Type => SOCK_STREAM, ); }
    Not only would it be nice to prevent the test from actually creating sockets, it would be good to know how we're creating those sockets. The test might look like this

    use Test; ... use My::Module; use Test::Resub qw(resub); use IO::Socket::INET; # test open_socket_to { my $rs = resub 'IO::Socket::INET::new', sub { 'a socket' }, captur +e => 1; my $socket = My::Module->open_socket_to( machine => 'example.com', port => '-29', ); # return value of ->open_socket_to is whatever IO::Socket::INET::n +ew gives us ok( $socket, 'a socket' ); # We created the socket correctly - the fact that we provided an # illegal PeerPort doesn't need to be handled by this test: # IO::Socket::INET should have his own tests for his ->new. is_deeply( $rs->named_method_args, [{ PeerAddr => 'example.com', PeerPort => -29, Proto => 'tcp', Type => SOCK_STREAM, }] ); }
    Sometimes you might find you want to allow the original code to run, but you want to eavesdrop in on the arguments that get sent in to it. Test::Wiretap handles that.

    You can have multiple resubs and wiretaps in place for the same subroutine: they'll stack properly and unroll correctly.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://875943]
Approved by Corion
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others browsing the Monastery: (2)
As of 2024-04-25 20:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found