Passed
Push — master ( b05304...84ccec )
by Harry
02:04
created

testModifyingThePriorityWillChangeWhichRunStartsFirst()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 50
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 29
dl 0
loc 50
rs 9.456
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of graze/parallel-process.
5
 *
6
 * Copyright © 2018 Nature Delivered Ltd. <https://www.graze.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @license https://github.com/graze/parallel-process/blob/master/LICENSE.md
12
 * @link    https://github.com/graze/parallel-process
13
 */
14
15
namespace Graze\ParallelProcess\Test\Unit;
16
17
use Graze\DataStructure\Collection\CollectionInterface;
18
use Graze\ParallelProcess\CallbackRun;
19
use Graze\ParallelProcess\Event\PoolRunEvent;
20
use Graze\ParallelProcess\PoolInterface;
21
use Graze\ParallelProcess\PriorityPool;
22
use Graze\ParallelProcess\ProcessRun;
23
use Graze\ParallelProcess\RunInterface;
24
use Graze\ParallelProcess\Test\TestCase;
25
use Mockery;
26
use Symfony\Component\Process\Process;
27
28
class PriorityPoolTest extends TestCase
29
{
30
    /** @var mixed */
31
    private $process;
32
33
    public function setUp()
34
    {
35
        parent::setUp();
36
37
        $this->process = Mockery::mock(Process::class)
38
                                ->allows(['stop' => null, 'isStarted' => false, 'isRunning' => false]);
39
    }
40
41
    public function testPriorityPoolIsARunInterface()
42
    {
43
        $priorityPool = new PriorityPool();
44
        $this->assertInstanceOf(RunInterface::class, $priorityPool);
45
    }
46
47
    public function testPriorityPoolIsACollectionOfRuns()
48
    {
49
        $priorityPool = new PriorityPool();
50
        $this->assertInstanceOf(CollectionInterface::class, $priorityPool);
51
52
        $this->assertSame($priorityPool, $priorityPool->add($this->process));
53
54
        $runs = $priorityPool->getAll();
55
        $this->assertCount(1, $runs);
56
57
        $this->assertInstanceOf(ProcessRun::class, reset($runs));
58
    }
59
60
    public function testPriorityPoolInitialStateWithProcess()
61
    {
62
        $priorityPool = new PriorityPool();
63
        $priorityPool->add($this->process);
64
65
        $this->assertFalse($priorityPool->isSuccessful());
66
        $this->assertFalse($priorityPool->isRunning());
67
        $this->assertFalse($priorityPool->hasStarted());
68
        $this->assertFalse($priorityPool->isRunInstantly());
69
        $this->assertEquals(PriorityPool::NO_MAX, $priorityPool->getMaxSimultaneous());
70
    }
71
72
    public function testPriorityPoolConstructor()
73
    {
74
        $runs = [];
75
        for ($i = 0; $i < 2; $i++) {
76
            $runs[] = Mockery::mock(RunInterface::class)
77
                             ->allows(['isRunning' => false, 'hasStarted' => false, 'addListener' => true, 'getPriority' => 1.0]);
78
        }
79
80
        $priorityPool = new PriorityPool($runs);
81
82
        $this->assertEquals(2, $priorityPool->count());
83
    }
84
85
    /**
86
     * @expectedException \InvalidArgumentException
87
     */
88
    public function testAddingNonRunInterfaceWillThrowException()
89
    {
90
        $nope = Mockery::mock();
91
        $priorityPool = new PriorityPool();
92
        $priorityPool->add($nope);
0 ignored issues
show
Bug introduced by
$nope of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

92
        $priorityPool->add(/** @scrutinizer ignore-type */ $nope);
Loading history...
93
    }
94
95
    public function testPriorityPoolInitialStateWithNoRuns()
96
    {
97
        $priorityPool = new PriorityPool();
98
99
        $this->assertFalse($priorityPool->isSuccessful(), 'should not be successful');
100
        $this->assertFalse($priorityPool->isRunning(), 'should not be running');
101
        $this->assertFalse($priorityPool->hasStarted(), 'should not be started');
102
    }
103
104
    public function testPriorityPoolAddingRun()
105
    {
106
        $run = Mockery::mock(RunInterface::class);
107
        $run->allows(['hasStarted' => false, 'isRunning' => false, 'addListener' => $run, 'getPriority' => 1.0]);
108
109
        $priorityPool = new PriorityPool();
110
        $priorityPool->add($run);
0 ignored issues
show
Bug introduced by
$run of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

110
        $priorityPool->add(/** @scrutinizer ignore-type */ $run);
Loading history...
111
112
        $this->assertEquals(1, $priorityPool->count());
113
    }
114
115
    public function testPriorityPoolAddingRunFiresAnEvent()
116
    {
117
        $run = Mockery::mock(RunInterface::class);
118
        $run->allows(['hasStarted' => false, 'isRunning' => false, 'addListener' => $run, 'getPriority' => 1.0]);
119
120
        $hit = false;
121
122
        $priorityPool = new PriorityPool();
123
        $priorityPool->addListener(
124
            PoolRunEvent::POOL_RUN_ADDED,
125
            function (PoolRunEvent $event) use ($priorityPool, $run, &$hit) {
126
                $this->assertSame($priorityPool, $event->getPool());
127
                $this->assertSame($run, $event->getRun());
128
                $hit = true;
129
            }
130
        );
131
        $priorityPool->add($run);
0 ignored issues
show
Bug introduced by
$run of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

131
        $priorityPool->add(/** @scrutinizer ignore-type */ $run);
Loading history...
132
133
        $this->assertEquals(1, $priorityPool->count());
134
        $this->assertTrue($hit);
135
    }
136
137
    public function testPriorityPoolAddingProcess()
138
    {
139
        $priorityPool = new PriorityPool();
140
        $priorityPool->add($this->process);
141
142
        $this->assertEquals(1, $priorityPool->count());
143
        $runs = $priorityPool->getAll();
144
        $run = reset($runs);
145
146
        $this->assertEquals($this->process, $run->getProcess());
147
    }
148
149
    public function testPriorityPoolAddingProcessFiresAnEvent()
150
    {
151
        $priorityPool = new PriorityPool();
152
        $priorityPool->addListener(
153
            PoolRunEvent::POOL_RUN_ADDED,
154
            function (PoolRunEvent $event) use ($priorityPool, &$hit) {
155
                $this->assertSame($priorityPool, $event->getPool());
156
                $run = $event->getRun();
157
                if ($run instanceof ProcessRun) {
158
                    $this->assertSame($this->process, $run->getProcess());
159
                }
160
                $hit = true;
161
            }
162
        );
163
        $priorityPool->add($this->process);
164
165
        $this->assertEquals(1, $priorityPool->count());
166
        $runs = $priorityPool->getAll();
167
        $run = reset($runs);
168
169
        $this->assertEquals($this->process, $run->getProcess());
170
        $this->assertTrue($hit);
171
    }
172
173
    public function testAddingChildPoolWillAddAllTheChildrenIntoThisPoolAndCreateAListener()
174
    {
175
        $run = new CallbackRun(function () {
176
            return true;
177
        });
178
        $pool = Mockery::mock(PoolInterface::class, RunInterface::class);
179
        $pool->allows([
180
            'isRunning'   => false,
181
            'hasStarted'  => false,
182
            'getPriority' => 1,
183
            'getAll'      => [$run],
184
        ]);
185
186
        $callback = null;
187
        $pool->allows()
188
             ->addListener(
0 ignored issues
show
Bug introduced by
The method addListener() does not exist on Mockery\ExpectationInterface. It seems like you code against a sub-type of Mockery\ExpectationInterface such as Mockery\CompositeExpectation. ( Ignorable by Annotation )

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

188
             ->/** @scrutinizer ignore-call */ addListener(
Loading history...
Bug introduced by
The method addListener() does not exist on Mockery\Expectation. ( Ignorable by Annotation )

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

188
             ->/** @scrutinizer ignore-call */ addListener(

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
189
                 PoolRunEvent::POOL_RUN_ADDED,
190
                 Mockery::on(function (callable $func) use (&$callback) {
191
                     $callback = $func;
192
                     return true;
193
                 })
194
             )
195
             ->once();
196
197
        $priorityPool = new PriorityPool();
198
        $priorityPool->add($pool);
0 ignored issues
show
Bug introduced by
$pool of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

198
        $priorityPool->add(/** @scrutinizer ignore-type */ $pool);
Loading history...
199
200
        $this->assertCount(1, $priorityPool->getAll());
201
        $this->assertEquals([$run], $priorityPool->getAll());
202
203
        $run2 = new CallbackRun(function () {
204
            return true;
205
        });
206
207
        call_user_func($callback, new PoolRunEvent($pool, $run2));
0 ignored issues
show
Bug introduced by
$pool of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\PoolInterface expected by parameter $pool of Graze\ParallelProcess\Ev...RunEvent::__construct(). ( Ignorable by Annotation )

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

207
        call_user_func($callback, new PoolRunEvent(/** @scrutinizer ignore-type */ $pool, $run2));
Loading history...
208
209
        $this->assertCount(2, $priorityPool->getAll());
210
        $this->assertEquals([$run, $run2], $priorityPool->getAll());
211
    }
212
213
    /**
214
     * @expectedException \Graze\ParallelProcess\Exceptions\NotRunningException
215
     */
216
    public function testPriorityPoolUnableToAddRunningProcessWhenPoolHasNotStarted()
217
    {
218
        $run = Mockery::mock(RunInterface::class);
219
        $run->shouldReceive('isRunning')
220
            ->andReturn(true);
221
222
        $priorityPool = new PriorityPool();
223
224
        $priorityPool->add($run);
0 ignored issues
show
Bug introduced by
$run of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

224
        $priorityPool->add(/** @scrutinizer ignore-type */ $run);
Loading history...
225
    }
226
227
    public function testPriorityPoolInstantRunStates()
228
    {
229
        $priorityPool = new PriorityPool();
230
231
        $this->assertFalse($priorityPool->isRunInstantly());
232
        $this->assertSame($priorityPool, $priorityPool->setRunInstantly(true));
233
        $this->assertTrue($priorityPool->isRunInstantly());
234
    }
235
236
    public function testPriorityPoolInitialStateWithInstantRun()
237
    {
238
        $process = Mockery::mock(Process::class);
239
        $process->shouldReceive('stop');
240
        $process->shouldReceive('isStarted')->andReturn(false, false, false, true);
241
        $process->shouldReceive('isRunning')->andReturn(false, false, true, false);
242
        $process->shouldReceive('start')->atLeast()->once();
243
        $process->shouldReceive('isSuccessful')->once()->andReturn(true);
244
245
        $priorityPool = new PriorityPool([$process], PriorityPool::NO_MAX, true);
246
247
        $this->assertTrue($priorityPool->hasStarted());
248
        $this->assertTrue($priorityPool->isRunning());
249
        $this->assertFalse($priorityPool->isSuccessful());
250
        $this->assertTrue($priorityPool->isRunInstantly());
251
252
        $priorityPool->poll();
253
254
        $this->assertTrue($priorityPool->hasStarted());
255
        $this->assertFalse($priorityPool->isRunning());
256
        $this->assertTrue($priorityPool->isSuccessful());
257
    }
258
259
    public function testPriorityPoolRunsRunWhenInstantRunIsOn()
260
    {
261
        $priorityPool = new PriorityPool();
262
        $priorityPool->setRunInstantly(true);
263
264
        $process = Mockery::mock(Process::class);
265
        $process->shouldReceive('stop');
266
        $process->shouldReceive('isStarted')->andReturn(false, false, false, true);
267
        $process->shouldReceive('isRunning')->andReturn(false, false, true, false);
268
        $process->shouldReceive('start')->atLeast()->once();
269
        $process->shouldReceive('isSuccessful')->once()->andReturn(true);
270
271
        $this->assertFalse($priorityPool->hasStarted());
272
        $this->assertFalse($priorityPool->isRunning());
273
        $this->assertFalse($priorityPool->isSuccessful());
274
275
        $priorityPool->add($process);
0 ignored issues
show
Bug introduced by
$process of type Mockery\MockInterface is incompatible with the type Graze\ParallelProcess\Po...lelProcess\RunInterface expected by parameter $item of Graze\ParallelProcess\PriorityPool::add(). ( Ignorable by Annotation )

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

275
        $priorityPool->add(/** @scrutinizer ignore-type */ $process);
Loading history...
276
277
        $this->assertTrue($priorityPool->hasStarted());
278
        $this->assertTrue($priorityPool->isRunning());
279
        $this->assertFalse($priorityPool->isSuccessful());
280
281
        $priorityPool->poll();
282
283
        $this->assertTrue($priorityPool->hasStarted());
284
        $this->assertFalse($priorityPool->isRunning());
285
        $this->assertTrue($priorityPool->isSuccessful());
286
    }
287
288
    public function testPriorityWillRunTheHighestFirst()
289
    {
290
        $priorityPool = new PriorityPool();
291
        $priorityPool->setRunInstantly(false)
292
                     ->setMaxSimultaneous(1);
293
294
        $run1 = new CallbackRun(
295
            function () {
296
                return true;
297
            },
298
            [],
299
            1.5
300
        );
301
302
        $priorityPool->add($run1);
303
304
        $run2 = new CallbackRun(
305
            function () {
306
                return true;
307
            },
308
            [],
309
            1.6
310
        );
311
312
        $priorityPool->add($run2);
313
314
        $this->assertFalse($priorityPool->hasStarted());
315
        $this->assertFalse($priorityPool->isRunning());
316
        $this->assertFalse($priorityPool->isSuccessful());
317
318
        $priorityPool->poll();
319
320
        $this->assertTrue($priorityPool->hasStarted());
321
        $this->assertTrue($priorityPool->isRunning());
322
        $this->assertFalse($priorityPool->isSuccessful());
323
324
        $waiting = $priorityPool->getWaiting();
325
326
        $this->assertSame($run1, reset($waiting));
327
328
        $priorityPool->poll();
329
330
        $this->assertTrue($priorityPool->hasStarted());
331
        $this->assertFalse($priorityPool->isRunning());
332
        $this->assertTrue($priorityPool->isSuccessful());
333
334
        $priorityPool->poll();
335
    }
336
337
    public function testModifyingThePriorityWillChangeWhichRunStartsFirst()
338
    {
339
        $pool = new PriorityPool();
340
        $pool->setMaxSimultaneous(1);
341
342
        $run = new CallbackRun(
343
            function () {
344
                return true;
345
            },
346
            [],
347
            1.1
348
        );
349
        $run2 = new CallbackRun(
350
            function () {
351
                return true;
352
            },
353
            [],
354
            1.2
355
        );
356
357
        $pool->add($run);
358
        $pool->add($run2);
359
360
        $run->setPriority(1.3);
361
362
        $pool->start();
363
364
        $this->assertTrue($run->hasStarted());
365
        $this->assertFalse($run2->hasStarted());
366
367
        $run3 = new CallbackRun(
368
            function () {
369
                return true;
370
            },
371
            [],
372
            1.05
373
        );
374
        $pool->add($run3);
375
376
        $run->setPriority(1.00);
377
378
        $this->assertTrue($run->hasStarted());
379
        $this->assertFalse($run2->hasStarted());
380
        $this->assertFalse($run3->hasStarted());
381
382
        $pool->poll();
383
384
        $this->assertTrue($run->hasStarted());
385
        $this->assertTrue($run2->hasStarted());
386
        $this->assertFalse($run3->hasStarted());
387
    }
388
}
389