Add tests for inline {con,de}structor warnings and inlining. Got rid
[gitmo/Class-MOP.git] / t / 310_inline_structor.t
1 use strict;
2 use warnings;
3
4 use Test::More;
5
6 BEGIN {
7     eval "use Test::Output;";
8     plan skip_all => "Test::Output is required for this test" if $@;
9     plan 'no_plan';
10 }
11
12 use Class::MOP;
13
14 {
15     package HasConstructor;
16
17     sub new { bless {}, $_[0] }
18
19     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
20
21     $meta->superclasses('NotMoose');
22
23     ::stderr_like(
24         sub { $meta->make_immutable },
25         qr/\QNot inlining a constructor for HasConstructor since it defines its own constructor.\E\s+\QIf you are certain you don't need to inline your constructor, specify inline_constructor => 0 in your call to HasConstructor->meta->make_immutable\E/,
26         'got a warning that Foo will not have an inlined constructor because it defines its own new method'
27     );
28
29     ::is(
30         $meta->find_method_by_name('new')->body,
31         HasConstructor->can('new'),
32         'HasConstructor->new was untouched'
33     );
34 }
35
36 {
37     package My::Constructor;
38
39     use base 'Class::MOP::Method::Constructor';
40
41     sub _expected_method_class { 'Base::Class' }
42 }
43
44 {
45     package No::Constructor;
46 }
47
48 {
49     package My::Constructor2;
50
51     use base 'Class::MOP::Method::Constructor';
52
53     sub _expected_method_class { 'No::Constructor' }
54 }
55
56 {
57     package Base::Class;
58
59     sub new { bless {}, $_[0] }
60     sub DESTROY { }
61 }
62
63 {
64     package NotMoose;
65
66     sub new {
67         my $class = shift;
68
69         return bless { not_moose => 1 }, $class;
70     }
71 }
72
73 {
74     package Foo;
75     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
76
77     $meta->superclasses('NotMoose');
78
79     ::stderr_like(
80         sub { $meta->make_immutable( constructor_class => 'My::Constructor' ) },
81         qr/\QNot inlining 'new' for Foo since it is not inheriting the default Base::Class::new\E\s+\QIf you are certain you don't need to inline your constructor, specify inline_constructor => 0 in your call to Foo->meta->make_immutable/,
82         'got a warning that Foo will not have an inlined constructor'
83     );
84
85     ::is(
86         $meta->find_method_by_name('new')->body,
87         NotMoose->can('new'),
88         'Foo->new is inherited from NotMoose'
89     );
90 }
91
92 {
93     package Bar;
94     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
95
96     $meta->superclasses('NotMoose');
97
98     ::stderr_is(
99         sub { $meta->make_immutable( replace_constructor => 1 ) },
100         q{},
101         'no warning when replace_constructor is true'
102     );
103
104     ::is(
105         $meta->find_method_by_name('new')->package_name,
106         'Bar',
107         'Bar->new is inlined, and not inherited from NotMoose'
108     );
109 }
110
111 {
112     package Baz;
113     Class::MOP::Class->initialize(__PACKAGE__)->make_immutable;
114 }
115
116 {
117     package Quux;
118     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
119
120     $meta->superclasses('Baz');
121
122     ::stderr_is(
123         sub { $meta->make_immutable },
124         q{},
125         'no warning when inheriting from a class that has already made itself immutable'
126     );
127 }
128
129 {
130     package Whatever;
131     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
132
133     ::stderr_like(
134         sub { $meta->make_immutable( constructor_class => 'My::Constructor2' ) },
135         qr/\QNot inlining 'new' for Whatever since No::Constructor::new is not defined/,
136         'got a warning that Whatever will not have an inlined constructor because its expected inherited method does not exist'
137     );
138 }
139
140 {
141     package My::Constructor3;
142
143     use base 'Class::MOP::Method::Constructor';
144 }
145
146 {
147     package CustomCons;
148
149     Class::MOP::Class->initialize(__PACKAGE__)->make_immutable( constructor_class => 'My::Constructor3' );
150 }
151
152 {
153     package Subclass;
154     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
155
156     $meta->superclasses('CustomCons');
157
158     ::stderr_is(
159         sub { $meta->make_immutable },
160         q{},
161         'no warning when inheriting from a class that has already made itself immutable'
162     );
163 }
164
165 {
166     package ModdedNew;
167     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
168
169     sub new { bless {}, shift }
170
171     $meta->add_before_method_modifier( 'new' => sub { } );
172 }
173
174 {
175     package ModdedSub;
176     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
177
178     $meta->superclasses('ModdedNew');
179
180     ::stderr_like(
181         sub { $meta->make_immutable },
182         qr/\QNot inlining 'new' for ModdedSub since it has method modifiers which would be lost if it were inlined/,
183         'got a warning that ModdedSub will not have an inlined constructor since it inherited a wrapped new'
184     );
185 }
186
187 {
188     package My::Destructor;
189
190     use base 'Class::MOP::Method::Inlined';
191
192     sub new {
193         my $class   = shift;
194         my %options = @_;
195
196         my $self = bless \%options, $class;
197         $self->_inline_destructor;
198
199         return $self;
200     }
201
202     sub _inline_destructor {
203         my $self = shift;
204
205         my $code = $self->_eval_closure( {}, 'sub { }' );
206
207         $self->{body} = $code;
208     }
209
210     sub is_needed { 1 }
211     sub associated_metaclass { $_[0]->{metaclass} }
212     sub _expected_method_class { 'Base::Class' }
213 }
214
215 {
216     package HasDestructor;
217     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
218
219     sub DESTROY { }
220
221     ::stderr_like(
222         sub {
223             $meta->make_immutable(
224                 inline_destructor => 1,
225                 destructor_class  => 'My::Destructor',
226             );
227         },
228         qr/Not inlining a destructor for HasDestructor since it defines its own destructor./,
229         'got a warning when trying to inline a destructor for a class that already defines DESTROY'
230     );
231
232     ::is(
233         $meta->find_method_by_name('DESTROY')->body,
234         HasDestructor->can('DESTROY'),
235         'HasDestructor->DESTROY was untouched'
236     );
237 }
238
239 {
240     package HasDestructor2;
241     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
242
243     sub DESTROY { }
244
245     $meta->make_immutable(
246         inline_destructor  => 1,
247         destructor_class   => 'My::Destructor',
248         replace_destructor => 1
249     );
250
251     ::stderr_is(
252         sub {
253             $meta->make_immutable(
254                 inline_destructor  => 1,
255                 destructor_class   => 'My::Destructor',
256                 replace_destructor => 1
257             );
258         },
259         q{},
260         'no warning when replace_destructor is true'
261     );
262
263     ::isnt(
264         $meta->find_method_by_name('new')->body,
265         HasConstructor2->can('new'),
266         'HasConstructor2->new was replaced'
267     );
268 }
269
270 {
271     package ParentHasDestructor;
272
273     sub DESTROY { }
274 }
275
276 {
277     package DestructorChild;
278
279     use base 'ParentHasDestructor';
280
281     my $meta = Class::MOP::Class->initialize(__PACKAGE__);
282
283     ::stderr_like(
284         sub {
285             $meta->make_immutable(
286                 inline_destructor => 1,
287                 destructor_class  => 'My::Destructor',
288             );
289         },
290         qr/Not inlining 'DESTROY' for DestructorChild since it is not inheriting the default Base::Class::DESTROY/,
291         'got a warning when trying to inline a destructor in a class that inherits an unexpected DESTROY'
292     );
293 }