File Coverage

blib/lib/Test/Future/AsyncAwait/Awaitable.pm
Criterion Covered Total %
statement 70 72 97.2
branch 6 12 50.0
condition 1 2 50.0
subroutine 12 12 100.0
pod 1 1 100.0
total 90 99 90.9


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2020-2022 -- leonerd@leonerd.org.uk
5              
6             package Test::Future::AsyncAwait::Awaitable 0.64;
7              
8 1     1   99889 use v5.14;
  1         13  
9 1     1   6 use warnings;
  1         2  
  1         34  
10              
11 1     1   6 use Test::More;
  1         2  
  1         5  
12              
13 1     1   266 use Exporter 'import';
  1         4  
  1         865  
14             our @EXPORT_OK = qw(
15             test_awaitable
16             );
17              
18             =head1 NAME
19              
20             C - conformance tests for awaitable role API
21              
22             =head1 SYNOPSIS
23              
24             use Test::More;
25             use Test::Future::AsyncAwait::Awaitable;
26              
27             use My::Future::Subclass;
28              
29             test_awaitable "My subclass of Future",
30             class => "My::Future::Subclass";
31              
32             done_testing;
33              
34             =head1 DESCRIPTION
35              
36             This module provides a single test function, which runs a suite of subtests to
37             check that a given class provides a useable implementation of the
38             L role. It runs tests that simulate various
39             ways in which L will try to use an instance of this class,
40             to check that the implementation is valid.
41              
42             =cut
43              
44             =head1 FUNCTIONS
45              
46             =cut
47              
48             =head2 test_awaitable
49              
50             test_awaitable( $title, %args )
51              
52             Runs the API conformance tests. C<$title> is printed in the test description
53             output so should be some human-friendly string.
54              
55             Takes the following named arguments:
56              
57             =over 4
58              
59             =item class => STRING
60              
61             Gives the name of the class. This is the class on which the C
62             and C methods will be invoked.
63              
64             =item new => CODE
65              
66             Optional. Gives a callback function to invoke to construct a new pending
67             instance; used by the tests to create pending instances that would be passed
68             into the C keyword. As this is not part of the API as such, the test
69             code does not rely on being able to directly perform it via the API.
70              
71             This argument is optional; if not provided the tests will simply try to invoke
72             the regular C constructor on the given class name. For most
73             implementations this should be sufficient.
74              
75             $f = $new->()
76              
77             =item cancel => CODE
78              
79             Optional. Gives a callback function to invoke to cancel a pending instance, if
80             the implementation provides cancellation semantics. If this callback is
81             provided then an extra subtest suite is run to check the API around
82             cancellation.
83              
84             $cancel->( $f )
85              
86             =item force => CODE
87              
88             Optional. Gives a callback function to invoke to wait for a promise to invoke
89             its on-ready callbacks. Some future-like implementations will run these
90             immediately when the future is marked as done or failed, and so this callback
91             will not be required. Other implementations will defer these invocations,
92             perhaps until the next tick of an event loop or similar. In the latter case,
93             these implementations should provide a way for the test to wait for this to
94             happen.
95              
96             $force->( $f )
97              
98             =back
99              
100             =cut
101              
102             sub test_awaitable
103             {
104 1     1 1 120 my ( $title, %args ) = @_;
105              
106 1         4 my $class = $args{class};
107 1   50 4   8 my $new = $args{new} || sub { return $class->new() };
  4         16  
108 1         3 my $cancel = $args{cancel};
109 1         2 my $force = $args{force};
110              
111             subtest "$title immediate done" => sub {
112 1     1   1413 ok( my $f = $class->AWAIT_NEW_DONE( "result" ), "AWAIT_NEW_DONE yields object" );
113              
114 1         520 ok( $f->AWAIT_IS_READY, 'AWAIT_IS_READY true' );
115 1         471 ok( !$f->AWAIT_IS_CANCELLED, 'AWAIT_IS_CANCELLED false' );
116              
117 1         419 is_deeply( [ $f->AWAIT_GET ], [ "result" ], 'AWAIT_GET in list context' );
118 1         714 is( scalar $f->AWAIT_GET, "result", 'AWAIT_GET in scalar context' );
119 1         417 ok( defined eval { $f->AWAIT_GET; 1 }, 'AWAIT_GET in void context' );
  1         5  
  1         13  
120 1         8 };
121              
122             subtest "$title immediate fail" => sub {
123 1     1   961 ok( my $f = $class->AWAIT_NEW_FAIL( "Oopsie\n" ), "AWAIT_NEW_FAIL yields object" );
124              
125 1         415 ok( $f->AWAIT_IS_READY, 'AWAIT_IS_READY true' );
126 1         389 ok( !$f->AWAIT_IS_CANCELLED, 'AWAIT_IS_CANCELLED false' );
127              
128 1         386 ok( !defined eval { $f->AWAIT_GET; 1 }, 'AWAIT_GET in void context' );
  1         5  
  0         0  
129 1         744 is( $@, "Oopsie\n", 'AWAIT_GET throws exception' );
130 1         2598 };
131              
132 1 50       2169 my $fproto = $new->() or BAIL_OUT( "new did not yield an instance" );
133              
134             subtest "$title deferred done" => sub {
135 1     1   1006 ok( my $f = $fproto->AWAIT_CLONE, 'AWAIT_CLONE yields object' );
136              
137 1         402 ok( !$f->AWAIT_IS_READY, 'AWAIT_IS_READY false' );
138              
139 1         393 $f->AWAIT_DONE( "Late result" );
140              
141 1         52 ok( $f->AWAIT_IS_READY, 'AWAIT_IS_READY true' );
142              
143 1         425 is( scalar $f->AWAIT_GET, "Late result", 'AWAIT_GET in scalar context' );
144 1         17 };
145              
146             subtest "$title deferred fail" => sub {
147 1     1   969 ok( my $f = $fproto->AWAIT_CLONE, 'AWAIT_CLONE yields object' );
148              
149 1         412 ok( !$f->AWAIT_IS_READY, 'AWAIT_IS_READY false' );
150              
151 1         398 $f->AWAIT_FAIL( "Late oopsie\n" );
152              
153 1         45 ok( $f->AWAIT_IS_READY, 'AWAIT_IS_READY true' );
154              
155 1         384 ok( !defined eval { $f->AWAIT_GET; 1 }, 'AWAIT_GET in void context' );
  1         5  
  0         0  
156 1         400 is( $@, "Late oopsie\n", 'AWAIT_GET throws exception' );
157 1         2092 };
158              
159             subtest "$title on-ready" => sub {
160 1 50   1   1003 my $f = $new->() or BAIL_OUT( "new did not yield an instance" );
161              
162 1         10 my $called;
163 1         12 $f->AWAIT_ON_READY( sub { $called++ } );
  1         52  
164 1         18 ok( !$called, 'AWAIT_ON_READY CB not yet invoked' );
165              
166 1         388 $f->AWAIT_DONE( "ping" );
167 1 50       8 $force->( $f ) if $force;
168 1         5 ok( $called, 'AWAIT_ON_READY CB now invoked' );
169 1         2178 };
170              
171             $cancel and subtest "$title cancellation" => sub {
172 1 50   1   992 my $f1 = $new->() or BAIL_OUT( "new did not yield an instance" );
173              
174 1         11 my $f2 = $f1->AWAIT_CLONE;
175              
176 1         16 $f1->AWAIT_CHAIN_CANCEL( $f2 );
177              
178 1         23 ok( !$f2->AWAIT_IS_CANCELLED, 'AWAIT_IS_CANCELLED false before cancellation' );
179              
180 1         393 $cancel->( $f1 );
181              
182 1         98 ok( $f2->AWAIT_IS_CANCELLED, 'AWAIT_IS_CANCELLED true after AWAIT_ON_CANCEL propagation' );
183              
184 1 50       387 my $f3 = $new->() or BAIL_OUT( "new did not yield an instance" );
185              
186 1         9 my $cancelled;
187 1         11 $f3->AWAIT_ON_CANCEL( sub { $cancelled++ } );
  1         17  
188              
189 1         13 $cancel->( $f3 );
190              
191 1         26 ok( $cancelled, 'AWAIT_ON_CANCEL invoked callback' );
192 1 50       2003 };
193             }
194              
195             =head1 AUTHOR
196              
197             Paul Evans
198              
199             =cut
200              
201             0x55AA;