Completed
Pull Request — master (#35)
by
unknown
03:11
created

QueuedTasksTableTest   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 504
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 18
eloc 249
dl 0
loc 504
rs 10
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A testQueueInstance() 0 3 1
A setUp() 0 5 2
B testRateLimit() 0 91 1
A testIsQueued() 0 19 1
A testNotBeforeOrder() 0 71 2
A testSequence() 0 44 5
A testNotBefore() 0 9 1
A _needsConnection() 0 4 1
A testCreateAndCount() 0 35 1
A _testRequeueAfterTimeout2() 0 38 1
A _testRequeueAfterTimeout() 0 29 1
A testCreateAndFetch() 0 49 1
1
<?php
2
/**
3
 * @author [email protected]
4
 * @license http://www.opensource.org/licenses/mit-license.php The MIT License
5
 * @link http://github.com/MSeven/cakephp_queue
6
 */
7
namespace Queue\Test\TestCase\Model\Table;
8
9
use Cake\Datasource\ConnectionManager;
10
use Cake\I18n\FrozenTime;
11
use Cake\I18n\Time;
12
use Cake\ORM\TableRegistry;
13
use Cake\TestSuite\TestCase;
14
use Queue\Model\Table\QueuedTasksTable;
15
16
/**
17
 * Queue\Model\Table\QueuedTasksTable Test Case
18
 */
19
class QueuedTasksTableTest extends TestCase
20
{
21
22
    /**
23
     *
24
     * @var \Queue\Model\Table\QueuedTasksTable
25
     */
26
    protected $QueuedTasks;
27
28
    /**
29
     * Fixtures
30
     *
31
     * @var array
32
     */
33
    public $fixtures = [
34
        'plugin.Queue.QueuedTasks'
35
    ];
36
37
    /**
38
     * setUp method
39
     *
40
     * @return void
41
     */
42
    public function setUp()
43
    {
44
        parent::setUp();
45
        $config = TableRegistry::getTableLocator()->exists('QueuedTasks') ? [] : ['className' => QueuedTasksTable::class];
46
        $this->QueuedTasks = TableRegistry::getTableLocator()->get('QueuedTasks', $config);
47
    }
48
49
    /**
50
     * Basic Instance test
51
     *
52
     * @return void
53
     */
54
    public function testQueueInstance()
55
    {
56
        $this->assertInstanceOf(QueuedTasksTable::class, $this->QueuedTasks);
57
    }
58
59
    /**
60
     * Test the basic create and length evaluation functions.
61
     *
62
     * @return void
63
     */
64
    public function testCreateAndCount()
65
    {
66
        // at first, the queue should contain 0 items.
67
        $this->assertSame(0, $this->QueuedTasks->getLength());
68
69
        // create a job
70
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test1', [
71
            'some' => 'random',
72
            'test' => 'data'
73
        ]));
74
75
        // test if queue Length is 1 now.
76
        $this->assertSame(1, $this->QueuedTasks->getLength());
77
78
        // create some more jobs
79
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test2', [
80
            'some' => 'random',
81
            'test' => 'data2'
82
        ]));
83
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test2', [
84
            'some' => 'random',
85
            'test' => 'data3'
86
        ]));
87
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test3', [
88
            'some' => 'random',
89
            'test' => 'data4'
90
        ]));
91
92
        // overall queueLength shpould now be 4
93
        $this->assertSame(4, $this->QueuedTasks->getLength());
94
95
        // there should be 1 task of type 'test1', one of type 'test3' and 2 of type 'test2'
96
        $this->assertSame(1, $this->QueuedTasks->getLength('test1'));
97
        $this->assertSame(2, $this->QueuedTasks->getLength('test2'));
98
        $this->assertSame(1, $this->QueuedTasks->getLength('test3'));
99
    }
100
101
    /**
102
     * Test the basic create and fetch functions.
103
     *
104
     * @return void
105
     */
106
    public function testCreateAndFetch()
