OSDN Git Service

Merge pull request #307 from opentween/detect-rate-limits
[opentween/open-tween.git] / OpenTween.Tests / DebounceTimerTest.cs
1 // OpenTween - Client of Twitter
2 // Copyright (c) 2022 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
4 //
5 // This file is part of OpenTween.
6 //
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
10 // any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General public License
15 // for more details.
16 //
17 // You should have received a copy of the GNU General public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
21
22 using System;
23 using System.Collections.Generic;
24 using System.Linq;
25 using System.Text;
26 using System.Threading.Tasks;
27 using Xunit;
28
29 namespace OpenTween
30 {
31     public class DebounceTimerTest
32     {
33         private class TestDebounceTimer : DebounceTimer
34         {
35             public MockTimer MockTimer = new(() => Task.CompletedTask);
36
37             public TestDebounceTimer(Func<Task> timerCallback, TimeSpan interval, bool leading, bool trailing)
38                 : base(timerCallback, interval, leading, trailing)
39             {
40             }
41
42             protected override ITimer CreateTimer(Func<Task> callback)
43                 => this.MockTimer = new MockTimer(callback);
44         }
45
46         [Fact]
47         public async Task Callback_Debounce_Trailing_Test()
48         {
49             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
50             {
51                 var count = 0;
52
53                 Task Callback()
54                 {
55                     count++;
56                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
57                     return Task.CompletedTask;
58                 }
59
60                 var interval = TimeSpan.FromMinutes(2);
61                 var maxWait = TimeSpan.MaxValue;
62                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
63                 var mockTimer = debouncing.MockTimer;
64
65                 debouncing.Enabled = true;
66
67                 Assert.Equal(0, count);
68                 Assert.False(mockTimer.IsTimerRunning);
69
70                 // 0:00:00 - 0:00:00
71                 await debouncing.Call();
72
73                 // call
74                 Assert.Equal(0, count);
75                 Assert.True(mockTimer.IsTimerRunning);
76                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
77
78                 // 0:01:00 - 0:01:00
79                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
80                 await debouncing.Call();
81
82                 // call (throttled)
83                 Assert.Equal(0, count);
84                 Assert.True(mockTimer.IsTimerRunning);
85
86                 // 0:02:00 - 0:02:00
87                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
88                 await mockTimer.Invoke();
89
90                 // timer
91                 Assert.Equal(0, count);
92                 Assert.True(mockTimer.IsTimerRunning);
93                 Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
94
95                 // 0:03:00 - 0:03:10
96                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
97                 await mockTimer.Invoke();
98
99                 // timer
100                 Assert.Equal(1, count); // invoked (trailing)
101                 Assert.False(mockTimer.IsTimerRunning);
102             }
103         }
104
105         [Fact]
106         public async Task Callback_Debounce_Trailing_CallOnceTest()
107         {
108             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
109             {
110                 var count = 0;
111
112                 Task Callback()
113                 {
114                     count++;
115                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
116                     return Task.CompletedTask;
117                 }
118
119                 var interval = TimeSpan.FromMinutes(2);
120                 var maxWait = TimeSpan.MaxValue;
121                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
122                 var mockTimer = debouncing.MockTimer;
123
124                 debouncing.Enabled = true;
125
126                 Assert.Equal(0, count);
127                 Assert.False(mockTimer.IsTimerRunning);
128
129                 // 0:00:00 - 0:00:00
130                 await debouncing.Call();
131
132                 // call
133                 Assert.Equal(0, count);
134                 Assert.True(mockTimer.IsTimerRunning);
135                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
136
137                 // 0:02:00 - 0:02:10
138                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
139                 await mockTimer.Invoke();
140
141                 // timer
142                 Assert.Equal(1, count); // invoked (trailing)
143                 Assert.False(mockTimer.IsTimerRunning);
144             }
145         }
146
147         [Fact]
148         public async Task Callback_Debounce_Trailing_ResumeTest()
149         {
150             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
151             {
152                 var count = 0;
153
154                 Task Callback()
155                 {
156                     count++;
157                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
158                     return Task.CompletedTask;
159                 }
160
161                 var interval = TimeSpan.FromMinutes(2);
162                 var maxWait = TimeSpan.MaxValue;
163                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
164                 var mockTimer = debouncing.MockTimer;
165
166                 debouncing.Enabled = true;
167
168                 Assert.Equal(0, count);
169                 Assert.False(mockTimer.IsTimerRunning);
170
171                 // 0:00:00 - 0:00:00
172                 await debouncing.Call();
173
174                 // call
175                 Assert.Equal(0, count);
176                 Assert.True(mockTimer.IsTimerRunning);
177                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
178
179                 // 0:02:00 - 0:02:10
180                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
181                 await mockTimer.Invoke();
182
183                 // timer
184                 Assert.Equal(1, count); // invoked (trailing)
185                 Assert.False(mockTimer.IsTimerRunning);
186
187                 // 0:02:10 - 0:02:10
188                 await debouncing.Call();
189
190                 // call
191                 Assert.Equal(1, count);
192                 Assert.True(mockTimer.IsTimerRunning);
193                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
194
195                 // 0:04:10 - 0:04:20
196                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
197                 await mockTimer.Invoke();
198
199                 // timer
200                 Assert.Equal(2, count); // invoked (trailing)
201                 Assert.False(mockTimer.IsTimerRunning);
202             }
203         }
204
205         [Fact]
206         public async Task Callback_Debounce_LeadingAndTrailing_Test()
207         {
208             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
209             {
210                 var count = 0;
211
212                 Task Callback()
213                 {
214                     count++;
215                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
216                     return Task.CompletedTask;
217                 }
218
219                 var interval = TimeSpan.FromMinutes(2);
220                 var maxWait = TimeSpan.MaxValue;
221                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true);
222                 var mockTimer = debouncing.MockTimer;
223
224                 debouncing.Enabled = true;
225
226                 Assert.Equal(0, count);
227                 Assert.False(mockTimer.IsTimerRunning);
228
229                 // 0:00:00 - 0:00:10
230                 await debouncing.Call();
231
232                 // call
233                 Assert.Equal(1, count); // invoked (leading)
234                 Assert.True(mockTimer.IsTimerRunning);
235                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
236
237                 // 0:01:10 - 0:01:10
238                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
239                 await debouncing.Call();
240
241                 // call (throttled)
242                 Assert.Equal(1, count);
243                 Assert.True(mockTimer.IsTimerRunning);
244
245                 // 0:02:10 - 0:02:10
246                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
247                 await mockTimer.Invoke();
248
249                 // timer
250                 Assert.Equal(1, count);
251                 Assert.True(mockTimer.IsTimerRunning);
252                 Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
253
254                 // 0:03:10 - 0:03:20
255                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
256                 await mockTimer.Invoke();
257
258                 // timer
259                 Assert.Equal(2, count); // invoked (trailing)
260                 Assert.False(mockTimer.IsTimerRunning);
261             }
262         }
263
264         [Fact]
265         public async Task Callback_Debounce_LeadingAndTrailing_CallOnceTest()
266         {
267             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
268             {
269                 var count = 0;
270
271                 Task Callback()
272                 {
273                     count++;
274                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
275                     return Task.CompletedTask;
276                 }
277
278                 var interval = TimeSpan.FromMinutes(2);
279                 var maxWait = TimeSpan.MaxValue;
280                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true);
281                 var mockTimer = debouncing.MockTimer;
282
283                 debouncing.Enabled = true;
284
285                 Assert.Equal(0, count);
286                 Assert.False(mockTimer.IsTimerRunning);
287
288                 // 0:00:00 - 0:00:10
289                 await debouncing.Call();
290
291                 // call
292                 Assert.Equal(1, count); // invoked (leading)
293                 Assert.True(mockTimer.IsTimerRunning);
294                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
295
296                 // 0:02:10 - 0:02:10
297                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
298                 await mockTimer.Invoke();
299
300                 // timer
301                 Assert.Equal(1, count); // skip trailing
302                 Assert.False(mockTimer.IsTimerRunning);
303             }
304         }
305
306         [Fact]
307         public async Task Callback_Debounce_LeadingAndTrailing_ResumeTest()
308         {
309             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
310             {
311                 var count = 0;
312
313                 Task Callback()
314                 {
315                     count++;
316                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
317                     return Task.CompletedTask;
318                 }
319
320                 var interval = TimeSpan.FromMinutes(2);
321                 var maxWait = TimeSpan.MaxValue;
322                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: true, trailing: true);
323                 var mockTimer = debouncing.MockTimer;
324
325                 debouncing.Enabled = true;
326
327                 Assert.Equal(0, count);
328                 Assert.False(mockTimer.IsTimerRunning);
329
330                 // 0:00:00 - 0:00:10
331                 await debouncing.Call();
332
333                 // call
334                 Assert.Equal(1, count); // invoked (leading)
335                 Assert.True(mockTimer.IsTimerRunning);
336                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
337
338                 // 0:01:10 - 0:01:10
339                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
340                 await debouncing.Call();
341
342                 // call (throttled)
343                 Assert.Equal(1, count);
344                 Assert.True(mockTimer.IsTimerRunning);
345
346                 // 0:02:10 - 0:02:10
347                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
348                 await mockTimer.Invoke();
349
350                 // timer
351                 Assert.Equal(1, count);
352                 Assert.True(mockTimer.IsTimerRunning);
353                 Assert.Equal(TimeSpan.FromMinutes(1), mockTimer.DueTime);
354
355                 // 0:03:10 - 0:03:20
356                 TestUtils.DriftTime(TimeSpan.FromMinutes(1));
357                 await mockTimer.Invoke();
358
359                 // timer
360                 Assert.Equal(2, count); // invoked (trailing)
361                 Assert.False(mockTimer.IsTimerRunning);
362
363                 // 0:03:20 - 0:03:30
364                 await debouncing.Call();
365
366                 // call
367                 Assert.Equal(3, count); // invoked (leading)
368                 Assert.True(mockTimer.IsTimerRunning);
369                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
370
371                 // 0:05:30 - 0:05:30
372                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
373                 await mockTimer.Invoke();
374
375                 // timer
376                 Assert.Equal(3, count); // skip trailing
377                 Assert.False(mockTimer.IsTimerRunning);
378             }
379         }
380
381         [Fact]
382         public async Task Callback_Debounce_SystemClockChangedTest()
383         {
384             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 1, 0, 0)))
385             {
386                 var count = 0;
387
388                 Task Callback()
389                 {
390                     count++;
391                     TestUtils.DriftTime(TimeSpan.FromSeconds(10));
392                     return Task.CompletedTask;
393                 }
394
395                 var interval = TimeSpan.FromMinutes(2);
396                 var maxWait = TimeSpan.MaxValue;
397                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
398                 var mockTimer = debouncing.MockTimer;
399
400                 debouncing.Enabled = true;
401
402                 Assert.Equal(0, count);
403                 Assert.False(mockTimer.IsTimerRunning);
404
405                 // 1:00:00 - 1:00:00
406                 await debouncing.Call();
407
408                 // call
409                 Assert.Equal(0, count);
410                 Assert.True(mockTimer.IsTimerRunning);
411                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
412
413                 // 0:00:00
414                 // システムの時刻が変更され1時間前に戻った場合
415                 TestUtils.DriftTime(TimeSpan.FromHours(-1));
416
417                 // 0:02:00 - 0:02:00
418                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
419                 await mockTimer.Invoke();
420
421                 // timer
422                 Assert.Equal(0, count);
423                 Assert.True(mockTimer.IsTimerRunning);
424                 Assert.Equal(TimeSpan.FromMinutes(2), mockTimer.DueTime);
425
426                 // 0:04:00 - 0:04:10
427                 TestUtils.DriftTime(TimeSpan.FromMinutes(2));
428                 await mockTimer.Invoke();
429
430                 // timer
431                 Assert.Equal(1, count);
432                 Assert.False(mockTimer.IsTimerRunning);
433             }
434         }
435
436         [Fact]
437         public async Task Call_DisabledTest()
438         {
439             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
440             {
441                 static Task Callback()
442                     => Task.CompletedTask;
443
444                 var interval = TimeSpan.FromMinutes(2);
445                 var maxWait = TimeSpan.MaxValue;
446                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
447                 var mockTimer = debouncing.MockTimer;
448
449                 debouncing.Enabled = false;
450
451                 await debouncing.Call();
452                 Assert.False(mockTimer.IsTimerRunning);
453             }
454         }
455
456         [Fact]
457         public async Task DisabledWhileTimerIsRunning()
458         {
459             using (TestUtils.FreezeTime(new DateTimeUtc(2022, 1, 1, 0, 0, 0)))
460             {
461                 static Task Callback()
462                     => Task.CompletedTask;
463
464                 var interval = TimeSpan.FromMinutes(2);
465                 var maxWait = TimeSpan.MaxValue;
466                 using var debouncing = new TestDebounceTimer(Callback, interval, leading: false, trailing: true);
467                 var mockTimer = debouncing.MockTimer;
468
469                 debouncing.Enabled = true;
470
471                 await debouncing.Call();
472                 Assert.True(mockTimer.IsTimerRunning);
473
474                 debouncing.Enabled = false;
475                 Assert.False(mockTimer.IsTimerRunning);
476             }
477         }
478     }
479 }