Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Prototypes required even after mocking a sub

by stevieb (Canon)
on Sep 21, 2018 at 19:11 UTC ( [id://1222807]=perlquestion: print w/replies, xml ) Need Help??

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

So, I had a user of my Mock::Sub distribution file an issue report where trying to mock a subroutine that has prototypes threw a warning. After monkeying about with it, I was able to quell said warnings, but it raised something else that I don't understand. I'll post some code and context, then get to what I'm hoping to have answered.

This is a runnable example based on the snippet that the user reported:

use warnings; use strict; use Mock::Sub; my $m = Mock::Sub->new; sub foo ($$$){ return undef; } my $foo_sub = $m->mock('foo');

Result:

Prototype mismatch: sub main::foo ($$$) vs none at /home/stevieb/perl5 +/perlbrew/perls/perl-5.26.1/lib/site_perl/5.26.1/Mock/Sub/Child.pm li +ne 122. Prototype mismatch: sub main::foo: none vs ($$$) at /home/stevieb/perl +5/perlbrew/perls/perl-5.26.1/lib/site_perl/5.26.1/Mock/Sub/Child.pm l +ine 140.

All well and good. As a response to the user, I drummed up a test script with a signal handler to catch Prototype warnings, and just evaporate them (I'll incorporate this into the distribution directly if the reporter is satisfied, enabled only if requested explicitly):

use warnings; use strict; use feature 'say'; use Mock::Sub; $SIG{__WARN__} = sub {say $_[0] if $_[0] !~ /Prototype/}; my $m = Mock::Sub->new; sub foo ($$$){ say "hello!"; # doesn't get called return undef; } my $foo = $m->mock('foo'); $foo->return_value('test'); say foo(1, 2, 3); say foo(1, 2, 3); say foo(1, 2, 3); say $foo->called_count;

That code does the right thing, insofar that the mocked sub is properly called and the prototype warnings are no longer displayed:

test test test 3

Now, what I found while testing, is that initially, I called foo() with no parameters, but the prototype stuck, resulting in fatality. Instead of foo(1, 2, 3); (proper number of params), I had just foo(), and...

Not enough arguments for main::foo at mock.pl line 21, near "()" Execution of mock.pl aborted due to compilation errors.

All I do when I mock out a sub, is overwrite the symbol table for it (actual code from the module):

{ no strict 'refs'; no warnings 'redefine'; my $mock = $self; weaken $mock; *$sub = sub { @{ $mock->{called_with} } = @_; ++$mock->{called_count}; if ($mock->{side_effect}) { if (wantarray){ my @effect = $mock->{side_effect}->(@_); return @effect if @effect; } else { my $effect = $mock->{side_effect}->(@_); return $effect if defined $effect; } } return if ! defined $mock->{return}; if ($mock->{return}[0] && $mock->{return}[0] eq 'params'){ return ! wantarray ? $_[0] : @_; } else { return ! wantarray && @{ $mock->{return} } == 1 ? $mock->{return}[0] : @{ $mock->{return} }; } }; }

My question here, is if the symtab entry was overwritten correctly (ie. the mocked sub is most definitely called as desired), why does perl still think that it requires the prototyped parameters? Clearly, that information is stored somewhere, but where and how?

Can someone point me in a direction I can look down to understand this, or even explain it to me?

Thanks, as always,

-stevieb

Replies are listed 'Best First'.
Re: Prototypes required even after mocking a sub
by Anonymous Monk on Sep 21, 2018 at 20:28 UTC
    The prototype is used by the parser itself to validate the number of parameters at compile time. It's likely your mocked override was not installed before this happened, if it was installed at runtime.

      Thank you.

      I did some preliminary and very basic quick tests to do the work in the compile phase, but to no avail. I will put more effort into seeing how I may be able to do this.

        A recent thread 1215668 discussed the difficulty of overriding a function called by a module. The solution required executing the override before compiling the module. The details may apply to your problem.
        Bill
        In order for it to happen in the compile phase, the user must call your code (both loading your module and executing the sub override) in that file's compile phase, e.g. in a BEGIN block or use declaration.
Re: Prototypes required even after mocking a sub
by haukex (Archbishop) on Sep 24, 2018 at 20:16 UTC

    If you set the prototype of your mock sub before replacing the original one, that should get rid of the warning, try commenting out the set_prototype call in the following and you should get the "Prototype mismatch" warning back:

    use warnings; use strict; use Scalar::Util qw/set_prototype/; sub foo ($$$) { print "foo(@_)\n" } foo(1,2,3); # prints "foo(1 2 3)" { my $name = "foo"; my $sub = sub { print "bar(@_)\n" }; set_prototype(\&$sub,prototype($name)); #use Devel::Peek; Dump($sub); no strict 'refs'; no warnings 'redefine'; *$name = $sub; } foo(4,5,6); # prints "bar(4 5 6)"

    By the way, in the code you showed, you're disabling strict 'refs' for a fairly large block of code.

Re: Prototypes required even after mocking a sub
by Anonymous Monk on Sep 21, 2018 at 20:30 UTC
    It would probably be better to just set no warnings 'prototype'; in the scope where you override the subroutine, than messing with the global __WARN__ handler which many applications already are using.

      I really don't like using sig handlers on a large scale for the reason you stated. I try to limit scope as best as possible.

      The no warnings 'prototype'; is one that I didn't come across (I'll be honest, I didn't dig deep enough). I can make that user-selectable, so that users have to turn that off explicitly so there's no confusion (barf first, re-read docs, then disable).

      That said, I'm still uncertain how this works. I desire to know *why* the prototypes exist after I've over-written the symtab entry for the subroutine.

        The specific warning you're talking about here is emitted when you override the sub with a different prototype. https://perldoc.pl/perldiag#Prototype-mismatch:-%25s-vs-%25s (making this a link does not work on this dumb website.)

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (3)
As of 2024-04-19 20:23 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found