107
    {
108
        $this->_needsConnection();
109
110
        // $capabilities is a list of tasks the worker can run.
111
        $capabilities = [
112
            'task1' => [
113
                'name' => 'task1',
114
                'timeout' => 100,
115
                'retries' => 2
116
            ]
117
        ];
118
        $testData = [
119
            'x1' => 'y1',
120
            'x2' => 'y2',
121
            'x3' => 'y3',
122
            'x4' => 'y4'
123
        ];
124
125
        // start off empty.
126
        $this->assertSame([], $this->QueuedTasks->find()
127
            ->toArray());
128
        // at first, the queue should contain 0 items.
129
        $this->assertSame(0, $this->QueuedTasks->getLength());
130
        // there are no jobs, so we cant fetch any.
131
        $this->assertNull($this->QueuedTasks->requestJob($capabilities));
132
        // insert one job.
133
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $testData));
134
135
        // fetch and check the first job.
136
        $job = $this->QueuedTasks->requestJob($capabilities);
137
        $this->assertSame(1, $job->id);
138
        $this->assertSame('task1', $job->task);
139
        $this->assertSame(0, $job->failed);
0 ignored issues
show
Bug Best Practice introduced by
The property failed does not exist on Queue\Model\Entity\QueuedTask. Since you implemented __get, consider adding a @property annotation.
Loading history...
140
        $this->assertNull($job->completed);
141
        $this->assertSame($testData, unserialize($job->data));
142
143
        // after this job has been fetched, it may not be reassigned.
144
        $result = $this->QueuedTasks->requestJob($capabilities);
145
        $this->assertNull($result);
146
147
        // queue length is still 1 since the first job did not finish.
148
        $this->assertSame(1, $this->QueuedTasks->getLength());
149
150
        // Now mark Task1 as done
151
        $this->assertTrue($this->QueuedTasks->markJobDone($job));
0 ignored issues
show
Bug introduced by
It seems like $job can also be of type null; however, parameter $task of Queue\Model\Table\QueuedTasksTable::markJobDone() does only seem to accept Queue\Model\Entity\QueuedTask, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

151
        $this->assertTrue($this->QueuedTasks->markJobDone(/** @scrutinizer ignore-type */ $job));
Loading history...
152
153
        // Should be 0 again.
154
        $this->assertSame(0, $this->QueuedTasks->getLength());
155
    }
156
157
    /**
158
     * Test the delivery of jobs in sequence, skipping fetched but not completed tasks.
159
     *
160
     * @return void
161
     */
162
    public function testSequence()
163
    {
164
        $this->_needsConnection();
165
166
        // $capabilities is a list of tasks the worker can run.
167
        $capabilities = [
168
            'task1' => [
169
                'name' => 'task1',
170
                'timeout' => 100,
171
                'retries' => 2
172
            ]
173
        ];
174
        // at first, the queue should contain 0 items.
175
        $this->assertSame(0, $this->QueuedTasks->getLength());
176
        // create some more jobs
177
        foreach (range(0, 9) as $num) {
178
            $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
179
                'tasknum' => $num
180
            ]));
181
        }
182
        // 10 jobs in the queue.
183
        $this->assertSame(10, $this->QueuedTasks->getLength());
184
185
        // jobs should be fetched in the original sequence.
186
        $array = [];
187
        foreach (range(0, 4) as $num) {
188
            $this->QueuedTasks->clearKey();
189
            $array[$num] = $this->QueuedTasks->requestJob($capabilities);
190
            $jobData = unserialize($array[$num]['data']);
191
            $this->assertSame($num, $jobData['tasknum']);
192
        }
193
        // now mark them as done
194
        foreach (range(0, 4) as $num) {
195
            $this->assertTrue($this->QueuedTasks->markJobDone($array[$num]));
196
            $this->assertSame(9 - $num, $this->QueuedTasks->getLength());
197
        }
198
199
        // jobs should be fetched in the original sequence.
