On Fri, Jan 27, 2012 at 12:51:40PM +1000, Jason Galea wrote:
> Hi all,
> 
> I need to stop looking at this.. I have a headache now..
> 
> I'm messing around with building a Plack app and had some really odd
> behaviour which I tracked back and boiled down to the test cases below.
> (unfortunately the result was my app connecting to a separate dev database
> after creating an empty test database and telling me a user existed where I
> knew it didn't.. took me a while to get past that..)
> 
> anyhoo.. can someone please explain the oddities I'm seeing in these tests?
> 
> My::Builder works as expected until I call make_immutable on it, then it
> returns undef.
> 
> My::Builder2 is the same as My::Builder except
>  - the 'app' attribute is lazy.
>  - it's been made immutable
> it works as expected.
> 
> My:Builder3 is the same as My::Builder except
>  - it's been made immutable
>  - I renamed the 'app' attribute as 'attr2'..
> it works as expected.
> 
> My::Builder4 is the same as My::Builder except
>  - attr1 uses lazy_build
>  - it's been made immutable
> it fails, but it returns attr1's default value
> 
> please tell me I'm doing something stupid so I can stop trying to work it
> all out.. 8)
> 
> cheers,
> 
> J
> 
> use strict;
> use warnings;
> use Test::More;
> 
> {
>     package My::Builder;
>     use Moose;
> 
>     has attr1 => ( is => 'ro', default => 'default value' );
> 
>     has app => ( is => 'ro', builder => '_build_app' );
> 
>     sub _build_app{ shift->attr1; }
> 
>     sub web_app{
>         my $self = shift;
>         return sub { $self->app };
>     }
> 
> 
> 
>     package My::Builder2;
>     use Moose;
> 
>     has attr1 => ( is => 'ro', default => 'default value' );
> 
>     has app => ( is => 'ro', builder => '_build_app', lazy => 1 );
> 
>     sub _build_app{ shift->attr1; }
> 
>     sub web_app{
>         my $self = shift;
>         return sub { $self->app };
>     }
> 
>     __PACKAGE__->meta->make_immutable;
> 
> 
>     package My::Builder3;
>     use Moose;
> 
>     has attr1 => ( is => 'ro', default => 'default value' );
> 
>     has attr2 => ( is => 'ro', builder => '_build_attr2' );
> 
>     sub _build_attr2{ shift->attr1; }
> 
>     sub web_app{
>         my $self = shift;
>         return sub { $self->attr2 };
>     }
> 
>     __PACKAGE__->meta->make_immutable;
> 
> 
>     package My::Builder4;
>     use Moose;
> 
>     has attr1 => ( is => 'ro', lazy_build => 1 );
>     sub _build_attr1{ 'default value' }
> 
>     has app => ( is => 'ro', builder => '_build_app' );
> 
>     sub _build_app{ shift->attr1; }
> 
>     sub web_app{
>         my $self = shift;
>         return sub { $self->app };
>     }
> 
>     __PACKAGE__->meta->make_immutable;
> 
> }
> 
> my $app = My::Builder->new( attr1 => 'my value' )->web_app;
> is $app->(), 'my value', 'no make immutable or lazy ok';
> 
> $app = My::Builder2->new( attr1 => 'my value' )->web_app;
> is $app->(), 'my value', 'make_immutable with lazy ok';
> 
> $app = My::Builder3->new( attr1 => 'my value' )->web_app;
> is $app->(), 'my value', 'make_immutable without lazy breaks.. not!';
> 
> My::Builder->meta->make_immutable;
> $app = My::Builder->new( attr1 => 'my value' )->web_app;
> is $app->(), 'my value', 'make_immutable without lazy breaks (returns
> undef)';
> 
> $app = My::Builder4->new( attr1 => 'my value' )->web_app;
> is $app->(), 'my value', 'make_immutable without lazy breaks (returns
> default value)';
> 
> done_testing();
> 
> 
> $ prove -lv t/999-make-immutable-breaksit.t
> t/999-make-immutable-breaksit.t ..
> ok 1 - no make immutable or lazy ok
> ok 2 - make_immutable with lazy ok
> ok 3 - make_immutable without lazy breaks.. not!
> not ok 4 - make_immutable without lazy breaks (returns undef)
> 
> #   Failed test 'make_immutable without lazy breaks (returns undef)'
> #   at t/999-make-immutable-breaksit.t line 86.
> #          got: undef
> #     expected: 'my value'
> not ok 5 - make_immutable without lazy breaks (returns default value)
> 
> #   Failed test 'make_immutable without lazy breaks (returns default value)'
> #   at t/999-make-immutable-breaksit.t line 89.
> #          got: 'default value'
> #     expected: 'my value'
> 1..5
> # Looks like you failed 2 tests of 5.
> Dubious, test returned 2 (wstat 512, 0x200)
> Failed 2/5 subtests
> 
> Test Summary Report
> -------------------
> t/999-make-immutable-breaksit.t (Wstat: 512 Tests: 5 Failed: 2)
>   Failed tests:  4-5
>   Non-zero exit status: 2
> Files=1, Tests=5,  0 wallclock secs ( 0.02 usr  0.00 sys +  0.18 cusr  0.01
> csys =  0.21 CPU)
> Result: FAIL

This actually doesn't have anything to do with make_immutable. The issue
is that attributes without lazy are initialized in an arbitrary order,
so when the 'app' attribute is initialized (at 'new' time, since it
isn't lazy), it might be initialized before attr1, and it might be
initialized after. The only way to guarantee the order is to make app
lazy. A good rule of thumb is that any attribute whose default or
builder depends on the value of another attribute should be lazy.

-doy

Reply via email to