Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

How do I localize a value to the calling function's scope?

by Ytrew (Pilgrim)
on Oct 14, 2004 at 19:14 UTC ( [id://399300]=perlquestion: print w/replies, xml ) Need Help??

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

I'm doing some testing, and am trying to test each function in isolation, as much as is possible. In many cases, when testing a function that calls other, complex functions, I just want to call "stub functions" with known outputs. This simplifies my testing. I know that I can locally override the functions that I want (within the framework of a single test function), by using code like the following:
sub test_foo { my $temp; $temp = $^W; # save warning flag status $^W=0; # ignore "function is redefined" warning local *module::bar=\&stub_for_bar; $^W = $temp; # restore warnings # test code for module::foo() goes here }
I'd like to save myself some typing, and create a macro that overrides a given function with a given stub, local to the current scope. I see it looking something like this:
sub test_foo { override_with_stub_fn('module::bar',\&stub_for_bar); # test code for module::foo goes here }
I can't think of a way to do this in perl, however. If I try to move the code into a function, the local() command has the wrong scope. If there were a way to make it run in the parent's scope, (like TCL's uplevel() command), the problem would be solved, but I don't know of a way to do that. Is there some tricky caller/AUTOLOAD/goto trick I can do to accomplish this? I'd just as soon not use the -P (C-preprocessor) option if I can avoid it.

-- Ytrew

Replies are listed 'Best First'.
Re: How do I localize a value to the calling function's scope?
by blokhead (Monsignor) on Oct 14, 2004 at 19:24 UTC
    You can send back an object and store it in a lexical. Then when it goes out of scope, the object gets DESTROYed, you can restore the old value:
    package Localize; sub new { my ($self, $sub, $coderef) = @_; no strict 'refs'; local $^W; my $oldval = \&$sub; *$sub = $coderef; bless [ $sub, $oldval ], $self; } sub DESTROY { my $self = shift; no strict 'refs'; local $^W; my ($sub, $coderef) = @$self; *$sub = $coderef; } ############# package main; sub foo { print "Real foo!\n"; } foo(); { my $l = new Localize "main::foo", sub { print "Fake foo!\n" }; foo(); } foo(); __END__ Real foo! Fake foo Real foo!
    This is similar to what Hook::LexWrap does, but it replaces the entire sub instead of adding pre/post wrappers.

    Update: Speaking of this problem, I was wondering if anyone savvy with the internals can say whether or not it's possible for a sub to place a (anonymous) lexical onto the caller's pad, so that you can set up a callback via DESTROY for when the calling scope ends? I think this would be (insane but) cute -- you could even make a version of local for lexicals.

    blokhead

Re: How do I localize a value to the calling function's scope?
by dragonchild (Archbishop) on Oct 14, 2004 at 19:20 UTC
    I think you're approaching the problem from the wrong angle. If you want to test each function in isolation (which is a good thing), have each function appear in isolation. For example, if you have your functions in a library that uses Exporter, provide a way to export each function by itself. If you have an object method, create an class (on-the-fly, perhaps) that inherits from the object you want to test and that overrides every other method, leaving you just the one method to test.

    Either way you go about it, you now have the ability to create the appropriate stubs, either in the namespace you export into or in the class that inherits from the class you're testing.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: How do I localize a value to the calling function's scope?
by Zaxo (Archbishop) on Oct 14, 2004 at 19:56 UTC

    You can do that with local:

    sub test_foo { no warnings 'redefine'; local *Module::bar = \&stub_for_bar; # test code for Module::foo goes here }
    You have that in your list, but it's no more typing than your function call, and it does what you want. Wherever foo calls bar, the stub is called.

    Lexical warnings.pm saves a lot of trouble.

    After Compline,
    Zaxo

      Thanks, that certainly helps.

      But I'd still prefer a single subroutine call for documentation purposes. I can then document the perl idiom used to redefine a subroutine, explain how it works, and what it's supposed to do, right in the perldoc, all in one place.

      Using a non-obvious idiom repeatedly throughout my tests may well confuse a junior programmer assigned to maintain it. Redundantly documenting the idiom wastes space and time. Documenting the idiom in a overview section at the start of the test script is probably what I'll end up doing, but I was trying to avoid it.

      On the other hand, just using local and typeglobs to override seems like the simplest workable solution right now.

      Oh, well... as the Rolling Stones said, "you can't always get what you want" (even with Perl). *sigh*

      -- Ytrew Q. Uiop

Re: How do I localize a value to the calling function's scope?
by cLive ;-) (Prior) on Oct 14, 2004 at 20:37 UTC
    A small note that's bitten me before. Did you know that the use warnings pragma doesn't use $^W, so you'd have to turn warnings off explicitly as well.

    I'm not sure what the best practice is here, but it did bite me big when I missed that point.

    .02

    cLive ;-)

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others chanting in the Monastery: (7)
As of 2024-04-19 12:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found