200
        foreach (range(5, 9) as $num) {
201
            $job = $this->QueuedTasks->requestJob($capabilities);
202
            $jobData = unserialize($job->data);
203
            $this->assertSame($num, $jobData['tasknum']);
204
            $this->assertTrue($this->QueuedTasks->markJobDone($job));
0 ignored issues
show
Bug introduced by
It seems like $job can also be of type null; however, parameter $task of Queue\Model\Table\QueuedTasksTable::markJobDone() does only seem to accept Queue\Model\Entity\QueuedTask, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

204
            $this->assertTrue($this->QueuedTasks->markJobDone(/** @scrutinizer ignore-type */ $job));
Loading history...
205
            $this->assertSame(9 - $num, $this->QueuedTasks->getLength());
206
        }
207
    }
208
209
    /**
210
     * Test creating Jobs to run close to a specified time, and strtotime parsing.
211
     * Using toUnixString() function to convert Time object to timestamp, instead of strtotime
212
     *
213
     * @return null
214
     */
215
    public function testNotBefore()
216
    {
217
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '+ 1 Min'));
218
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '+ 1 Day'));
219
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '2009-07-01 12:00:00'));
220
        $data = $this->QueuedTasks->find('all')->toArray();
221
        $this->assertWithinRange((new Time('+ 1 Min'))->toUnixString(), $data[0]['not_before']->toUnixString(), 60);
0 ignored issues
show
Bug introduced by
new Cake\I18n\Time('+ 1 Min')->toUnixString() of type string is incompatible with the type double expected by parameter $expected of Cake\TestSuite\TestCase::assertWithinRange(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

221
        $this->assertWithinRange(/** @scrutinizer ignore-type */ (new Time('+ 1 Min'))->toUnixString(), $data[0]['not_before']->toUnixString(), 60);
Loading history...
222
        $this->assertWithinRange((new Time('+ 1 Day'))->toUnixString(), $data[1]['not_before']->toUnixString(), 60);
223
        $this->assertWithinRange((new Time('2009-07-01 12:00:00'))->toUnixString(), $data[2]['not_before']->toUnixString(), 60);
224
    }
225
226
    /**
227
     * Test Job reordering depending on 'notBefore' field.
228
     * Jobs with an expired not_before field should be executed before any other job without specific timing info.
229
     *
230
     * @return void
231
     */
232
    public function testNotBeforeOrder()
233
    {
234
        $this->_needsConnection();
235
236
        $capabilities = [
237
            'task1' => [
238
                'name' => 'task1',
239
                'timeout' => 100,
240
                'retries' => 2
241
            ],
242
            'dummytask' => [
243
                'name' => 'dummytask',
244
                'timeout' => 100,
245
                'retries' => 2
246
            ]
247
        ];
248
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
249
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
250
        // create a task with it's execution target some seconds in the past, so it should jump to the top of the list.
251
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
252
            'three'
253
        ], [
0 ignored issues
show
Bug introduced by
array('notBefore' => '- 3 Seconds') of type array<string,string> is incompatible with the type null|string expected by parameter $notBefore of Queue\Model\Table\QueuedTasksTable::createJob(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

253
        ], /** @scrutinizer ignore-type */ [
Loading history...
254
            'notBefore' => '- 3 Seconds'
255
        ]));
256
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
257
            'two'
258
        ], [
259
            'notBefore' => '- 5 Seconds'
260
        ]));
261
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
262
            'one'
263
        ], [
264
            'notBefore' => '- 7 Seconds'
265
        ]));
266
267
        // when using requestJob, the jobs we just created should be delivered in this order, NOT the order in which they where created.
268
        $expected = [
269
            [
270
                'name' => 'task1',
271
                'data' => [
272
                    'one'
273
                ]
274
            ],
275
            [
276
                'name' => 'task1',
277
                'data' => [
278
                    'two'
279
                ]
280
            ],
281
            [
282
                'name' => 'task1',
283
                'data' => [
284
                    'three'
285
                ]
286
            ],
287
            [
288
                'name' => 'dummytask',
289
                'data' => null
290
            ],
291
            [
292
                'name' => 'dummytask',
293
                'data' => null
294
            ]
295
        ];
