http://www.perlmonks.org?node_id=852746

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

We have a monster script that ought to be refactored, but since that's not going to happen, I wanted to at least write some regression tests for how it currently works. One saving grace is that monster.pl does a lot of its work in subroutines. Is there a way I can pull all these subroutine definitions into my test script without running monster.pl?

I can't use use or require or even do, because monster.pl explicitly exits.

Of course I could copy the subs into another file, but I want my test script to run against the current live version.

Of course the subs ought to be placed in one or more modules in separate files, but take my word for it, this is not going to happen.

How does perl -c monster.pl do what it does? Somehow it compiles the code without running it. How can I do the same thing?

Update

Thanks for all these answers!

The key idea seems to be to slurp the file and then wrap the contents in some construct that cancels the run-time phase.

In fact, I've just thought of my own variation (untested):

my $prog_code; my $test_code; # slurp monster.pl into $prod_code # slurp a test suite into $test_code eval "CHECK {$test_code} $prog_code";

Replies are listed 'Best First'.
Re: How to load the subs without running the script
by repellent (Priest) on Aug 03, 2010 at 21:31 UTC
    Quick and dirty: modify monster.pl and wrap the entire code in a function - i.e. put it in sub run { <all code goes here> }. Then do monster.pl;

    Scripts as Modules makes an interesting read.
Re: How to load the subs without running the script
by snoopy (Curate) on Aug 03, 2010 at 22:49 UTC
    How about returning up front? Say for example, if monster.pl contains:
    #!/usr/bin/perl sub mysub {print "I want to see this\n"}; print "but I don't want to see this\n"; exit; # don't want to exit either
    Then:
    #!/usr/bin/perl use warnings; use strict; my $prog_code = do {local $/ = undef; open (my $fh, '<', 'monster.pl') or die "open error: $!"; <$fh>}; eval 'return;'.$prog_code ; die "eval error: $@" if $@; warn "trying mysub"; mysub();
    The return gets executed as soon as compilation has finished, avoiding execution of the main body:
    perl do.pl trying mysub at do.pl line 13. I want to see this Compilation finished at Wed Aug 4 10:08:26

    Update: Removed intermediate subroutine and added sample output.

      Nice. I especially like your helpful summary of monster.pl. I should have included something like it, but your version abstracts the problem better than I could have done.

Re: How to load the subs without running the script
by jethro (Monsignor) on Aug 03, 2010 at 22:32 UTC

    Another possibility: Override exit() before you do or eval monster.pl.

    > perl -e ' use subs qw(exit); sub exit {}; exit(1); print "yes\n";' yes

    There is more than one way to override a builtin function in perl, this one is described in http://modperlbook.org/html/6-4-1-exit.html

Re: How to load the subs without running the script
by pemungkah (Priest) on Aug 04, 2010 at 00:19 UTC
    perl -c manages because the Perl interpreter itself is getting control before execution starts at all and then just exiting.

    You can use the debugger hooks to do this if you don't want any of the code in monster.pl to execute at all.

    If you create a sub called DB::DB, Perl will enter it just as soon as the code has compiled but before the first statement executes. You can then do what you like with the subs back in main:: (the namespace of the monster.pl script, assuming it doesn't have internal packages).

    Try something like this: in a new file, Devel::Stop:

    package Devel::Stop; use strict; use warnings; sub DB::DB { # Execute the code you want to run here my $result = main::foo(); print STDERR "1..2\n"; print STDERR ((defined $result ? "ok 1" : "not ok 1")," - got a valu +e\n"); print STDERR (($result eq "I am foo" ? "ok 2" : "not ok 2"), " - rig +ht value\n"); exit(0); } 1;
    and you'll get
    joe-desk-> perl -d:Stop xxx 1..2 ok 1 - got a value ok 2 - right value
    DB::DB gets called as soon as the code is loaded and compiled (including use statements in the main program). Since it calls exit before it returns, control never returns to the program being "debugged".

    Run this like this:

    perl -d:Stop monster.pl
    Exercise for the reader to make the mechanism more flexible so you don't have to write a new package for every test, though a new package for every test is better than no tests at all...

    Note that Test::Simple won't work here: it runs stuff in BEGIN and END blocks, and confuses this ultra-simple debugger. I'd recommend either writing your own little library to handle the ok, like, etc. functions, or beefing up the logic in DB::DB to allow the Test:: code to run but not anything in main::.

    This is meant to demonstrate a direction you can go rather than be a complete solution; there's a lot of potential here, accompanying a lot of potential "why did THAT happen?" situations.

Re: How to load the subs without running the script
by aquarium (Curate) on Aug 04, 2010 at 00:32 UTC
    there are also perl testing frameworks...so you may not need to modify code to regression test monster.pl
    the hardest line to type correctly is: stty erase ^H