How to get at the subroutines within a program

jdavidb on 2007-05-14T14:48:57

So you have a program that contains routines which should really be in a library. You want to run those routines separately to test them. You don't want to run the program, of course. Or, worse, you want to run those routines for actual production use (you should be shot) without running the program.

I found out how to do this in the middle of the night a couple of weeks ago, from a presentation somebody linked to from here. Jotted down some notes, and now I'm putting it here so I can find it later. :)

require($program);
import qw(routine routine routine);

Or, for testing:

require_ok($program);
import qw(routine routine routine)
ok(routine(1, 2));

This just gets at what the use keyword does, internally.

Update: Okay, this is apparently wrong. :) I didn't try it out at all, though I may have need to at some point. I'm going to leave it here to remind me that this is theoretically possible, although given what dakkar pointed out, it may not be.

Which means somebody's slides were way off. :) (Or else I was completely asleep and didn't understand.)


self-import-ant?

merlyn on 2007-05-14T16:12:14

Not sure what you're doing there. The require is likely loading the program into the main namespace, just like you already are. The import is importing against main, from main. I bet if you remove it, it works exactly the same.

Except that it does not work...

dakkar on 2007-05-14T17:04:38

Let's say you have this prog.pl:

#!/usr/bin/perl

sub do_something {
    return 42;
}

print "Running\n";

and this test.pl:

#!/usr/bin/perl

require 'prog.pl';
print "ok 1\n" if do_something()==42;

Now, running test.pl produces:

Running
ok 1

because the require will execute the program. So, if the program dies because @ARGV is wrong, or somesuch, your test dies as well...

What you should do is change the program

bart on 2007-05-14T21:04:34

If at the top level, you check caller(), you'll find it is false if the script is run as a standalone program, and true if it's loaded as a library.

Thus, do this at the top level:

unless(caller) {
   # former top level
   print "Run as a script\n";
} else {
   # you may drop this if you don't need it
   print "Initializing as a library file\n";
}
It'll work now.

Re:What you should do is change the program

jdavidb on 2007-05-15T15:47:51

Thank you. Sounds like this advice was either present in the slides I read, and I missed it (it was middle of the night, up late hacking for work :) ), or else the presenter missed it or only mentioned it orally without including it in the slides.

It's a Method!

chromatic on 2007-05-14T21:15:02

Besides your update, import() is a method. When you call methods as functions in Perl, you don't always get what you want.

I think you'll have more luck checking caller() to see if someone has invoked your program directly.

Perhaps you heard about modulinos

brian_d_foy on 2007-05-15T14:34:53

You write your programs as modules, where the main script is actually wrapped in a subroutine (just like the good old days of int main(void)). Now a program is just a set of subroutines and you can use it like every other library of subroutines (including testing it).

I write about it in Chapter 18 of Mastering Perl

Here's how I did it

Phred on 2007-05-15T22:20:41

Hopefully it wasn't my slides that were way off ;-) Here's how I poked at the internals of a program, including global variables. I got the idea from another post here on use Perl, and I hacked out this working program and test.

phred@pooky ~ $ cat program.pl
#!perl -w

use strict;
use warnings;

our $var = 0;

sub addition {
    my ($one, $other_one) = @_;
    my $result = $one + $other_one+$var;
    return $result;
}

sub should_die {
    die "oops";
}

1;

phred@pooky ~ $ cat foo.t
#!perl

use strict;
use warnings FATAL => 'all';

use Test::More tests => 3;

my $class = 'program';
require_ok("$class.pl");
import $class qw( addition should_die);

no strict 'refs';
${"main\:\:var"} = 5;
my $method = 'addition';
cmp_ok($method->(1,1), '==', 7, '1+1+var returns 7');

$method = 'should_die';
eval { $method->() };
like($@, qr/oops/i, 'should_die() died');

Re:Here's how I did it

Phred on 2007-05-15T22:23:10

Oops, I forgot the output

phred@pooky ~ $ perl foo.t
1..3
ok 1 - require 'program.pl';
ok 2 - 1+1+var returns 7
ok 3 - should_die() died