296
297
        foreach ($expected as $item) {
298
            $this->QueuedTasks->clearKey();
299
            $tmp = $this->QueuedTasks->requestJob($capabilities);
300
301
            $this->assertSame($item['name'], $tmp['task']);
302
            $this->assertEquals($item['data'], unserialize($tmp['data']));
303
        }
304
    }
305
306
    /**
307
     * Job Rate limiting.
308
     * Do not execute jobs of a certain type more often than once every X seconds.
309
     *
310
     * @return void
311
     */
312
    public function testRateLimit()
313
    {
314
        $this->_needsConnection();
315
316
        $capabilities = [
317
            'task1' => [
318
                'name' => 'task1',
319
                'timeout' => 101,
320
                'retries' => 2,
321
                'rate' => 2
322
            ],
323
            'dummytask' => [
324
                'name' => 'dummytask',
325
                'timeout' => 101,
326
                'retries' => 2
327
            ]
328
        ];
329
330
        // clear out the rate history
331
        $this->QueuedTasks->rateHistory = [];
0 ignored issues
show
Bug introduced by
The property rateHistory does not seem to exist on Queue\Model\Table\QueuedTasksTable.
Loading history...
332
333
        $data1 = [
334
            'key' => 1
335
        ];
336
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $data1));
337
        $data2 = [
338
            'key' => 2
339
        ];
340
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $data2));
341
        $data3 = [
342
            'key' => 3
343
        ];
344
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $data3));
345
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
346
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
347
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
348
        $this->assertTrue((bool) $this->QueuedTasks->createJob('dummytask'));
349
350
        // At first we get task1-1.
351
        $this->QueuedTasks->clearKey();
352
        $tmp = $this->QueuedTasks->requestJob($capabilities);
353
        $this->assertSame('task1', $tmp['task']);
354
        $this->assertSame($data1, unserialize($tmp['data']));
355
356
        // The rate limit should now skip over task1-2 and fetch a dummytask.
357
        $this->QueuedTasks->clearKey();
358
        $tmp = $this->QueuedTasks->requestJob($capabilities);
359
        $this->assertSame('dummytask', $tmp['task']);
360
        $this->assertFalse(unserialize($tmp['data']));
361
362
        usleep(100000);
363
        // and again.
364
        $this->QueuedTasks->clearKey();
365
        $tmp = $this->QueuedTasks->requestJob($capabilities);
366
        $this->assertSame('dummytask', $tmp['task']);
367
        $this->assertFalse(unserialize($tmp['data']));
368
369
        // Then some time passes
370
        sleep(2);
371
372
        // Now we should get task1-2
373
        $this->QueuedTasks->clearKey();
374
        $tmp = $this->QueuedTasks->requestJob($capabilities);
375
        $this->assertSame('task1', $tmp['task']);
376
        $this->assertSame($data2, unserialize($tmp['data']));
377
378
        // and again rate limit to dummytask.
379
        $this->QueuedTasks->clearKey();
380
        $tmp = $this->QueuedTasks->requestJob($capabilities);
381
        $this->assertSame('dummytask', $tmp['task']);
382
        $this->assertFalse(unserialize($tmp['data']));
383
384
        // Then some more time passes
385
        sleep(2);
386
387
        // Now we should get task1-3
388
        $this->QueuedTasks->clearKey();
389
        $tmp = $this->QueuedTasks->requestJob($capabilities);
390
        $this->assertSame('task1', $tmp['task']);
391
        $this->assertSame($data3, unserialize($tmp['data']));
392
393
        // and again rate limit to dummytask.
394
        $this->QueuedTasks->clearKey();
395
        $tmp = $this->QueuedTasks->requestJob($capabilities);
396
        $this->assertSame('dummytask', $tmp['task']);
397
        $this->assertFalse(unserialize($tmp['data']));
398
399
        // and now the queue is empty
400
        $this->QueuedTasks->clearKey();
401
        $tmp = $this->QueuedTasks->requestJob($capabilities);
