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

QueuedTasksTableTest::testCreateAndFetch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 49
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 27
nc 1
nop 0
dl 0
loc 49
rs 9.488
c 0
b 0
f 0
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
46
        $config = TableRegistry::exists('QueuedTasks') ? [] : [
0 ignored issues
show
Deprecated Code introduced by
The function Cake\ORM\TableRegistry::exists() has been deprecated: 3.6.0 Use \Cake\ORM\Locator\TableLocator::exists() instead. ( Ignorable by Annotation )

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

46
        $config = /** @scrutinizer ignore-deprecated */ TableRegistry::exists('QueuedTasks') ? [] : [

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
47
            'className' => QueuedTasksTable::class
48
        ];
49
        $this->QueuedTasks = TableRegistry::get('QueuedTasks', $config);
0 ignored issues
show
Deprecated Code introduced by
The function Cake\ORM\TableRegistry::get() has been deprecated: 3.6.0 Use \Cake\ORM\Locator\TableLocator::get() instead. ( Ignorable by Annotation )

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

49
        $this->QueuedTasks = /** @scrutinizer ignore-deprecated */ TableRegistry::get('QueuedTasks', $config);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
50
    }
51
52
    /**
53
     * Basic Instance test
54
     *
55
     * @return void
56
     */
57
    public function testQueueInstance()
58
    {
59
        $this->assertInstanceOf(QueuedTasksTable::class, $this->QueuedTasks);
60
    }
61
62
    /**
63
     * Test the basic create and length evaluation functions.
64
     *
65
     * @return void
66
     */
67
    public function testCreateAndCount()
68
    {
69
        // at first, the queue should contain 0 items.
70
        $this->assertSame(0, $this->QueuedTasks->getLength());
71
72
        // create a job
73
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test1', [
74
            'some' => 'random',
75
            'test' => 'data'
76
        ]));
77
78
        // test if queue Length is 1 now.
79
        $this->assertSame(1, $this->QueuedTasks->getLength());
80
81
        // create some more jobs
82
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test2', [
83
            'some' => 'random',
84
            'test' => 'data2'
85
        ]));
86
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test2', [
87
            'some' => 'random',
88
            'test' => 'data3'
89
        ]));
90
        $this->assertTrue((bool) $this->QueuedTasks->createJob('test3', [
91
            'some' => 'random',
92
            'test' => 'data4'
93
        ]));
94
95
        // overall queueLength shpould now be 4
96
        $this->assertSame(4, $this->QueuedTasks->getLength());
97
98
        // there should be 1 task of type 'test1', one of type 'test3' and 2 of type 'test2'
99
        $this->assertSame(1, $this->QueuedTasks->getLength('test1'));
100
        $this->assertSame(2, $this->QueuedTasks->getLength('test2'));
101
        $this->assertSame(1, $this->QueuedTasks->getLength('test3'));
102
    }
103
104
    /**
105
     * Test the basic create and fetch functions.
106
     *
107
     * @return void
108
     */
109
    public function testCreateAndFetch()
110
    {
111
        $this->_needsConnection();
112
113
        // $capabilities is a list of tasks the worker can run.
114
        $capabilities = [
115
            'task1' => [
116
                'name' => 'task1',
117
                'timeout' => 100,
118
                'retries' => 2
119
            ]
120
        ];
121
        $testData = [
122
            'x1' => 'y1',
123
            'x2' => 'y2',
124
            'x3' => 'y3',
125
            'x4' => 'y4'
126
        ];
127
128
        // start off empty.
129
        $this->assertSame([], $this->QueuedTasks->find()
130
            ->toArray());
131
        // at first, the queue should contain 0 items.
132
        $this->assertSame(0, $this->QueuedTasks->getLength());
133
        // there are no jobs, so we cant fetch any.
134
        $this->assertNull($this->QueuedTasks->requestJob($capabilities));
135
        // insert one job.
136
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', $testData));
137
138
        // fetch and check the first job.
139
        $job = $this->QueuedTasks->requestJob($capabilities);
140
        $this->assertSame(1, $job->id);
141
        $this->assertSame('task1', $job->task);
142
        $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...
143
        $this->assertNull($job->completed);
144
        $this->assertSame($testData, unserialize($job->data));
145
146
        // after this job has been fetched, it may not be reassigned.
147
        $result = $this->QueuedTasks->requestJob($capabilities);
148
        $this->assertNull($result);
149
150
        // queue length is still 1 since the first job did not finish.
151
        $this->assertSame(1, $this->QueuedTasks->getLength());
152
153
        // Now mark Task1 as done
154
        $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

154
        $this->assertTrue($this->QueuedTasks->markJobDone(/** @scrutinizer ignore-type */ $job));
Loading history...
155
156
        // Should be 0 again.
157
        $this->assertSame(0, $this->QueuedTasks->getLength());
158
    }
159
160
    /**
161
     * Test the delivery of jobs in sequence, skipping fetched but not completed tasks.
162
     *
163
     * @return void
164
     */
165
    public function testSequence()
166
    {
167
        $this->_needsConnection();
168
169
        // $capabilities is a list of tasks the worker can run.
170
        $capabilities = [
171
            'task1' => [
172
                'name' => 'task1',
173
                'timeout' => 100,
174
                'retries' => 2
175
            ]
176
        ];
177
        // at first, the queue should contain 0 items.
178
        $this->assertSame(0, $this->QueuedTasks->getLength());
179
        // create some more jobs
180
        foreach (range(0, 9) as $num) {
181
            $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', [
182
                'tasknum' => $num
183
            ]));
184
        }
185
        // 10 jobs in the queue.
186
        $this->assertSame(10, $this->QueuedTasks->getLength());
187
188
        // jobs should be fetched in the original sequence.
189
        $array = [];
190
        foreach (range(0, 4) as $num) {
191
            $this->QueuedTasks->clearKey();
192
            $array[$num] = $this->QueuedTasks->requestJob($capabilities);
193
            $jobData = unserialize($array[$num]['data']);
194
            $this->assertSame($num, $jobData['tasknum']);
195
        }
196
        // now mark them as done
197
        foreach (range(0, 4) as $num) {
198
            $this->assertTrue($this->QueuedTasks->markJobDone($array[$num]));
199
            $this->assertSame(9 - $num, $this->QueuedTasks->getLength());
200
        }
201
202
        // jobs should be fetched in the original sequence.
203
        foreach (range(5, 9) as $num) {
204
            $job = $this->QueuedTasks->requestJob($capabilities);
205
            $jobData = unserialize($job->data);
206
            $this->assertSame($num, $jobData['tasknum']);
207
            $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

207
            $this->assertTrue($this->QueuedTasks->markJobDone(/** @scrutinizer ignore-type */ $job));
Loading history...
208
            $this->assertSame(9 - $num, $this->QueuedTasks->getLength());
209
        }
210
    }
211
212
    /**
213
     * Test creating Jobs to run close to a specified time, and strtotime parsing.
214
     * Using toUnixString() function to convert Time object to timestamp, instead of strtotime
215
     *
216
     * @return null
217
     */
218
    public function testNotBefore()
219
    {
220
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '+ 1 Min'));
221
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '+ 1 Day'));
222
        $this->assertTrue((bool) $this->QueuedTasks->createJob('task1', null, '2009-07-01 12:00:00'));
223
        $data = $this->QueuedTasks->find('all')->toArray();
224
        $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

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

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