1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use Test::More tests => 65;
7use Test::Exception;
8
9BEGIN {
10    use_ok('IOC::Container');
11    use_ok('IOC::Service::SetterInjection');
12}
13
14# Cyclical dependencies:
15#     +---+
16#  +--| A |<-+
17#  |  +---+  |
18#  |  +---+  |
19#  +->| B |--+
20#     +---+
21
22{
23    {
24        package A;
25        sub new { bless { b => $_[1] }, $_[0] }
26        sub testA { 'testA' }
27
28        package B;
29        sub new { bless { a => $_[1] }, $_[0] }
30        sub testB { 'testB' }
31    }
32
33    my $container = IOC::Container->new();
34    $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) }));
35    $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) }));
36
37    my $a;
38    lives_ok {
39        $a = $container->get('a');
40    } '... we got our A object ok';
41    isa_ok($a, 'A');
42
43    isa_ok($a->{b}, 'B');
44    is(ref($a->{b}), 'B', '... and it is actually a B object too');
45    is($a->{b}->testB(), 'testB', '... make sure our B object works as expected');
46
47    isa_ok($a->{b}->{a}, 'A');
48    is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object');
49
50    is($a->{b}->{a}->testA(), 'testA', '... this should inflate our deferred A object');
51    is(ref($a->{b}->{a}), 'A', '... now this should be an A object');
52
53    is($a, $a->{b}->{a}, '... and our A instances are both the same since they are singletons');
54}
55
56# Graph Dependecies
57#       +---+
58#    +--| C |<-+
59#    |  +---+  |
60#  +-V-+     +---+
61#  | D |     | F |
62#  +---+     +-^-+
63#    |  +---+  |
64#    +->| E |--+
65#       +---+
66
67{
68
69    {
70        package C;
71        sub new { bless { d => $_[1] }, $_[0] }
72
73        package D;
74        sub new { bless { e => $_[1] }, $_[0] }
75
76        package E;
77        sub new { bless { f => $_[1] }, $_[0] }
78
79        package F;
80        sub new { bless { c => $_[1] }, $_[0] }
81    }
82
83    my $container = IOC::Container->new();
84    $container->register(IOC::Service->new('c' => sub { C->new((shift)->get('d')) }));
85    $container->register(IOC::Service->new('d' => sub { D->new((shift)->get('e')) }));
86    $container->register(IOC::Service->new('e' => sub { E->new((shift)->get('f')) }));
87    $container->register(IOC::Service->new('f' => sub { F->new((shift)->get('c')) }));
88
89    my $c;
90    lives_ok {
91        $c = $container->get('c');
92    } '... we got our C object ok';
93    isa_ok($c, 'C');
94
95    isa_ok($c->{d}, 'D');
96    is(ref($c->{d}), 'D', '... and it is actually a D object too');
97
98    isa_ok($c->{d}->{e}, 'E');
99    is(ref($c->{d}->{e}), 'E', '... and it is actually a E object too');
100
101    isa_ok($c->{d}->{e}->{f}, 'F');
102    is(ref($c->{d}->{e}->{f}), 'F', '... and it is actually a F object too');
103
104    isa_ok($c->{d}->{e}->{f}->{c}, 'C');
105    is(ref($c->{d}->{e}->{f}->{c}), 'IOC::Service::Deferred', '... however this is actually an IOC::Service::Deferred object');
106
107    isa_ok($c->{d}->{e}->{f}->{c}->{d}, 'D');
108    is(ref($c->{d}->{e}->{f}->{c}), 'C', '... but now we have been infalted into a proper C object');
109}
110
111# Graph Dependecies
112#       +---+
113#    +--| G |<-+
114#    |  +---+  |
115#  +-V-+     +---+  +---+  +---+
116#  | H |     | J |  | K |->| L |
117#  +---+     +-^-+  +-^-+  +---+
118#   | |  +---+ |      |
119#   | +->| I |-+      |
120#   |    +---+        |
121#   +-----------------+
122
123{
124    {
125        package G;
126        sub new { bless [ $_[1] ], $_[0] }
127
128        package H;
129        sub new { bless { i => $_[1], k => $_[2] }, $_[0] }
130
131        package I;
132        sub new { bless { j => $_[1] }, $_[0] }
133
134        package J;
135        sub new { bless { g => $_[1] }, $_[0] }
136
137        package K;
138        sub new { bless { l => $_[1] }, $_[0] }
139    }
140
141    my $container = IOC::Container->new();
142    $container->register(IOC::Service->new('g' => sub { G->new((shift)->get('h')) }));
143    $container->register(IOC::Service->new('h' => sub { H->new($_[0]->get('i'), $_[0]->get('k')) }));
144    $container->register(IOC::Service->new('i' => sub { I->new((shift)->get('j')) }));
145    $container->register(IOC::Service->new('j' => sub { J->new((shift)->get('g')) }));
146    $container->register(IOC::Service->new('k' => sub { K->new((shift)->get('l')) }));
147    $container->register(IOC::Service->new('l' => sub { '... this is the end' }));
148
149    my $g;
150    lives_ok {
151        $g = $container->get('g');
152    } '... we got our G object ok';
153    isa_ok($g, 'G');
154
155    isa_ok($g->[0], 'H');
156    is(ref($g->[0]), 'H', '... and it is actually a H object too');
157
158    isa_ok($g->[0]->{i}, 'I');
159    is(ref($g->[0]->{i}), 'I', '... and it is actually a I object too');
160
161    isa_ok($g->[0]->{i}->{j}, 'J');
162    is(ref($g->[0]->{i}->{j}), 'J', '... and it is actually a J object too');
163
164    isa_ok($g->[0]->{i}->{j}->{g}, 'G');
165    is(ref($g->[0]->{i}->{j}->{g}), 'IOC::Service::Deferred', '... and it is actually an IOC::Service::Deferred object');
166
167    isa_ok($g->[0]->{i}->{j}->{g}->[0], 'H');
168    is(ref($g->[0]->{i}->{j}->{g}), 'G', '... and it is actually the inflated G object now');
169
170    isa_ok($g->[0]->{k}, 'K');
171    is(ref($g->[0]->{k}), 'K', '... and it is actually a K object too');
172
173    is($g->[0]->{k}->{l}, '... this is the end', '... and this is our L string');
174
175
176}
177
178## EDGE CASES
179
180# Cyclical dependencies:
181#     +---+
182#  +--| M |<-+
183#  |  +---+  |
184#  |         > $m->test()
185#  |  +---+  |
186#  +->| N |--+
187#     +---+
188#
189# in this test we call a method on the deferred M instance
190# before we are finished creating N, this results in M
191# being intialized before N is finished initializing.
192#
193# +---+    +---+   +-------+  /   N->new calls  \   +-------+
194# | M |--->| N |-->| <<M>> |-| $m->testM() which |->| <<N>> |
195# +---+    +---+   +-------+  \  inflates <<M>> /   +-------+     .
196#   \        \          \              V                 \........|
197#    \        \          \.............|__________________________|
198#     \        \__________________________________________________|
199#      \__________________________________________________________|
200#                    <<scope of call to instance()>>
201#
202# basically what is happening is that when the first deferred <<M>>
203# is inflated, the first real M has not yet been fully created. Thus
204# the IOC::Service object M occupies first stores the deferred <<M>>
205# then returns, which satisfies the N and returns, which then satisfies
206# the first M which is then stored in the IOC::Service instance that
207# the first <<M>> is stored in, thus overwriting it.
208#
209# We solve this by checking to see if the IOC::Service object in question
210# already has an instance, in which case, we use that one and discard the
211# extra instance. It is not the best solution (since we create an instace
212# only to discard it), but it allows for this to work.
213
214{
215    {
216        package M;
217        sub new { bless { n => $_[1] }, $_[0] }
218        sub testM { 'testM' }
219        ## uncomment this line to see the
220        ## recursive spiral of death happen.
221        # sub testM { (shift)->{n}->testN() }
222
223        package N;
224        sub new {
225            my ($class, $m) = @_;
226            Test::More::is(ref($m), 'IOC::Service::Deferred', '... we got a deferred M');
227            Test::More::is($m->testM(), 'testM', '... and we got the right output');
228            bless { m => $m }, $class
229        }
230        sub testN { 'testN' }
231    }
232
233    my $container = IOC::Container->new();
234    $container->register(IOC::Service->new('m' => sub { M->new((shift)->get('n')) }));
235    $container->register(IOC::Service->new('n' => sub { N->new((shift)->get('m')) }));
236
237    my $m;
238    lives_ok {
239        $m = $container->get('m');
240    } '... we got our M object ok';
241    isa_ok($m, 'M');
242
243    isa_ok($m->{n}, 'N');
244    is(ref($m->{n}), 'IOC::Service::Deferred', '... this an IOC::Service::Deferred instance');
245    is($m->{n}->testN(), 'testN', '... make sure our N object works as expected');
246    is(ref($m->{n}), 'N', '... this now an N instance');
247
248    isa_ok($m->{n}->{m}, 'M');
249    is(ref($m->{n}->{m}), 'M', '... but this it is actually an M object since it was resolved earlier');
250
251    is($m, $m->{n}->{m}, '... and our M instances are both the same since they are singletons');
252}
253
254# test some of the errors for this
255
256{
257    throws_ok {
258        IOC::Service::Deferred->new();
259    } 'IOC::InsufficientArguments', '... got the error we expected';
260
261    throws_ok {
262        IOC::Service::Deferred->new([]);
263    } 'IOC::InsufficientArguments', '... got the error we expected';
264
265    throws_ok {
266        IOC::Service::Deferred->new(bless({}, 'Fail'));
267    } 'IOC::InsufficientArguments', '... got the error we expected';
268
269}
270
271{
272    my $container = IOC::Container->new();
273    $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) }));
274    $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) }));
275
276    my $a;
277    lives_ok {
278        $a = $container->get('a');
279    } '... we got our A object ok';
280    isa_ok($a, 'A');
281
282    isa_ok($a->{b}->{a}, 'A');
283    is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object');
284
285    ok(!defined($a->{b}->{a}->can('Fail')), '... we dont have this method');
286
287    is(ref($a->{b}->{a}), 'A', '... now this it is actually a A object');
288}
289
290{
291    my $container = IOC::Container->new();
292    $container->register(IOC::Service->new('a' => sub { A->new((shift)->get('b')) }));
293    $container->register(IOC::Service->new('b' => sub { B->new((shift)->get('a')) }));
294
295    my $a;
296    lives_ok {
297        $a = $container->get('a');
298    } '... we got our A object ok';
299    isa_ok($a, 'A');
300
301    isa_ok($a->{b}->{a}, 'A');
302    is(ref($a->{b}->{a}), 'IOC::Service::Deferred', '... but this it is actually a IOC::Service::Deferred object');
303
304    throws_ok {
305        $a->{b}->{a}->Fail()
306    } 'IOC::MethodNotFound', '... got the error we expected';
307
308    is(ref($a->{b}->{a}), 'A', '... now this it is actually a A object');
309}
310