402
        $this->assertNull($tmp);
403
    }
404
405
    /**
406
     * Are those tests still valid? //FIXME
407
     *
408
     * @return void
409
     */
410
    public function _testRequeueAfterTimeout()
411
    {
412
        $capabilities = [
413
            'task1' => [
414
                'name' => 'task1',
415
                'timeout' => 1,
416
                'retries' => 2,
417
                'rate' => 0
418
            ]
419
        ];
420
421
        $data = [
422
            'key' => '1'
423
        ];
424
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $data));
425
426
        $this->QueuedTasks->clearKey();
427
        $tmp = $this->QueuedTasks->requestJob($capabilities);
428
        $this->assertSame('task1', $tmp['task']);
429
        $this->assertSame($data, unserialize($tmp['data']));
430
        $this->assertSame('0', $tmp['failed']);
431
        sleep(2);
432
433
        $this->QueuedTasks->clearKey();
434
        $tmp = $this->QueuedTasks->requestJob($capabilities);
435
        $this->assertSame('task1', $tmp['task']);
436
        $this->assertSame($data, unserialize($tmp['data']));
437
        $this->assertSame('1', $tmp['failed']);
438
        $this->assertSame('Restart after timeout', $tmp['failure_message']);
439
    }
440
441
    /**
442
     * Tests whether the timeout of second tasks doesn't interfere with
443
     * requeue of tasks
444
     *
445
     * Are those tests still valid? //FIXME
446
     *
447
     * @return void
448
     */
449
    public function _testRequeueAfterTimeout2()
450
    {
451
        $capabilities = [
452
            'task1' => [
453
                'name' => 'task1',
454
                'timeout' => 1,
455
                'retries' => 2,
456
                'rate' => 0
457
            ],
458
            'task2' => [
459
                'name' => 'task2',
460
                'timeout' => 100,
461
                'retries' => 2,
462
                'rate' => 0
463
            ]
464
        ];
465
466
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
467
            '1'
468
        ]));
469
470
        $this->QueuedTasks->clearKey();
471
        $tmp = $this->QueuedTasks->requestJob($capabilities);
472
        $this->assertSame('task1', $tmp['task']);
473
        $this->assertSame([
474
            '1'
475
        ], unserialize($tmp['data']));
476
        $this->assertSame('0', $tmp['failed']);
477
        sleep(2);
478
479
        $this->QueuedTasks->clearKey();
480
        $tmp = $this->QueuedTasks->requestJob($capabilities);
481
        $this->assertSame('task1', $tmp['task']);
482
        $this->assertSame([
483
            '1'
484
        ], unserialize($tmp['data']));
485
        $this->assertSame('1', $tmp['failed']);
486
        $this->assertSame('Restart after timeout', $tmp['failure_message']);
487
    }
488
489
    /**
490
     *
491
     * @return void
492
     */
493
    public function testIsQueued()
494
    {
495
        $result = $this->QueuedTasks->isQueued('foo-bar');
496
        $this->assertFalse($result);
497
498
        $queuedJob = $this->QueuedTasks->newEntity([
499
            'key' => 'key',
500
            'task' => 'FooBar'
501
        ]);
502
        $this->QueuedTasks->saveOrFail($queuedJob);
503
504
        $result = $this->QueuedTasks->isQueued('foo-bar');
505
        $this->assertTrue($result);
506
507
        $queuedJob->completed = new FrozenTime();
508
        $this->QueuedTasks->saveOrFail($queuedJob);
509
510
        $result = $this->QueuedTasks->isQueued('foo-bar');
511
        $this->assertFalse($result);
512
    }
513
514
    /**
515
     * Helper method for skipping tests that need a real connection.
516
     *
517
     * @return void
518
     */
519
    protected function _needsConnection()
520
    {
521
        $config = ConnectionManager::getConfig('test');
522
        $this->skipIf(strpos($config['driver'], 'Mysql') === false, 'Only Mysql is working yet for this.');
523
    }
524
}