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

QueuedTasksTableTest::setUp()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

223
        $this->assertWithinRange(/** @scrutinizer ignore-type */ (new Time('+ 1 Min'))->toUnixString(), $data[0]['not_before']->toUnixString(), 60);
Loading history...
224
        $this->assertWithinRange((new Time('+ 1 Day'))->toUnixString(), $data[1]['not_before']->toUnixString(), 60);
225
        $this->assertWithinRange((new Time('2009-07-01 12:00:00'))->toUnixString(), $data[2]['not_before']->toUnixString(), 60);
226
    }
227
228
    /**
229
     * Test Job reordering depending on 'notBefore' field.
230
     * Jobs with an expired not_before field should be executed before any other job without specific timing info.
231
     *
232
     * @return void
233
     */
234
    public function testNotBeforeOrder()
235
    {
236
        $this->_needsConnection();
237
238
        $capabilities = [
239
            'task1' => [
240
                'name' => 'task1',
241
                'timeout' => 100,
242
                'retries' => 2
243
            ],
244
            'dummytask' => [
245
                'name' => 'dummytask',
246
                'timeout' => 100,
247
                'retries' => 2
248
            ]
249
        ];
250
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
251
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
252
        // create a task with it's execution target some seconds in the past, so it should jump to the top of the testCreateAndFetchlist.
253
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', [
254
            'three'
255
        ], '- 3 Seconds'));
256
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', [
257
            'two'
258
        ], '- 5 Seconds'));
259
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', [
260
            'one'
261
        ], '- 7 Seconds'));
262
263
        // when using requestJob, the jobs we just created should be delivered in this order, NOT the order in which they where created.
264
        $expected = [
265
            [
266
                'name' => 'task1',
267
                'data' => [
268
                    'one'
269
                ]
270
            ],
271
            [
272
                'name' => 'task1',
273
                'data' => [
274
                    'two'
275
                ]
276
            ],
277
            [
278
                'name' => 'task1',
279
                'data' => [
280
                    'three'
281
                ]
282
            ],
283
            [
284
                'name' => 'dummytask',
285
                'data' => null
286
            ],
287
            [
288
                'name' => 'dummytask',
289
                'data' => null
290
            ]
291
        ];
292
293
        foreach ($expected as $item) {
294
            $this->QueuedTasks->clearKey();
295
            $tmp = $this->QueuedTasks->requestJob($capabilities);
296
297
            $this->assertSame($item['name'], $tmp['task']);
298
            $this->assertEquals($item['data'], unserialize($tmp['data']));
299
        }
300
    }
301
302
    /**
303
     * Job Rate limiting.
304
     * Do not execute jobs of a certain type more often than once every X seconds.
305
     *
306
     * @return void
307
     */
308
    public function testRateLimit()
309
    {
310
        $this->_needsConnection();
311
312
        $capabilities = [
313
            'task1' => [
314
                'name' => 'task1',
315
                'timeout' => 101,
316
                'retries' => 2,
317
                'rate' => 2
318
            ],
319
            'dummytask' => [
320
                'name' => 'dummytask',
321
                'timeout' => 101,
322
                'retries' => 2
323
            ]
324
        ];
325
326
        // clear out the rate history
327
        $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...
328
329
        $data1 = [
330
            'key' => 1
331
        ];
332
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', $data1));
333
        $data2 = [
334
            'key' => 2
335
        ];
336
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', $data2));
337
        $data3 = [
338
            'key' => 3
339
        ];
340
        $this->assertTrue((bool)$this->QueuedTasks->createJob('task1', $data3));
341
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
342
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
343
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
344
        $this->assertTrue((bool)$this->QueuedTasks->createJob('dummytask'));
345
346
        // At first we get task1-1.
347
        $this->QueuedTasks->clearKey();
348
        $tmp = $this->QueuedTasks->requestJob($capabilities);
349
        $this->assertSame('task1', $tmp['task']);
350
        $this->assertSame($data1, unserialize($tmp['data']));
351
352
        // The rate limit should now skip over task1-2 and fetch a dummytask.
353
        $this->QueuedTasks->clearKey();
354
        $tmp = $this->QueuedTasks->requestJob($capabilities);
355
        $this->assertSame('dummytask', $tmp['task']);
356
        $this->assertFalse(unserialize($tmp['data']));
357
358
        usleep(100000);
359
        // and again.
360
        $this->QueuedTasks->clearKey();
361
        $tmp = $this->QueuedTasks->requestJob($capabilities);
362
        $this->assertSame('dummytask', $tmp['task']);
363
        $this->assertFalse(unserialize($tmp['data']));
364
365
        // Then some time passes
366
        sleep(2);
367
368
        // Now we should get task1-2
369
        $this->QueuedTasks->clearKey();
370
        $tmp = $this->QueuedTasks->requestJob($capabilities);
371
        $this->assertSame('task1', $tmp['task']);
372
        $this->assertSame($data2, unserialize($tmp['data']));
373
374
        // and again rate limit to dummytask.
375
        $this->QueuedTasks->clearKey();
376
        $tmp = $this->QueuedTasks->requestJob($capabilities);
377
        $this->assertSame('dummytask', $tmp['task']);
378
        $this->assertFalse(unserialize($tmp['data']));
379
380
        // Then some more time passes
381
        sleep(2);
382
383
        // Now we should get task1-3
384
        $this->QueuedTasks->clearKey();
385
        $tmp = $this->QueuedTasks->requestJob($capabilities);
386
        $this->assertSame('task1', $tmp['task']);
387
        $this->assertSame($data3, unserialize($tmp['data']));
388
389
        // and again rate limit to dummytask.
390
        $this->QueuedTasks->clearKey();
391
        $tmp = $this->QueuedTasks->requestJob($capabilities);
392
        $this->assertSame('dummytask', $tmp['task']);
393
        $this->assertFalse(unserialize($tmp['data']));
394
395
        // and now the queue is empty
396
        $this->QueuedTasks->clearKey();
397
        $tmp = $this->QueuedTasks->requestJob($capabilities);
398
        $this->assertNull($tmp);
399
    }
400
401
    /**
402
     *
403
     * @return void
404
     */
405
    public function testIsQueued()
406
    {
407
        $result = $this->QueuedTasks->isQueued('foo-bar');
408
        $this->assertFalse($result);
409
410
        $queuedJob = $this->QueuedTasks->newEntity([
411
            'key' => 'key',
412
            'task' => 'FooBar'
413
        ]);
414
        $this->QueuedTasks->saveOrFail($queuedJob);
415
416
        $result = $this->QueuedTasks->isQueued('foo-bar');
417
        $this->assertTrue($result);
418
419
        $queuedJob->completed = new FrozenTime();
420
        $this->QueuedTasks->saveOrFail($queuedJob);
421
422
        $result = $this->QueuedTasks->isQueued('foo-bar');
423
        $this->assertFalse($result);
424
    }
425
426
    /**
427
     * Helper method for skipping tests that need a real connection.
428
     *
429
     * @return void
430
     */
431
    protected function _needsConnection()
432
    {
433
        $config = ConnectionManager::getConfig('test');
434
        $this->skipIf(strpos($config['driver'], 'Mysql') === false, 'Only Mysql is working yet for this.');
435
    }
436
}
437