1#!/usr/bin/perl
2
3use strict;
4use warnings;
5
6use IO::Async::Test;
7
8use Test::More;
9use Test::Fatal;
10use Test::Refcount;
11
12use lib ".";
13use t::TimeAbout;
14
15use Time::HiRes qw( time );
16
17use IO::Async::Timer::Countdown;
18
19use IO::Async::Loop;
20
21use constant AUT => $ENV{TEST_QUICK_TIMERS} ? 0.1 : 1;
22
23my $loop = IO::Async::Loop->new_builtin;
24
25testing_loop( $loop );
26
27{
28   my $expired;
29   my @eargs;
30
31   my $timer = IO::Async::Timer::Countdown->new(
32      delay => 2 * AUT,
33
34      on_expire => sub { @eargs = @_; $expired = 1 },
35   );
36
37   ok( defined $timer, '$timer defined' );
38   isa_ok( $timer, "IO::Async::Timer", '$timer isa IO::Async::Timer' );
39
40   is_oneref( $timer, '$timer has refcount 1 initially' );
41
42   $loop->add( $timer );
43
44   is_refcount( $timer, 2, '$timer has refcount 2 after adding to Loop' );
45
46   ok( !$timer->is_running, 'New Timer is no yet running' );
47   ok( !$timer->is_expired, 'New Timer is no yet expired' );
48
49   is( $timer->start, $timer, '$timer->start returns $timer' );
50
51   is_refcount( $timer, 2, '$timer has refcount 2 after starting' );
52
53   ok(  $timer->is_running, 'Started Timer is running' );
54   ok( !$timer->is_expired, 'Started Timer not yet expired' );
55
56   time_about( sub { wait_for { $expired } }, 2, 'Timer works' );
57   is_deeply( \@eargs, [ $timer ], 'on_expire args' );
58
59   ok( !$timer->is_running, 'Expired Timer is no longer running' );
60   ok(  $timer->is_expired, 'Expired Timer now expired' );
61
62   undef @eargs;
63
64   is_refcount( $timer, 2, '$timer has refcount 2 before removing from Loop' );
65
66   $loop->remove( $timer );
67
68   is_oneref( $timer, '$timer has refcount 1 after removing from Loop' );
69
70   undef $expired;
71
72   is( $timer->start, $timer, '$timer->start out of a Loop returns $timer' );
73
74   $loop->add( $timer );
75
76   ok(  $timer->is_running, 'Re-started Timer is running' );
77   ok( !$timer->is_expired, 'Re-started Timer not yet expired' );
78
79   time_about( sub { wait_for { $expired } }, 2, 'Timer works a second time' );
80
81   ok( !$timer->is_running, '2nd-time expired Timer is no longer running' );
82   ok(  $timer->is_expired, '2nd-time expired Timer now expired' );
83
84   undef $expired;
85   $timer->start;
86
87   $loop->loop_once( 1 * AUT );
88
89   $timer->stop;
90
91   $timer->stop;
92
93   ok( 1, "Timer can be stopped a second time" );
94
95   $loop->loop_once( 2 * AUT );
96
97   ok( !$expired, "Stopped timer doesn't expire" );
98
99   undef $expired;
100   $timer->start;
101
102   $loop->loop_once( 1 * AUT );
103
104   my $now = time;
105   $timer->reset;
106
107   $loop->loop_once( 1.5 * AUT );
108
109   ok( !$expired, "Reset Timer hasn't expired yet" );
110
111   wait_for { $expired };
112   my $took = (time - $now) / AUT;
113
114   cmp_ok( $took, '>', 1.5, "Timer has now expired took at least 1.5" );
115   cmp_ok( $took, '<', 2.5, "Timer has now expired took no more than 2.5" );
116
117   $loop->remove( $timer );
118
119   undef @eargs;
120
121   is_oneref( $timer, 'Timer has refcount 1 finally' );
122}
123
124{
125   my $timer = IO::Async::Timer::Countdown->new(
126      delay => 2 * AUT,
127      on_expire => sub { },
128   );
129
130   $loop->add( $timer );
131
132   $timer->start;
133
134   $loop->remove( $timer );
135
136   $loop->loop_once( 3 * AUT );
137
138   ok( !$timer->is_expired, "Removed Timer does not expire" );
139}
140
141{
142   my $timer = IO::Async::Timer::Countdown->new(
143      delay => 2 * AUT,
144      on_expire => sub { },
145   );
146
147   $timer->start;
148
149   $loop->add( $timer );
150
151   ok( $timer->is_running, 'Pre-started Timer is running after adding' );
152
153   time_about( sub { wait_for { $timer->is_expired } }, 2, 'Pre-started Timer works' );
154
155   $loop->remove( $timer );
156}
157
158{
159   my $timer = IO::Async::Timer::Countdown->new(
160      delay => 2 * AUT,
161      on_expire => sub { },
162   );
163
164   $timer->start;
165   $timer->stop;
166
167   $loop->add( $timer );
168
169   $loop->loop_once( 3 * AUT );
170
171   ok( !$timer->is_expired, "start/stopped Timer doesn't expire" );
172
173   $loop->remove( $timer );
174}
175
176{
177   my $timer = IO::Async::Timer::Countdown->new(
178      delay => 2 * AUT,
179      on_expire => sub { },
180   );
181
182   $loop->add( $timer );
183
184   $timer->configure( delay => 1 * AUT );
185
186   $timer->start;
187
188   time_about( sub { wait_for { $timer->is_expired } }, 1, 'Reconfigured timer delay works' );
189
190   my $expired;
191   $timer->configure( on_expire => sub { $expired = 1 } );
192
193   $timer->start;
194
195   time_about( sub { wait_for { $expired } }, 1, 'Reconfigured timer on_expire works' );
196
197   $timer->start;
198   ok( exception { $timer->configure( delay => 5 ); },
199       'Configure a running timer fails' );
200
201   $loop->remove( $timer );
202}
203
204{
205   my $timer = IO::Async::Timer::Countdown->new(
206      delay => 1 * AUT,
207      remove_on_expire => 1,
208
209      on_expire => sub { },
210   );
211
212   $loop->add( $timer );
213   $timer->start;
214
215   time_about( sub { wait_for { $timer->is_expired } }, 1, 'remove_on_expire Timer' );
216
217   is( $timer->loop, undef, 'remove_on_expire Timer removed from Loop after expire' );
218}
219
220## Subclass
221
222my $sub_expired;
223{
224   my $timer = TestTimer->new(
225      delay => 2 * AUT,
226   );
227
228   ok( defined $timer, 'subclass $timer defined' );
229   isa_ok( $timer, "IO::Async::Timer", 'subclass $timer isa IO::Async::Timer' );
230
231   is_oneref( $timer, 'subclass $timer has refcount 1 initially' );
232
233   $loop->add( $timer );
234
235   is_refcount( $timer, 2, 'subclass $timer has refcount 2 after adding to Loop' );
236
237   $timer->start;
238
239   is_refcount( $timer, 2, 'subclass $timer has refcount 2 after starting' );
240
241   ok( $timer->is_running, 'Started subclass Timer is running' );
242
243   time_about( sub { wait_for { $sub_expired } }, 2, 'subclass Timer works' );
244
245   ok( !$timer->is_running, 'Expired subclass Timer is no longer running' );
246
247   is_refcount( $timer, 2, 'subclass $timer has refcount 2 before removing from Loop' );
248
249   $loop->remove( $timer );
250
251   is_oneref( $timer, 'subclass $timer has refcount 1 after removing from Loop' );
252}
253
254done_testing;
255
256package TestTimer;
257use base qw( IO::Async::Timer::Countdown );
258
259sub on_expire { $sub_expired = 1 }
260