1#!/usr/bin/perl
2
3use v5.10;
4use strict;
5use warnings;
6
7use Test::More;
8use Test::Fatal;
9use Test::Refcount;
10
11use Future;
12
13# All done
14{
15   my $f1 = Future->new;
16   my $f2 = Future->new;
17
18   my $future = Future->needs_all( $f1, $f2 );
19   is_oneref( $future, '$future has refcount 1 initially' );
20
21   # Two refs; one lexical here, one in $future
22   is_refcount( $f1, 2, '$f1 has refcount 2 after adding to ->needs_all' );
23   is_refcount( $f2, 2, '$f2 has refcount 2 after adding to ->needs_all' );
24
25   my $ready;
26   $future->on_ready( sub { $ready++ } );
27
28   ok( !$future->is_ready, '$future not yet ready' );
29
30   $f1->done( one => 1 );
31   $f2->done( two => 2 );
32
33   is( $ready, 1, '$future is now ready' );
34
35   ok( $future->is_ready, '$future now ready after f2 ready' );
36   is_deeply( [ $future->result ], [ one => 1, two => 2 ], '$future->result after f2 ready' );
37
38   is_refcount( $future, 1, '$future has refcount 1 at end of test' );
39   undef $future;
40
41   is_refcount( $f1, 1, '$f1 has refcount 1 at end of test' );
42   is_refcount( $f2, 1, '$f2 has refcount 1 at end of test' );
43}
44
45# One fails
46{
47   my $f1 = Future->new;
48   my $f2 = Future->new;
49   my $c2;
50   $f2->on_cancel( sub { $c2++ } );
51
52   my $future = Future->needs_all( $f1, $f2 );
53
54   my $ready;
55   $future->on_ready( sub { $ready++ } );
56
57   ok( !$future->is_ready, '$future not yet ready' );
58
59   $f1->fail( "It fails" );
60
61   is( $ready, 1, '$future is now ready' );
62
63   ok( $future->is_ready, '$future now ready after f1 fails' );
64   is( $future->failure, "It fails", '$future->failure yields exception' );
65   my $file = __FILE__;
66   my $line = __LINE__ + 1;
67   like( exception { $future->result }, qr/^It fails at \Q$file line $line\E\.?\n$/, '$future->result throws exception' );
68
69   is( $c2, 1, 'Unfinished child future cancelled on failure' );
70
71   is_deeply( [ $future->pending_futures ],
72              [],
73              '$future->pending_futures after $f1 failure' );
74
75   is_deeply( [ $future->ready_futures ],
76              [ $f1, $f2 ],
77              '$future->ready_futures after $f1 failure' );
78
79   is_deeply( [ $future->done_futures ],
80              [],
81              '$future->done_futures after $f1 failure' );
82
83   is_deeply( [ $future->failed_futures ],
84              [ $f1 ],
85              '$future->failed_futures after $f1 failure' );
86
87   is_deeply( [ $future->cancelled_futures ],
88              [ $f2 ],
89              '$future->cancelled_futures after $f1 failure' );
90}
91
92# immediately done
93{
94   my $future = Future->needs_all( Future->done );
95
96   ok( $future->is_ready, '$future of already-done sub already ready' );
97}
98
99# immediately fails
100{
101   my $future = Future->needs_all( Future->fail("F1"), Future->done );
102
103   ok( $future->is_ready, '$future of already-failed sub already ready' );
104}
105
106# cancel propagation
107{
108   my $f1 = Future->new;
109   my $c1;
110   $f1->on_cancel( sub { $c1++ } );
111
112   my $f2 = Future->new;
113   my $c2;
114   $f2->on_cancel( sub { $c2++ } );
115
116   my $future = Future->needs_all( $f1, $f2 );
117
118   $f2->done;
119
120   $future->cancel;
121
122   is( $c1, 1,     '$future->cancel marks subs cancelled' );
123   is( $c2, undef, '$future->cancel ignores ready subs' );
124}
125
126# cancelled convergent
127{
128   my $f1 = Future->new;
129   my $f2 = Future->new;
130
131   my $future = Future->needs_all( $f1, $f2 );
132
133   $f1->cancel;
134
135   ok( $future->is_ready, '$future of cancelled sub is ready after first cancellation' );
136
137   like( scalar $future->failure, qr/ cancelled/, 'Failure mentions cancelled' );
138}
139
140# needs_all on none
141{
142   my $f = Future->needs_all( () );
143
144   ok( $f->is_ready, 'needs_all on no Futures already done' );
145   is_deeply( [ $f->result ], [], '->result on empty needs_all is empty' );
146}
147
148# weakself retention (RT120468)
149{
150   my $f = Future->new;
151
152   my $wait;
153   $wait = Future->needs_all(
154      $f,
155      my $cancelled = Future->new->on_cancel( sub {
156         undef $wait;
157      }),
158   );
159
160   is( exception { $f->fail("oopsie\n") }, undef,
161      'no problems cancelling a Future which clears the original ->needs_all ref' );
162
163   ok( $cancelled->is_cancelled, 'cancellation occured as expected' );
164   ok( $f->is_failed, '->needs_all is marked as done' );
165}
166
167done_testing;
168