use warnings;
use strict;
use lib '.';
#use Count;
print One::foo();
{
#my $count = Count->new;
my $bar = Count->mock;
print One::foo();
}
print One::foo();
BEGIN{
package One;
sub foo {
return "foo\n";
}
1;
package Count;
sub new {
return bless {}, shift;
}
sub unmock {
my $self = shift;
*One::foo = \&{ $self->{sub} };
}
sub mock {
my $thing = shift;
my $self;
if (ref($thing) eq __PACKAGE__){
$self = $thing;
}
else {
$self = bless {}, __PACKAGE__;
}
use Scalar::Util 'weaken';
weaken $self;
$self->{sub} = \&One::foo;
{
no warnings 'redefine';
*One::foo = sub { $self->{x} = 'x'; return "baz\n"; };
}
return $self;
}
sub DESTROY {
my $self = shift;
print "destroying...\n";
$self->unmock;
}
1;
}
outputs
foo
Use of uninitialized value in subroutine dereference at script.pl line
+ 30.
destroying...
baz
baz
(in cleanup) Unable to create sub named "" at script.pl line 30.
If you weaken $self then you immediately lower the reference count to 0. If you copy $self to $closure_self then you raise the count to 2, then lower it to 1. $self does not go out of scope until after the return, at which point you've passed an external reference to your main block and thus still have a ref count of 1.
I ran your posted code under ActiveState 5.20, and got the output
foo
destroying...
baz
baz
and if I swap the chunk in question to
my $closure_self = $self;
weaken $closure_self;
$self->{sub} = \&One::foo;
*One::foo = sub { $closure_self->{x} = 'x'; return "baz\n"; };
return $self;
I get
foo
baz
destroying...
foo
Your code cannot restore the old function, because your object was out of scope before your assignment. This could be demonstrated by using Data::Dump in DESTROY:
sub DESTROY {
use Data::Dump;
my $self = shift;
print "destroying...\n";
print dd($self), "\n";
$self->unmock;
}
which, for your code, outputs
foo
destroying...
bless({}, "Count")
1
baz
baz
and for mine
foo
baz
destroying...
bless({ sub => sub { ... }, x => "x" }, "Count")
1
foo
You will also note you'll start getting the errors and warnings I posted if you put strict and warnings in your modules instead of just in your script.
This all raises the point that unmock is actually written terribly, because it creates a whole additional closure about $self. It should really be
sub unmock {
my $self = shift;
*One::foo = $self->{sub};
}
so that you restore the original sub reference.
#11929 First ask yourself `How would I do this without a computer?' Then have the computer do it the same way.
|