GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 646ea8...4450cc )
by Robbie
13s
created

testExceptionWithMemoryExhaustion()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 20
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 12
nc 1
nop 0
1
<?php
2
3
namespace Symbiote\QueuedJobs\Tests;
4
5
use SilverStripe\Dev\SapphireTest;
6
use SilverStripe\Core\Config\Config;
7
use SilverStripe\ORM\DataList;
8
use SilverStripe\ORM\DataObject;
9
use Symbiote\QueuedJobs\DataObjects\QueuedJobDescriptor;
10
use Symbiote\QueuedJobs\Services\QueuedJob;
11
use Symbiote\QueuedJobs\Services\QueuedJobService;
12
use Symbiote\QueuedJobs\Tests\QueuedJobsTest\TestExceptingJob;
13
use Symbiote\QueuedJobs\Tests\QueuedJobsTest\TestQueuedJob;
14
use Symbiote\QueuedJobs\Tests\QueuedJobsTest\TestQJService;
15
16
/**
17
 * @author Marcus Nyeholt <[email protected]>
18
 */
19
class QueuedJobsTest extends AbstractTest
20
{
21
    /**
22
     * We need the DB for this test
23
     *
24
     * @var bool
25
     */
26
    protected $usesDatabase = true;
27
28
    /**
29
     * {@inheritDoc}
30
     */
31
    protected function setUp()
32
    {
33
        parent::setUp();
34
35
        // Two restarts are allowed per job
36
        Config::modify()->set(QueuedJobService::class, 'stall_threshold', 2);
37
    }
38
39
    /**
40
     * @return QueuedJobService
41
     */
42
    protected function getService()
43
    {
44
        return singleton(TestQJService::class);
45
    }
46
47
    public function testQueueJob()
48
    {
49
        $svc = $this->getService();
50
51
        // lets create a new job and add it tio the queue
52
        $job = new TestQueuedJob();
53
        $jobId = $svc->queueJob($job);
54
        $list = $svc->getJobList();
55
56
        $this->assertEquals(1, $list->count());
57
58
        $myJob = null;
59
        foreach ($list as $job) {
60
            if ($job->Implementation == TestQueuedJob::class) {
61
                $myJob = $job;
62
                break;
63
            }
64
        }
65
66
        $this->assertNotNull($myJob);
67
        $this->assertTrue($jobId > 0);
68
        $this->assertEquals(TestQueuedJob::class, $myJob->Implementation);
69
        $this->assertNotNull($myJob->SavedJobData);
70
    }
71
72
    public function testJobRunAs()
73
    {
74
        $svc = $this->getService();
75
        $list = $svc->getJobList();
76
        foreach ($list as $job) {
77
            $job->delete();
78
        }
79
80
        $this->logInWithPermission('DUMMY');
81
82
        // lets create a new job and add it tio the queue
83
        $job = new TestQueuedJob();
84
        $job->runningAs = "DUMMY";
0 ignored issues
show
Documentation introduced by
The property runningAs does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
85
        $jobId = $svc->queueJob($job);
0 ignored issues
show
Unused Code introduced by
$jobId is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
86
        $list = $svc->getJobList();
87
88
        $myJob = $list->First();
89
90
        $this->assertEquals("[email protected]", $myJob->RunAs()->Email);
91
    }
92
93
    public function testQueueSignature()
94
    {
95
        $svc = $this->getService();
96
97
        // lets create a new job and add it tio the queue
98
        $job = new TestQueuedJob();
99
        $jobId = $svc->queueJob($job);
100
101
        $newJob = new TestQueuedJob();
102
        $newId = $svc->queueJob($newJob);
103
104
        $this->assertEquals($jobId, $newId);
105
106
        // now try another, but with different params
107
        $newJob = new TestQueuedJob();
108
        $newJob->randomParam = 'stuff';
0 ignored issues
show
Documentation introduced by
The property randomParam does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
109
        $newId = $svc->queueJob($newJob);
110
111
        $this->assertNotEquals($jobId, $newId);
112
    }
113
114
    public function testProcessJob()
115
    {
116
        $job = new TestQueuedJob();
117
        $job->setup();
118
        $job->process();
119
        // we should now have some  data
120
        $data = $job->getJobData();
121
        $this->assertNotNull($data->messages);
122
        $this->assertFalse($data->isComplete);
123
124
        $jd = $data->jobData;
125
        $this->assertTrue(isset($jd->times));
126
        $this->assertEquals(1, count($jd->times));
127
128
        // now take the 'saved' data and try restoring the job
129
    }
130
131
    public function testResumeJob()
132
    {
133
        $job = new TestQueuedJob();
134
        $job->setup();
135
        $job->process();
136
        // we should now have some  data
137
        $data = $job->getJobData();
138
139
        // so create a new job and restore it from this data
140
141
        $job = new TestQueuedJob();
142
        $job->setup();
143
144
        $job->setJobData($data->totalSteps, $data->currentStep, $data->isComplete, $data->jobData, $data->messages);
145
        $job->process();
146
147
        $data = $job->getJobData();
148
        $this->assertFalse($data->isComplete);
149
        $jd = $data->jobData;
150
        $this->assertTrue(isset($jd->times));
151
        $this->assertEquals(2, count($jd->times));
152
    }
153
154
    public function testInitialiseJob()
155
    {
156
        // okay, lets test it out on the actual service
157
        $svc = $this->getService();
158
        // lets create a new job and add it to the queue
159
        $job = new TestQueuedJob();
160
        $id = $svc->queueJob($job);
161
162
        $descriptor = DataObject::get_by_id(QueuedJobDescriptor::class, $id);
163
164
        $job = $svc->testInit($descriptor);
165
        $this->assertInstanceOf(TestQueuedJob::class, $job, 'Job has been triggered');
166
167
        $descriptor = DataObject::get_by_id(QueuedJobDescriptor::class, $id);
168
169
        $this->assertEquals(QueuedJob::STATUS_INIT, $descriptor->JobStatus);
170
    }
171
172
    public function testStartJob()
173
    {
174
        // okay, lets test it out on the actual service
175
        $svc = $this->getService();
176
        // lets create a new job and add it to the queue
177
178
        $this->logInWithPermission('DUMMYUSER');
179
180
        $job = new TestQueuedJob();
181
        $job->testingStartJob = true;
0 ignored issues
show
Documentation introduced by
The property testingStartJob does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
182
        $id = $svc->queueJob($job);
183
184
        $this->logInWithPermission('ADMIN');
185
186
        $result = $svc->runJob($id);
187
        $this->assertTrue($result);
188
189
        // we want to make sure that the current user is the runas user of the job
190
        $descriptor = DataObject::get_by_id(QueuedJobDescriptor::class, $id);
191
        $this->assertEquals('Complete', $descriptor->JobStatus);
192
    }
193
194
    public function testImmediateQueuedJob()
195
    {
196
        // okay, lets test it out on the actual service
197
        $svc = $this->getService();
198
        // lets create a new job and add it to the queue
199
200
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
201
        $job->firstJob = true;
0 ignored issues
show
Documentation introduced by
The property firstJob does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
202
        $id = $svc->queueJob($job);
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
203
204
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
205
        $job->secondJob = true;
0 ignored issues
show
Documentation introduced by
The property secondJob does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
206
        $id = $svc->queueJob($job);
0 ignored issues
show
Unused Code introduced by
$id is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
207
208
        $jobs = $svc->getJobList(QueuedJob::IMMEDIATE);
209
        $this->assertEquals(2, $jobs->count());
210
211
        // now fake a shutdown
212
        $svc->onShutdown();
213
214
        $jobs = $svc->getJobList(QueuedJob::IMMEDIATE);
215
        $this->assertInstanceOf(DataList::class, $jobs);
216
        $this->assertEquals(0, $jobs->count());
217
    }
218
219
    public function testNextJob()
220
    {
221
        $svc = $this->getService();
222
        $list = $svc->getJobList();
223
224
        foreach ($list as $job) {
225
            $job->delete();
226
        }
227
228
        $list = $svc->getJobList();
229
        $this->assertEquals(0, $list->count());
230
231
        $job = new TestQueuedJob();
232
        $id1 = $svc->queueJob($job);
233
234
        $job = new TestQueuedJob();
235
        // to get around the signature checks
236
        $job->randomParam = 'me';
0 ignored issues
show
Documentation introduced by
The property randomParam does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
237
        $id2 = $svc->queueJob($job);
0 ignored issues
show
Unused Code introduced by
$id2 is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
238
239
        $job = new TestQueuedJob();
240
        // to get around the signature checks
241
        $job->randomParam = 'mo';
0 ignored issues
show
Documentation introduced by
The property randomParam does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
242
        $id3 = $svc->queueJob($job);
243
244
        $this->assertEquals(2, $id3 - $id1);
245
246
        $list = $svc->getJobList();
247
        $this->assertEquals(3, $list->count());
248
249
        // okay, lets get the first one and initialise it, then make sure that a subsequent init attempt fails
250
        $job = $svc->getNextPendingJob();
251
252
        $this->assertEquals($id1, $job->ID);
253
        $svc->testInit($job);
254
255
        // now try and get another, it should be === false
256
        $next = $svc->getNextPendingJob();
257
258
        $this->assertFalse($next);
259
    }
260
261
    /**
262
     * Verify that broken jobs are correctly verified for health and restarted as necessary
263
     *
264
     * Order of checkJobHealth() and getNextPendingJob() is important
265
     *
266
     * Execution of this job is broken into several "loops", each of which represents one invocation
267
     * of ProcessJobQueueTask
268
     */
269
    public function testJobHealthCheck()
270
    {
271
        // Create a job and add it to the queue
272
        $svc = $this->getService();
273
        $logger = $svc->getLogger();
274
        $job = new TestQueuedJob(QueuedJob::IMMEDIATE);
275
        $job->firstJob = true;
0 ignored issues
show
Documentation introduced by
The property firstJob does not exist on object<Symbiote\QueuedJo...JobsTest\TestQueuedJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
276
        $id = $svc->queueJob($job);
277
        $descriptor = QueuedJobDescriptor::get()->byID($id);
278
279
        // Verify initial state is new and LastProcessedCount is not marked yet
280
        $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
281
        $this->assertEquals(0, $descriptor->StepsProcessed);
282
        $this->assertEquals(-1, $descriptor->LastProcessedCount);
283
        $this->assertEquals(0, $descriptor->ResumeCounts);
284
285
        // Loop 1 - Pick up new job and attempt to run it
286
        // Job health should not attempt to cleanup unstarted jobs
287
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
288
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
289
290
        // Ensure that this is the next job ready to go
291
        $descriptor = QueuedJobDescriptor::get()->byID($id);
292
        $this->assertEquals($nextJob->ID, $descriptor->ID);
293
        $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
294
        $this->assertEquals(0, $descriptor->StepsProcessed);
295
        $this->assertEquals(-1, $descriptor->LastProcessedCount);
296
        $this->assertEquals(0, $descriptor->ResumeCounts);
297
298
        // Run 1 - Start the job (no work is done)
299
        $descriptor->JobStatus = QueuedJob::STATUS_INIT;
300
        $descriptor->write();
301
302
        // Assume that something bad happens at this point, the process dies during execution, and
303
        // the task is re-initiated somewhere down the track
304
305
        // Loop 2 - Detect broken job, and mark it for future checking.
306
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
307
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
308
309
        // Note that we don't immediately try to restart it until StepsProcessed = LastProcessedCount
310
        $descriptor = QueuedJobDescriptor::get()->byID($id);
311
        $this->assertFalse($nextJob); // Don't run it this round please!
312
        $this->assertEquals(QueuedJob::STATUS_INIT, $descriptor->JobStatus);
313
        $this->assertEquals(0, $descriptor->StepsProcessed);
314
        $this->assertEquals(0, $descriptor->LastProcessedCount);
315
        $this->assertEquals(0, $descriptor->ResumeCounts);
316
317
        // Loop 3 - We've previously marked this job as broken, so restart it this round
318
        // If no more work has been done on the job at this point, assume that we are able to
319
        // restart it
320
        $logger->clear();
321
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
322
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
323
324
        // This job is resumed and exeuction is attempted this round
325
        $descriptor = QueuedJobDescriptor::get()->byID($id);
326
        $this->assertEquals($nextJob->ID, $descriptor->ID);
327
        $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
328
        $this->assertEquals(0, $descriptor->StepsProcessed);
329
        $this->assertEquals(0, $descriptor->LastProcessedCount);
330
        $this->assertEquals(1, $descriptor->ResumeCounts);
331
        $this->assertContains('A job named A Test job appears to have stalled. It will be stopped and restarted, please login to make sure it has continued', $logger->getMessages());
332
333
        // Run 2 - First restart (work is done)
334
        $descriptor->JobStatus = QueuedJob::STATUS_RUN;
335
        $descriptor->StepsProcessed++; // Essentially delays the next restart by 1 loop
336
        $descriptor->write();
337
338
        // Once again, at this point, assume the job fails and crashes
339
340
        // Loop 4 - Assuming a job has LastProcessedCount < StepsProcessed we are in the same
341
        // situation as step 2.
342
        // Because the last time the loop ran, StepsProcessed was incremented,
343
        // this indicates that it's likely that another task could be working on this job, so
344
        // don't run this.
345
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
346
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
347
348
        $descriptor = QueuedJobDescriptor::get()->byID($id);
349
        $this->assertFalse($nextJob); // Don't run jobs we aren't sure should be restarted
350
        $this->assertEquals(QueuedJob::STATUS_RUN, $descriptor->JobStatus);
351
        $this->assertEquals(1, $descriptor->StepsProcessed);
352
        $this->assertEquals(1, $descriptor->LastProcessedCount);
353
        $this->assertEquals(1, $descriptor->ResumeCounts);
354
355
        // Loop 5 - Job is again found to not have been restarted since last iteration, so perform second
356
        // restart. The job should be attempted to run this loop
357
        $logger->clear();
358
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
359
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
360
361
        // This job is resumed and exeuction is attempted this round
362
        $descriptor = QueuedJobDescriptor::get()->byID($id);
363
        $this->assertEquals($nextJob->ID, $descriptor->ID);
364
        $this->assertEquals(QueuedJob::STATUS_WAIT, $descriptor->JobStatus);
365
        $this->assertEquals(1, $descriptor->StepsProcessed);
366
        $this->assertEquals(1, $descriptor->LastProcessedCount);
367
        $this->assertEquals(2, $descriptor->ResumeCounts);
368
        $this->assertContains('A job named A Test job appears to have stalled. It will be stopped and restarted, please login to make sure it has continued', $logger->getMessages());
369
370
        // Run 3 - Second and last restart (no work is done)
371
        $descriptor->JobStatus = QueuedJob::STATUS_RUN;
372
        $descriptor->write();
373
374
        // Loop 6 - As no progress has been made since loop 3, we can mark this as dead
375
        $logger->clear();
376
        $svc->checkJobHealth(QueuedJob::IMMEDIATE);
377
        $nextJob = $svc->getNextPendingJob(QueuedJob::IMMEDIATE);
378
379
        // Since no StepsProcessed has been done, don't wait another loop to mark this as dead
380
        $descriptor = QueuedJobDescriptor::get()->byID($id);
381
        $this->assertEquals(QueuedJob::STATUS_PAUSED, $descriptor->JobStatus);
382
        $this->assertEmpty($nextJob);
383
        $this->assertContains('A job named A Test job appears to have stalled. It has been paused, please login to check it', $logger->getMessages());
384
    }
385
386
    public function testExceptionWithMemoryExhaustion()
387
    {
388
        $svc = $this->getService();
389
        $job = new TestExceptingJob();
390
        $job->firstJob = true;
0 ignored issues
show
Documentation introduced by
The property firstJob does not exist on object<Symbiote\QueuedJo...sTest\TestExceptingJob>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
391
        $id = $svc->queueJob($job);
392
        $descriptor = QueuedJobDescriptor::get()->byID($id);
0 ignored issues
show
Unused Code introduced by
$descriptor is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
393
394
        // we want to set the memory limit _really_ low so that our first run triggers
395
        $mem = Config::inst()->get('QueuedJobService', 'memory_limit');
396
        Config::inst()->update('QueuedJobService', 'memory_limit', 1);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...s\DeltaConfigCollection, SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
397
398
        $svc->runJob($id);
399
400
        Config::inst()->update('QueuedJobService', 'memory_limit', $mem);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface SilverStripe\Config\Coll...nfigCollectionInterface as the method update() does only exist in the following implementations of said interface: SilverStripe\Config\Coll...s\DeltaConfigCollection, SilverStripe\Config\Coll...\MemoryConfigCollection.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
401
402
        $descriptor = QueuedJobDescriptor::get()->byID($id);
403
404
        $this->assertEquals(QueuedJob::STATUS_BROKEN, $descriptor->JobStatus);
405
    }
406
407
    public function testCheckdefaultJobs()
408
    {
409
        // Create a job and add it to the queue
410
        $svc = $this->getService();
411
        $testDefaultJobsArray = array(
412
            'ArbitraryName' => array(
413
                # I'll get restarted and create an alert email
414
                'type' => TestQueuedJob::class,
415
                'filter' => array(
416
                    'JobTitle' => "A Test job"
417
                ),
418
                'recreate' => 1,
419
                'construct' => array(
420
                    'queue' => QueuedJob::QUEUED
421
                ),
422
                'startDateFormat' => 'Y-m-d 02:00:00',
423
                'startTimeString' => 'tomorrow',
424
                'email' => '[email protected]'
425
            ));
426
        $svc->defaultJobs = $testDefaultJobsArray;
427
        $jobConfig = $testDefaultJobsArray['ArbitraryName'];
428
429
        $activeJobs = QueuedJobDescriptor::get()->filter(
430
            'JobStatus',
431
            array(
432
                QueuedJob::STATUS_NEW,
433
                QueuedJob::STATUS_INIT,
434
                QueuedJob::STATUS_RUN,
435
                QueuedJob::STATUS_WAIT,
436
                QueuedJob::STATUS_PAUSED
437
            )
438
        );
439
        //assert no jobs currently active
440
        $this->assertEquals(0, $activeJobs->count());
441
442
        //add a default job to the queue
443
        $svc->checkdefaultJobs();
444
        $this->assertEquals(1, $activeJobs->count());
445
        $descriptor = $activeJobs->filter(array_merge(
446
            array('Implementation' => $jobConfig['type']),
447
            $jobConfig['filter']
448
        ))->first();
449
        // Verify initial state is new
450
        $this->assertEquals(QueuedJob::STATUS_NEW, $descriptor->JobStatus);
451
452
        //update Job to paused
453
        $descriptor->JobStatus = QueuedJob::STATUS_PAUSED;
454
        $descriptor->write();
455
        //check defaults the paused job shoudl be ignored
456
        $svc->checkdefaultJobs();
457
        $this->assertEquals(1, $activeJobs->count());
458
        //assert we now still have 1 of our job (paused)
459
        $this->assertEquals(1, QueuedJobDescriptor::get()->count());
460
461
        //update Job to broken
462
        $descriptor->JobStatus = QueuedJob::STATUS_BROKEN;
463
        $descriptor->write();
464
        //check and add job for broken job
465
        $svc->checkdefaultJobs();
466
        $this->assertEquals(1, $activeJobs->count());
467
        //assert we now have 2 of our job (one good one broken)
468
        $this->assertEquals(2, QueuedJobDescriptor::get()->count());
469
470
        //test not adding a job when job is there already
471
        $svc->checkdefaultJobs();
472
        $this->assertEquals(1, $activeJobs->count());
473
        //assert we now have 2 of our job (one good one broken)
474
        $this->assertEquals(2, QueuedJobDescriptor::get()->count());
475
476
        //test add jobs with various start dates
477
        $job = $activeJobs->first();
478
        date('Y-m-d 02:00:00', strtotime('+1 day'));
479
        $this->assertEquals(date('Y-m-d 02:00:00', strtotime('+1 day')), $job->StartAfter);
480
        //swap start time to midday
481
        $testDefaultJobsArray['ArbitraryName']['startDateFormat'] = 'Y-m-d 12:00:00';
482
        //clean up then add new jobs
483
        $svc->defaultJobs = $testDefaultJobsArray;
484
        $activeJobs->removeAll();
485
        $svc->checkdefaultJobs();
486
        //assert one jobs currently active
487
        $this->assertEquals(1, $activeJobs->count());
488
        $job = $activeJobs->first();
489
        $this->assertEquals(date('Y-m-d 12:00:00', strtotime('+1 day')), $job->StartAfter);
490
        //test alert email
491
        $email = $this->findEmail('[email protected]');
492
        $this->assertNotNull($email);
493
494
        //test broken job config
495
        unset($testDefaultJobsArray['ArbitraryName']['startDateFormat']);
496
        //clean up then add new jobs
497
        $svc->defaultJobs = $testDefaultJobsArray;
498
        $activeJobs->removeAll();
499
        $svc->checkdefaultJobs();
500
    }
501
}
502