views:

3065

answers:

4

I'm working on a test framework in Perl. As part of the tests, I may need to add precondition or postcondition checks for any given test, but not necessarily for all of them. What I've got so far is something like:

eval "&verify_precondition_TEST$n";
print $@ if $@;

Unfortunately, this outputs "Undefined subroutine &verify_precondition_TEST1 called at ..." if the function does not exist.

How can I determine ahead of time whether the function exists, before trying to call it?

+14  A: 
Package::Name->can('function')

or

*Package::Name::function{CODE}

# or no strict; *{ "Package::Name::$function" }{CODE}

or just live with the exception. If you call the function in an eval and $@ is set, then you can't call the function.

Finally, it sounds like you may want Test::Class instead of writing this yourself.

Edit: defined &function_name (or the no strict; defined &{ $function_name } variant), as mentioned in the other answers, looks to be the best way. UNIVERSAL::can is best for something you're going to call as a method (stylistically), and why bother messing around with the symbol table when Perl gives you syntax to do what you want.

Learning++ :)

jrockway
Unfortunately I'm doing this outside any package, so I get: Can't locate object method "can" via package "main". Also, I'm using an ancient version of Perl (5.002) in an environment where I have absolutely no Perl modules installed.
Greg Hewgill
Whoa, 5.002? I think that came out before I was born :)
jrockway
Isn't 5.002 the one with all of the buffer overflow problems? :)
brian d foy
According to perlhist, 5.003 is 5.002 with security fixes.
jrockway
(And, they didn't have UNIVERSAL:: back then, apparently. Which is why main->can doesn't work; it works fine under 5.10.)
jrockway
@Greg Hewgill you are never outside of a package. Try main->can("function")
Chas. Owens
+5  A: 

With defined:

if (eval "defined(&verify_precondition_TEST$n)") {
    eval "&verify_precondition_TEST$n";
    print $@ if $@;
}
else {
    print "verify_precondition_TEST$n does not exist\n";
}

EDIT: hmm, I only thought of eval as it was in the question but with symbolic references brought up with Leon Timmermans, couldn't you do

if (defined(&{"verify_precondition_TEST$n)") {
    &{"verify_precondition_TEST$n"};
    print $@ if $@;
}
else {
    print "verify_precondition_TEST$n does not exist\n";
}

even with strict?

Timo Metsälä
This is the wrong eval. You want block eval, not string eval. Plus testing if a function exists can be done with an eval anyway.
Dave Rolsky
I need to use string eval because $n is not known at compile time.
Greg Hewgill
No you don't. You need to use symbolic references. See my answer for an example of how to approach them.
Leon Timmermans
You're right, defined is exempt from strict refs. However, calling a sub isn't. You have to either do 'no strict' or use an eval, the former being cleaner IMNSHO
Leon Timmermans
+10  A: 
sub function_exists {    
    no strict 'refs';
    my $funcname = shift;
    return \&{$funcname} if defined &{$funcname};
    return;
}

if (my $subref = function_exists("verify_precondition_TEST$n") {
    ...
}
Leon Timmermans
+1 (no strict is always better than string eval)BTW, tiny typo: s/$functname/$funcname/
jrockway
I'd write that as `no strict 'refs'` just to note to the reader that you're about to do some symbolic reference magic. :)
brian d foy
Yeah, good point.
Leon Timmermans
A: 

I had used Leon's approach, but when I had multiple packages, it failed. I'm not sure precisely why; I think it relates to the propagation of scope between namespaces. This is the solution I came up with.

my %symbols = ();
my $package =__PACKAGE__; #bring it in at run-time
{
    no strict;
    %symbols = %{$package . "::"}; #See Symbol Tables on perlmod
}
print "$funcname not defined\n" if (! defined($symbols{$funcname});

References:
__PACKAGE__ reference on the perlmod page.

Packages/__PACKAGE__reference on Perl Training Australia.

Paul Nathan