Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace core\task;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
require_once(__DIR__ . '/../fixtures/task_fixtures.php');
21
 
22
/**
23
 * Test class for scheduled task.
24
 *
25
 * @package core
26
 * @category test
27
 * @copyright 2013 Damyon Wiese
28
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1441 ariadna 29
 * @covers \core\task\scheduled_task
1 efrain 30
 */
1441 ariadna 31
final class scheduled_task_test extends \advanced_testcase {
1 efrain 32
 
33
    /**
34
     * Data provider for {@see test_eval_cron_field}
35
     *
36
     * @return array
37
     */
38
    public static function eval_cron_provider(): array {
39
        return [
40
            // At every 3rd <unit>.
41
            ['*/3', 0, 29, [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]],
42
            // At <unit> 1 and every 2nd <unit>.
43
            ['1,*/2', 0, 29, [0, 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28]],
44
            // At every <unit> from 1 through 10 and every <unit> from 5 through 15.
45
            ['1-10,5-15', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]],
46
            // At every <unit> from 1 through 10 and every 2nd <unit> from 5 through 15.
47
            ['1-10,5-15/2', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15]],
48
            // At every <unit> from 1 through 10 and every 2nd <unit> from 5 through 29.
49
            ['1-10,5/2', 0, 29, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29]],
50
            // At <unit> 1, 2, 3.
51
            ['1,2,3,1,2,3', 0, 29, [1, 2, 3]],
52
            // Invalid.
53
            ['-1,10,80', 0, 29, []],
54
            // Invalid.
55
            ['-1', 0, 29, []],
56
        ];
57
    }
58
 
59
    /**
60
     * Test the cron scheduling method
61
     *
62
     * @param string $field
63
     * @param int $min
64
     * @param int $max
65
     * @param int[] $expected
66
     *
67
     * @dataProvider eval_cron_provider
68
     */
69
    public function test_eval_cron_field(string $field, int $min, int $max, array $expected): void {
70
        $testclass = new scheduled_test_task();
71
 
72
        $this->assertEquals($expected, $testclass->eval_cron_field($field, $min, $max));
73
    }
74
 
11 efrain 75
    public function test_get_next_scheduled_time(): void {
1 efrain 76
        $this->resetAfterTest();
77
 
1441 ariadna 78
        // Test for job run every 10 minutes.
1 efrain 79
        $testclass = new scheduled_test_task();
80
 
81
        // All fields default to '*'.
82
        $testclass->set_minute('*/10');
83
        // Next valid time should be next 10 minute boundary.
84
        $nexttime = $testclass->get_next_scheduled_time();
85
 
86
        $minutes = ((intval(date('i') / 10)) + 1) * 10;
87
        $nexttenminutes = mktime(date('H'), $minutes, 0);
88
 
89
        $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
90
 
91
        // Disabled flag does not affect next time.
92
        $testclass->set_disabled(true);
93
        $nexttime = $testclass->get_next_scheduled_time();
94
        $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.');
95
 
96
        // Test hourly job executed on Sundays only.
97
        $testclass = new scheduled_test_task();
98
        $testclass->set_minute('0');
99
        $testclass->set_day_of_week('7');
100
 
101
        $nexttime = $testclass->get_next_scheduled_time();
102
 
103
        $this->assertEquals(7, date('N', $nexttime));
104
        $this->assertEquals(0, date('i', $nexttime));
105
 
106
        // Test monthly job.
107
        $testclass = new scheduled_test_task();
108
        $testclass->set_minute('32');
109
        $testclass->set_hour('0');
110
        $testclass->set_day('1');
111
 
112
        $nexttime = $testclass->get_next_scheduled_time();
113
 
114
        $this->assertEquals(32, date('i', $nexttime));
115
        $this->assertEquals(0, date('G', $nexttime));
116
        $this->assertEquals(1, date('j', $nexttime));
117
    }
118
 
119
    /**
120
     * Data provider for get_next_scheduled_time_detail.
121
     *
122
     * Note all times in here are in default Australia/Perth time zone.
123
     *
124
     * @return array[] Function parameters for each run
125
     */
126
    public static function get_next_scheduled_time_detail_provider(): array {
127
        return [
128
            // Every minute = next minute.
129
            ['2023-11-01 15:15', '*', '*', '*', '*', '*', '2023-11-01 15:16'],
130
            // Specified minute (coming up) = same hour, that minute.
131
            ['2023-11-01 15:15', '18', '*', '*', '*', '*', '2023-11-01 15:18'],
132
            // Specified minute (passed) = next hour, that minute.
133
            ['2023-11-01 15:15', '11', '*', '*', '*', '*', '2023-11-01 16:11'],
134
            // Range of minutes = same hour, next matching value.
135
            ['2023-11-01 15:15', '*/15', '*', '*', '*', '*', '2023-11-01 15:30'],
136
            // Specified hour, any minute = first minute that hour.
137
            ['2023-11-01 15:15', '*', '20', '*', '*', '*', '2023-11-01 20:00'],
138
            // Specified hour, specified minute = that time.
139
            ['2023-11-01 15:15', '13', '20', '*', '*', '*', '2023-11-01 20:13'],
140
            // Any minute, range of hours = next hour in range, 00:00.
141
            ['2023-11-01 15:15', '*', '*/6', '*', '*', '*', '2023-11-01 18:00'],
142
            // Specified minute, range of hours = next hour where minute not passed, that minute.
143
            ['2023-11-01 18:15', '10', '*/6', '*', '*', '*', '2023-11-02 00:10'],
144
            // Specified day, any hour/minute.
145
            ['2023-11-01 15:15', '*', '*', '3', '*', '*', '2023-11-03 00:00'],
146
            // Specified day (next month), any hour/minute.
147
            ['2023-11-05 15:15', '*', '*', '3', '*', '*', '2023-12-03 00:00'],
148
            // Specified day, specified hour.
149
            ['2023-11-01 15:15', '*', '17', '3', '*', '*', '2023-11-03 17:00'],
150
            // Specified day, specified minute.
151
            ['2023-11-01 15:15', '17', '*', '3', '*', '*', '2023-11-03 00:17'],
152
            // 30th of every month, February.
153
            ['2023-01-31 15:15', '15', '10', '30', '*', '*', '2023-03-30 10:15'],
154
            // Friday, any time. 2023-11-01 is a Wednesday, so it will run in 2 days.
155
            ['2023-11-01 15:15', '*', '*', '*', '5', '*', '2023-11-03 00:00'],
156
            // Friday, any time (but it's already Friday).
157
            ['2023-11-03 15:15', '*', '*', '*', '5', '*', '2023-11-03 15:16'],
158
            // Sunday (week rollover).
159
            ['2023-11-01 15:15', '*', '*', '*', '0', '*', '2023-11-05 00:00'],
160
            // Specified days and day of week (days come first).
161
            ['2023-11-01 15:15', '*', '*', '2,4,6', '5', '*', '2023-11-02 00:00'],
162
            // Specified days and day of week (day of week comes first).
163
            ['2023-11-01 15:15', '*', '*', '4,6,8', '5', '*', '2023-11-03 00:00'],
164
            // Specified months.
165
            ['2023-11-01 15:15', '*', '*', '*', '*', '6,8,10,12', '2023-12-01 00:00'],
166
            // Specified months (crossing year).
167
            ['2023-11-01 15:15', '*', '*', '*', '*', '6,8,10', '2024-06-01 00:00'],
168
            // Specified months and day of week (i.e. first Sunday in December).
169
            ['2023-11-01 15:15', '*', '*', '*', '0', '6,8,10,12', '2023-12-03 00:00'],
170
            // It's already December, but the next Friday is not until next month.
171
            ['2023-12-30 15:15', '*', '*', '*', '5', '6,8,10,12', '2024-06-07 00:00'],
172
            // Around end of year.
173
            ['2023-12-31 23:00', '10', '3', '*', '*', '*', '2024-01-01 03:10'],
174
            // Some impossible requirements...
175
            ['2023-12-31 23:00', '*', '*', '30', '*', '2', scheduled_task::NEVER_RUN_TIME],
176
            ['2023-12-31 23:00', '*', '*', '31', '*', '9,4,6,11', scheduled_task::NEVER_RUN_TIME],
177
            // Normal years and leap years.
178
            ['2021-01-01 23:00', '*', '*', '28', '*', '2', '2021-02-28 00:00'],
179
            ['2021-01-01 23:00', '*', '*', '29', '*', '2', '2024-02-29 00:00'],
180
            // Missing leap year over century. Longest possible gap between runs.
181
            ['2096-03-01 00:00', '59', '23', '29', '*', '2', '2104-02-29 23:59'],
182
        ];
183
    }
184
 
185
    /**
186
     * Tests get_next_scheduled_time using a large number of example scenarios.
187
     *
188
     * @param string $now Current time (strtotime format)
189
     * @param string $minute Minute restriction list for task
190
     * @param string $hour Hour restriction list for task
191
     * @param string $day Day restriction list for task
192
     * @param string $dayofweek Day of week restriction list for task
193
     * @param string $month Month restriction list for task
194
     * @param string|int $expected Expected run time (strtotime format or time int)
195
     * @dataProvider get_next_scheduled_time_detail_provider
196
     */
197
    public function test_get_next_scheduled_time_detail(string $now, string $minute, string $hour,
198
            string $day, string $dayofweek, string $month, string|int $expected): void {
199
        // Create test task with specified times.
200
        $task = new scheduled_test_task();
201
        $task->set_minute($minute);
202
        $task->set_hour($hour);
203
        $task->set_day($day);
204
        $task->set_day_of_week($dayofweek);
205
        $task->set_month($month);
206
 
207
        // Check function results.
208
        $nowtime = strtotime($now);
209
        if (is_int($expected)) {
210
            $expectedtime = $expected;
211
        } else {
212
            $expectedtime = strtotime($expected);
213
        }
214
        $actualtime = $task->get_next_scheduled_time($nowtime);
215
        $this->assertEquals($expectedtime, $actualtime, 'Expected ' . $expected . ', actual ' . date('Y-m-d H:i', $actualtime));
216
    }
217
 
218
    /**
219
     * Tests get_next_scheduled_time around DST changes, with regard to the continuity of frequent
220
     * tasks.
221
     *
222
     * We want frequent tasks to keep progressing as normal and not randomly stop for an hour, or
223
     * suddenly decide they need to happen in the past.
224
     */
225
    public function test_get_next_scheduled_time_dst_continuity(): void {
226
        $this->resetAfterTest();
227
        $this->setTimezone('Europe/London');
228
 
229
        // Test task is set to run every 20 minutes (:00, :20, :40).
230
        $task = new scheduled_test_task();
231
        $task->set_minute('*/20');
232
 
233
        // DST change forwards. Check times in GMT to ensure it progresses as normal.
234
        $before = strtotime('2023-03-26 00:59 GMT');
235
        $this->assertEquals(strtotime('2023-03-26 00:59 Europe/London'), $before);
236
        $one = $task->get_next_scheduled_time($before);
237
        $this->assertEquals(strtotime('2023-03-26 01:00 GMT'), $one);
238
        $this->assertEquals(strtotime('2023-03-26 02:00 Europe/London'), $one);
239
        $two = $task->get_next_scheduled_time($one);
240
        $this->assertEquals(strtotime('2023-03-26 01:20 GMT'), $two);
241
        $three = $task->get_next_scheduled_time($two);
242
        $this->assertEquals(strtotime('2023-03-26 01:40 GMT'), $three);
243
        $four = $task->get_next_scheduled_time($three);
244
        $this->assertEquals(strtotime('2023-03-26 02:00 GMT'), $four);
245
 
246
        // DST change backwards.
247
        $before = strtotime('2023-10-29 00:59 GMT');
248
        // The 'before' time is 01:59 Europe/London, but we won't explicitly test that because
249
        // there are two 01:59s so it might fail depending on implementation.
250
        $one = $task->get_next_scheduled_time($before);
251
        $this->assertEquals(strtotime('2023-10-29 01:00 GMT'), $one);
252
        // We cannot compare against the Eerope/London time (01:00) because there are two 01:00s.
253
        $two = $task->get_next_scheduled_time($one);
254
        $this->assertEquals(strtotime('2023-10-29 01:20 GMT'), $two);
255
        $three = $task->get_next_scheduled_time($two);
256
        $this->assertEquals(strtotime('2023-10-29 01:40 GMT'), $three);
257
        $four = $task->get_next_scheduled_time($three);
258
        $this->assertEquals(strtotime('2023-10-29 02:00 GMT'), $four);
259
        // This time is now unambiguous in Europe/London.
260
        $this->assertEquals(strtotime('2023-10-29 02:00 Europe/London'), $four);
261
    }
262
 
1441 ariadna 263
    /**
264
     * Tests get_next_scheduled_time for tasks set to run at the hour the time changes
265
     * for Daylight Saving Time.
266
     */
267
    public function test_get_next_scheduled_time_dst_hour(): void {
268
        $this->resetAfterTest();
269
 
270
        // Check a timezone where the clock change happens at 1am.
271
        $this->setTimezone('Europe/London');
272
        // Test task is set to run every day at 1am and 3am.
273
        $task = new scheduled_test_task();
274
        $task->set_hour('1, 3');
275
        $task->set_minute('0');
276
 
277
        // DST change forwards. Check times in GMT to ensure it progresses as normal.
278
        $now = strtotime('2025-03-30 00:59 GMT');
279
        $this->assertEquals(strtotime('2025-03-30 00:59 Europe/London'), $now);
280
        $nexttime = $task->get_next_scheduled_time($now);
281
        $this->assertEquals(strtotime('2025-03-30 01:00 GMT'), $nexttime);
282
        $this->assertEquals(strtotime('2025-03-30 02:00 Europe/London'), $nexttime);
283
 
284
        // Check the next run is at 3am.
285
        $nexttime = $task->get_next_scheduled_time($nexttime);
286
        $this->assertEquals(strtotime('2025-03-30 02:00 GMT'), $nexttime);
287
        $this->assertEquals(strtotime('2025-03-30 03:00 Europe/London'), $nexttime);
288
 
289
        // Test task is set to run every day at 1am.
290
        $task = new scheduled_test_task();
291
        $task->set_hour('1');
292
        $task->set_minute('0');
293
 
294
        // DST change backwards.
295
        $now = strtotime('2025-10-25 00:01 GMT');
296
        $this->assertEquals(strtotime('2025-10-25 01:01 Europe/London'), $now);
297
        $nexttime = $task->get_next_scheduled_time($now);
298
        // The next time is 1:00 Europe/London, but we won't explicitly test that because
299
        // there are two 1:00s so it might fail depending on implementation.
300
        $this->assertEquals(strtotime('2025-10-26 01:00 GMT'), $nexttime);
301
 
302
        // Check a timezone where the clock change happens at midnight.
303
        $this->setTimezone('Africa/Cairo');
304
        // Test task is set to run every day at midnight and 3am.
305
        $task = new scheduled_test_task();
306
        $task->set_hour('0, 3');
307
        $task->set_minute('0');
308
 
309
        // DST change forwards. Check times in GMT to ensure it progresses as normal.
310
        $now = strtotime('2025-04-24 23:59 GMT+2');
311
        $this->assertEquals(strtotime('2025-04-24 23:59 Africa/Cairo'), $now);
312
        $nexttime = $task->get_next_scheduled_time($now);
313
        $this->assertEquals(strtotime('2025-04-25 00:00 GMT+2'), $nexttime);
314
        $this->assertEquals(strtotime('2025-04-25 01:00 Africa/Cairo'), $nexttime);
315
 
316
        // Test task is set to run every day at 11pm.
317
        $task = new scheduled_test_task();
318
        $task->set_hour('23');
319
        $task->set_minute('0');
320
 
321
        // DST change backwards.
322
        $now = strtotime('2025-10-29 23:01 GMT+3');
323
        $this->assertEquals(strtotime('2025-10-29 23:01 Africa/Cairo'), $now);
324
        $nexttime = $task->get_next_scheduled_time($now);
325
        // The next time is 00:00 Africa/Cairo, but we won't explicitly test that because
326
        // there are two 00:00s so it might fail depending on implementation.
327
        $this->assertEquals(strtotime('2025-10-30 23:00 GMT+2'), $nexttime);
328
    }
329
 
330
    /**
331
     * Tests get_next_scheduled_time for tasks set to run at minute intervals during
332
     * the hour the time changes for Daylight Saving Time.
333
     */
334
    public function test_get_next_scheduled_time_dst_hour_minute(): void {
335
        $this->resetAfterTest();
336
 
337
        // Check a timezone where the clock change happens at 1am.
338
        $this->setTimezone('Europe/London');
339
        // Test task is set to run every day at 1:00, 1:20, 1:40.
340
        $task = new scheduled_test_task();
341
        $task->set_hour('1');
342
        $task->set_minute('*/20');
343
 
344
        // DST change forwards. 1am doesn't exist so the runs happen during the following hour.
345
        $before = strtotime('2025-03-30 00:59 GMT');
346
        $this->assertEquals(strtotime('2025-03-30 00:59 Europe/London'), $before);
347
        $one = $task->get_next_scheduled_time($before);
348
        $this->assertEquals(strtotime('2025-03-30 02:00 Europe/London'), $one);
349
        $two = $task->get_next_scheduled_time($one);
350
        $this->assertEquals(strtotime('2025-03-30 02:20 Europe/London'), $two);
351
        $three = $task->get_next_scheduled_time($two);
352
        $this->assertEquals(strtotime('2025-03-30 02:40 Europe/London'), $three);
353
        $four = $task->get_next_scheduled_time($three);
354
        $this->assertEquals(strtotime('2025-03-31 01:00 Europe/London'), $four);
355
 
356
        // DST change backwards. 1am happens twice, but we only expect the runs to happen during
357
        // the second occurrence.
358
        $before = strtotime('2025-10-25 23:59 GMT');
359
        $this->assertEquals(strtotime('2025-10-26 00:59 Europe/London'), $before);
360
        // The next 3 times happen during the second occurrence of 01:00 Europe/London, but we
361
        // won't explicitly test that because there are two 01:00s so it might fail depending
362
        // on implementation.
363
        $one = $task->get_next_scheduled_time($before);
364
        $this->assertEquals(strtotime('2025-10-26 01:00 GMT'), $one);
365
        $two = $task->get_next_scheduled_time($one);
366
        $this->assertEquals(strtotime('2025-10-26 01:20 GMT'), $two);
367
        $three = $task->get_next_scheduled_time($two);
368
        $this->assertEquals(strtotime('2025-10-26 01:40 GMT'), $three);
369
        $four = $task->get_next_scheduled_time($three);
370
        $this->assertEquals(strtotime('2025-10-27 1:00 GMT'), $four);
371
        $this->assertEquals(strtotime('2025-10-27 1:00 Europe/London'), $four);
372
 
373
        // Test task is set to run every thirty minutes over the time change - midnight to 3am.
374
        $task = new scheduled_test_task();
375
        $task->set_hour('0,1,2,3');
376
        $task->set_minute('*/30');
377
 
378
        // DST change forwards. We expect 1am to be skipped, but not replaced by the next hour
379
        // as the task will run anyway during 2am.
380
        $before = strtotime('2025-03-29 23:59 GMT');
381
        $this->assertEquals(strtotime('2025-03-29 23:59 Europe/London'), $before);
382
        $midnight = $task->get_next_scheduled_time($before);
383
        $this->assertEquals(strtotime('2025-03-30 00:00 Europe/London'), $midnight);
384
        $midnightb = $task->get_next_scheduled_time($midnight);
385
        $this->assertEquals(strtotime('2025-03-30 00:30 Europe/London'), $midnightb);
386
        // 1am is skipped because it doesn't exist.
387
        $two = $task->get_next_scheduled_time($midnightb);
388
        $this->assertEquals(strtotime('2025-03-30 02:00 Europe/London'), $two);
389
        $twob = $task->get_next_scheduled_time($two);
390
        $this->assertEquals(strtotime('2025-03-30 02:30 Europe/London'), $twob);
391
        $three = $task->get_next_scheduled_time($twob);
392
        $this->assertEquals(strtotime('2025-03-30 03:00 Europe/London'), $three);
393
        $threeb = $task->get_next_scheduled_time($three);
394
        $this->assertEquals(strtotime('2025-03-30 03:30 Europe/London'), $threeb);
395
        $nextday = $task->get_next_scheduled_time($threeb);
396
        $this->assertEquals(strtotime('2025-03-31 00:00 Europe/London'), $nextday);
397
 
398
        // DST change backwards. A bit of a weird one - the task actually runs for an extra
399
        // hour because 1am happens twice.
400
        $before = strtotime('2025-10-25 22:59 GMT');
401
        $this->assertEquals(strtotime('2025-10-25 23:59 Europe/London'), $before);
402
        $midnight = $task->get_next_scheduled_time($before);
403
        $this->assertEquals(strtotime('2025-10-25 23:00 GMT'), $midnight);
404
        $this->assertEquals(strtotime('2025-10-26 00:00 Europe/London'), $midnight);
405
        $midnightb = $task->get_next_scheduled_time($midnight);
406
        $this->assertEquals(strtotime('2025-10-25 23:30 GMT'), $midnightb);
407
        $this->assertEquals(strtotime('2025-10-26 00:30 Europe/London'), $midnightb);
408
        $one = $task->get_next_scheduled_time($midnightb);
409
        // The next 4 times happen during 01:00 Europe/London, but we won't explicitly test that
410
        // because there are two 01:00s so it might fail depending on implementation.
411
        $this->assertEquals(strtotime('2025-10-26 00:00 GMT'), $one);
412
        $oneb = $task->get_next_scheduled_time($one);
413
        $this->assertEquals(strtotime('2025-10-26 00:30 GMT'), $oneb);
414
        // 1am happens again because of the hour change.
415
        $oneagain = $task->get_next_scheduled_time($oneb);
416
        $this->assertEquals(strtotime('2025-10-26 01:00 GMT'), $oneagain);
417
        $oneagainb = $task->get_next_scheduled_time($oneagain);
418
        $this->assertEquals(strtotime('2025-10-26 01:30 GMT'), $oneagainb);
419
        // Times are now unambiguous in Europe/London.
420
        $two = $task->get_next_scheduled_time($oneagainb);
421
        $this->assertEquals(strtotime('2025-10-26 02:00 GMT'), $two);
422
        $this->assertEquals(strtotime('2025-10-26 02:00 Europe/London'), $two);
423
        $twob = $task->get_next_scheduled_time($two);
424
        $this->assertEquals(strtotime('2025-10-26 02:30 Europe/London'), $twob);
425
        $three = $task->get_next_scheduled_time($twob);
426
        $this->assertEquals(strtotime('2025-10-26 03:00 Europe/London'), $three);
427
        $threeb = $task->get_next_scheduled_time($three);
428
        $this->assertEquals(strtotime('2025-10-26 03:30 Europe/London'), $threeb);
429
        $nextday = $task->get_next_scheduled_time($threeb);
430
        $this->assertEquals(strtotime('2025-10-27 00:00 Europe/London'), $nextday);
431
    }
432
 
11 efrain 433
    public function test_timezones(): void {
1 efrain 434
        global $CFG, $USER;
435
 
436
        // The timezones used in this test are chosen because they do not use DST - that would break the test.
437
        $this->resetAfterTest();
438
 
439
        $this->setTimezone('Asia/Kabul');
440
 
441
        $testclass = new scheduled_test_task();
442
 
443
        // Scheduled tasks should always use servertime - so this is 03:30 GMT.
444
        $testclass->set_hour('1');
445
        $testclass->set_minute('0');
446
 
447
        // Next valid time should be 1am of the next day.
448
        $nexttime = $testclass->get_next_scheduled_time();
449
 
450
        // GMT+05:45.
451
        $USER->timezone = 'Asia/Kathmandu';
452
        $userdate = userdate($nexttime);
453
 
454
        // Should be displayed in user timezone.
455
        // I used http://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+Test&iso=20160502T01&p1=113
456
        // setting my location to Kathmandu to verify this time.
457
        $this->assertStringContainsString('2:15 AM', \core_text::strtoupper($userdate));
458
    }
459
 
460
    public function test_reset_scheduled_tasks_for_component_customised(): void {
461
        $this->resetAfterTest(true);
462
 
463
        $tasks = manager::load_scheduled_tasks_for_component('moodle');
464
 
465
        // Customise a task.
466
        $task = reset($tasks);
467
        $task->set_minute('1');
468
        $task->set_hour('2');
469
        $task->set_day('3');
470
        $task->set_month('4');
471
        $task->set_day_of_week('5');
472
        $task->set_customised('1');
473
        manager::configure_scheduled_task($task);
474
 
475
        // Now call reset.
476
        manager::reset_scheduled_tasks_for_component('moodle');
477
 
478
        // Fetch the task again.
479
        $taskafterreset = manager::get_scheduled_task(get_class($task));
480
 
481
        // The task should still be the same as the customised.
482
        $this->assertTaskEquals($task, $taskafterreset);
483
    }
484
 
485
    public function test_reset_scheduled_tasks_for_component_deleted(): void {
486
        global $DB;
487
        $this->resetAfterTest(true);
488
 
489
        // Delete a task to simulate the fact that its new.
490
        $tasklist = manager::load_scheduled_tasks_for_component('moodle');
491
 
492
        // Note: This test must use a task which does not use any random values.
493
        $task = manager::get_scheduled_task(session_cleanup_task::class);
494
 
495
        $DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($task), '\\')));
496
        $this->assertFalse(manager::get_scheduled_task(session_cleanup_task::class));
497
 
498
        // Now call reset on all the tasks.
499
        manager::reset_scheduled_tasks_for_component('moodle');
500
 
501
        // Assert that the second task was added back.
502
        $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
503
        $this->assertNotFalse($taskafterreset);
504
 
505
        $this->assertTaskEquals($task, $taskafterreset);
506
        $this->assertCount(count($tasklist), manager::load_scheduled_tasks_for_component('moodle'));
507
    }
508
 
509
    public function test_reset_scheduled_tasks_for_component_changed_in_source(): void {
510
        $this->resetAfterTest(true);
511
 
512
        // Delete a task to simulate the fact that its new.
513
        // Note: This test must use a task which does not use any random values.
514
        $task = manager::get_scheduled_task(session_cleanup_task::class);
515
 
516
        // Get a copy of the task before maing changes for later comparison.
517
        $taskbeforechange = manager::get_scheduled_task(session_cleanup_task::class);
518
 
519
        // Edit a task to simulate a change in its definition (as if it was not customised).
520
        $task->set_minute('1');
521
        $task->set_hour('2');
522
        $task->set_day('3');
523
        $task->set_month('4');
524
        $task->set_day_of_week('5');
525
        manager::configure_scheduled_task($task);
526
 
527
        // Fetch the task out for comparison.
528
        $taskafterchange = manager::get_scheduled_task(session_cleanup_task::class);
529
 
530
        // The task should now be different to the original.
531
        $this->assertTaskNotEquals($taskbeforechange, $taskafterchange);
532
 
533
        // Now call reset.
534
        manager::reset_scheduled_tasks_for_component('moodle');
535
 
536
        // Fetch the task again.
537
        $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
538
 
539
        // The task should now be the same as the original.
540
        $this->assertTaskEquals($taskbeforechange, $taskafterreset);
541
    }
542
 
543
    /**
544
     * Tests that the reset function deletes old tasks.
545
     */
11 efrain 546
    public function test_reset_scheduled_tasks_for_component_delete(): void {
1 efrain 547
        global $DB;
548
        $this->resetAfterTest(true);
549
 
550
        $count = $DB->count_records('task_scheduled', array('component' => 'moodle'));
551
        $allcount = $DB->count_records('task_scheduled');
552
 
553
        $task = new scheduled_test_task();
554
        $task->set_component('moodle');
555
        $record = manager::record_from_scheduled_task($task);
556
        $DB->insert_record('task_scheduled', $record);
557
        $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
558
            'component' => 'moodle')));
559
 
560
        $task = new scheduled_test2_task();
561
        $task->set_component('moodle');
562
        $record = manager::record_from_scheduled_task($task);
563
        $DB->insert_record('task_scheduled', $record);
564
        $this->assertTrue($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
565
            'component' => 'moodle')));
566
 
567
        $aftercount = $DB->count_records('task_scheduled', array('component' => 'moodle'));
568
        $afterallcount = $DB->count_records('task_scheduled');
569
 
570
        $this->assertEquals($count + 2, $aftercount);
571
        $this->assertEquals($allcount + 2, $afterallcount);
572
 
573
        // Now check that the right things were deleted.
574
        manager::reset_scheduled_tasks_for_component('moodle');
575
 
576
        $this->assertEquals($count, $DB->count_records('task_scheduled', array('component' => 'moodle')));
577
        $this->assertEquals($allcount, $DB->count_records('task_scheduled'));
578
        $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test2_task',
579
            'component' => 'moodle')));
580
        $this->assertFalse($DB->record_exists('task_scheduled', array('classname' => '\core\task\scheduled_test_task',
581
            'component' => 'moodle')));
582
    }
583
 
11 efrain 584
    public function test_get_next_scheduled_task(): void {
1 efrain 585
        global $DB;
586
 
587
        $this->resetAfterTest(true);
588
        // Delete all existing scheduled tasks.
589
        $DB->delete_records('task_scheduled');
590
        // Add a scheduled task.
591
 
592
        // A task that runs once per hour.
593
        $record = new \stdClass();
594
        $record->blocking = true;
595
        $record->minute = '0';
596
        $record->hour = '0';
597
        $record->dayofweek = '*';
598
        $record->day = '*';
599
        $record->month = '*';
600
        $record->component = 'test_scheduled_task';
601
        $record->classname = '\core\task\scheduled_test_task';
602
 
603
        $DB->insert_record('task_scheduled', $record);
604
        // And another one to test failures.
605
        $record->classname = '\core\task\scheduled_test2_task';
606
        $DB->insert_record('task_scheduled', $record);
607
        // And disabled test.
608
        $record->classname = '\core\task\scheduled_test3_task';
609
        $record->disabled = 1;
610
        $DB->insert_record('task_scheduled', $record);
611
 
612
        $now = time();
613
 
614
        // Should get handed the first task.
615
        $task = manager::get_next_scheduled_task($now);
616
        $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
617
        $task->execute();
618
 
619
        manager::scheduled_task_complete($task);
620
        // Should get handed the second task.
621
        $task = manager::get_next_scheduled_task($now);
622
        $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
623
        $task->execute();
624
 
625
        manager::scheduled_task_failed($task);
626
        // Should not get any task.
627
        $task = manager::get_next_scheduled_task($now);
628
        $this->assertNull($task);
629
 
630
        // Should get the second task (retry after delay).
631
        $task = manager::get_next_scheduled_task($now + 120);
632
        $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
633
        $task->execute();
634
 
635
        manager::scheduled_task_complete($task);
636
 
637
        // Should not get any task.
638
        $task = manager::get_next_scheduled_task($now);
639
        $this->assertNull($task);
640
 
641
        // Check ordering.
642
        $DB->delete_records('task_scheduled');
643
        $record->lastruntime = 2;
644
        $record->disabled = 0;
645
        $record->classname = '\core\task\scheduled_test_task';
646
        $DB->insert_record('task_scheduled', $record);
647
 
648
        $record->lastruntime = 1;
649
        $record->classname = '\core\task\scheduled_test2_task';
650
        $DB->insert_record('task_scheduled', $record);
651
 
652
        // Should get handed the second task.
653
        $task = manager::get_next_scheduled_task($now);
654
        $this->assertInstanceOf('\core\task\scheduled_test2_task', $task);
655
        $task->execute();
656
        manager::scheduled_task_complete($task);
657
 
658
        // Should get handed the first task.
659
        $task = manager::get_next_scheduled_task($now);
660
        $this->assertInstanceOf('\core\task\scheduled_test_task', $task);
661
        $task->execute();
662
        manager::scheduled_task_complete($task);
663
 
664
        // Should not get any task.
665
        $task = manager::get_next_scheduled_task($now);
666
        $this->assertNull($task);
667
    }
668
 
11 efrain 669
    public function test_get_broken_scheduled_task(): void {
1 efrain 670
        global $DB;
671
 
672
        $this->resetAfterTest(true);
673
        // Delete all existing scheduled tasks.
674
        $DB->delete_records('task_scheduled');
675
        // Add a scheduled task.
676
 
677
        // A broken task that runs all the time.
678
        $record = new \stdClass();
679
        $record->blocking = true;
680
        $record->minute = '*';
681
        $record->hour = '*';
682
        $record->dayofweek = '*';
683
        $record->day = '*';
684
        $record->month = '*';
685
        $record->component = 'test_scheduled_task';
686
        $record->classname = '\core\task\scheduled_test_task_broken';
687
 
688
        $DB->insert_record('task_scheduled', $record);
689
 
690
        $now = time();
691
        // Should not get any task.
692
        $task = manager::get_next_scheduled_task($now);
693
        $this->assertDebuggingCalled();
694
        $this->assertNull($task);
695
    }
696
 
697
    /**
698
     * Tests the use of 'R' syntax in time fields of tasks to get
699
     * tasks be configured with a non-uniform time.
700
     */
11 efrain 701
    public function test_random_time_specification(): void {
1 efrain 702
 
703
        // Testing non-deterministic things in a unit test is not really
704
        // wise, so we just test the values have changed within allowed bounds.
705
        $testclass = new scheduled_test_task();
706
 
707
        // The test task defaults to '*'.
708
        $this->assertIsString($testclass->get_minute());
709
        $this->assertIsString($testclass->get_hour());
710
 
711
        // Set a random value.
712
        $testclass->set_minute('R');
713
        $testclass->set_hour('R');
714
        $testclass->set_day_of_week('R');
715
 
716
        // Verify the minute has changed within allowed bounds.
717
        $minute = $testclass->get_minute();
718
        $this->assertIsInt($minute);
719
        $this->assertGreaterThanOrEqual(0, $minute);
720
        $this->assertLessThanOrEqual(59, $minute);
721
 
722
        // Verify the hour has changed within allowed bounds.
723
        $hour = $testclass->get_hour();
724
        $this->assertIsInt($hour);
725
        $this->assertGreaterThanOrEqual(0, $hour);
726
        $this->assertLessThanOrEqual(23, $hour);
727
 
728
        // Verify the dayofweek has changed within allowed bounds.
729
        $dayofweek = $testclass->get_day_of_week();
730
        $this->assertIsInt($dayofweek);
731
        $this->assertGreaterThanOrEqual(0, $dayofweek);
732
        $this->assertLessThanOrEqual(6, $dayofweek);
733
    }
734
 
735
    /**
736
     * Test that the file_temp_cleanup_task removes directories and
737
     * files as expected.
738
     */
11 efrain 739
    public function test_file_temp_cleanup_task(): void {
1 efrain 740
        global $CFG;
741
        $backuptempdir = make_backup_temp_directory('');
742
 
743
        // Create directories.
744
        $dir = $backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses';
745
        mkdir($dir, 0777, true);
746
 
747
        // Create files to be checked and then deleted.
748
        $file01 = $dir . DIRECTORY_SEPARATOR . 'sections.xml';
749
        file_put_contents($file01, 'test data 001');
750
        $file02 = $dir . DIRECTORY_SEPARATOR . 'modules.xml';
751
        file_put_contents($file02, 'test data 002');
752
        // Change the time modified for the first file, to a time that will be deleted by the task (greater than seven days).
753
        touch($file01, time() - (8 * 24 * 3600));
754
 
755
        $task = manager::get_scheduled_task('\\core\\task\\file_temp_cleanup_task');
756
        $this->assertInstanceOf('\core\task\file_temp_cleanup_task', $task);
757
        $task->execute();
758
 
759
        // Scan the directory. Only modules.xml should be left.
760
        $filesarray = scandir($dir);
761
        $this->assertEquals('modules.xml', $filesarray[2]);
762
        $this->assertEquals(3, count($filesarray));
763
 
764
        // Change the time modified on modules.xml.
765
        touch($file02, time() - (8 * 24 * 3600));
766
        // Change the time modified on the courses directory.
767
        touch($backuptempdir . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR .
768
                'courses', time() - (8 * 24 * 3600));
769
        // Run the scheduled task to remove the file and directory.
770
        $task->execute();
771
        $filesarray = scandir($backuptempdir . DIRECTORY_SEPARATOR . 'backup01');
772
        // There should only be two items in the array, '.' and '..'.
773
        $this->assertEquals(2, count($filesarray));
774
 
775
        // Change the time modified on all of the files and directories.
776
        $dir = new \RecursiveDirectoryIterator($CFG->tempdir);
777
        // Show all child nodes prior to their parent.
778
        $iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST);
779
 
780
        for ($iter->rewind(); $iter->valid(); $iter->next()) {
781
            if ($iter->isDir() && !$iter->isDot()) {
782
                $node = $iter->getRealPath();
783
                touch($node, time() - (8 * 24 * 3600));
784
            }
785
        }
786
 
787
        // Run the scheduled task again to remove all of the files and directories.
788
        $task->execute();
789
        $filesarray = scandir($CFG->tempdir);
790
        // All of the files and directories should be deleted.
791
        // There should only be three items in the array, '.', '..' and '.htaccess'.
792
        $this->assertEquals([ '.', '..', '.htaccess' ], $filesarray);
793
    }
794
 
795
    /**
796
     * Test that the function to clear the fail delay from a task works correctly.
797
     */
11 efrain 798
    public function test_clear_fail_delay(): void {
1 efrain 799
 
800
        $this->resetAfterTest();
801
 
802
        // Get an example task to use for testing. Task is set to run every minute by default.
803
        $taskname = '\core\task\send_new_user_passwords_task';
804
 
805
        // Pretend task started running and then failed 3 times.
806
        $before = time();
807
        $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
808
        for ($i = 0; $i < 3; $i ++) {
809
            $task = manager::get_scheduled_task($taskname);
810
            $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
811
            $task->set_lock($lock);
812
            manager::scheduled_task_failed($task);
813
        }
814
 
815
        // Confirm task is now delayed by several minutes.
816
        $task = manager::get_scheduled_task($taskname);
817
        $this->assertEquals(240, $task->get_fail_delay());
818
        $this->assertGreaterThan($before + 230, $task->get_next_run_time());
819
 
820
        // Clear the fail delay and re-get the task.
821
        manager::clear_fail_delay($task);
822
        $task = manager::get_scheduled_task($taskname);
823
 
824
        // There should be no delay and it should run within the next minute.
825
        $this->assertEquals(0, $task->get_fail_delay());
826
        $this->assertLessThan($before + 70, $task->get_next_run_time());
827
    }
828
 
829
    /**
830
     * Data provider for test_scheduled_task_override_values.
831
     */
832
    public static function provider_schedule_overrides(): array {
833
        return array(
834
            array(
1441 ariadna 835
                'overrides' => array(
1 efrain 836
                    '\core\task\scheduled_test_task' => array(
837
                        'schedule' => '10 13 1 2 4',
838
                        'disabled' => 0,
839
                    ),
840
                    '\core\task\scheduled_test2_task' => array(
841
                        'schedule' => '* * * * *',
842
                        'disabled' => 1,
843
                    ),
844
                ),
1441 ariadna 845
                'tasks' => array(
1 efrain 846
                    '\core\task\scheduled_test_task',
847
                    '\core\task\scheduled_test2_task',
848
                ),
849
                'expected' => array(
850
                    '\core\task\scheduled_test_task' => array(
851
                        'min'   => '10',
852
                        'hour'  => '13',
853
                        'day'   => '1',
854
                        'month' => '2',
855
                        'week'  => '4',
856
                        'disabled' => 0,
857
                    ),
858
                    '\core\task\scheduled_test2_task' => array(
859
                        'min'   => '*',
860
                        'hour'  => '*',
861
                        'day'   => '*',
862
                        'month' => '*',
863
                        'week'  => '*',
864
                        'disabled' => 1,
865
                    ),
866
                )
867
            ),
868
            array(
1441 ariadna 869
                'overrides' => array(
1 efrain 870
                    '\core\task\*' => array(
871
                        'schedule' => '1 2 3 4 5',
872
                        'disabled' => 0,
873
                    )
874
                ),
1441 ariadna 875
                'tasks' => array(
1 efrain 876
                    '\core\task\scheduled_test_task',
877
                    '\core\task\scheduled_test2_task',
878
                ),
879
                'expected' => array(
880
                    '\core\task\scheduled_test_task' => array(
881
                        'min'   => '1',
882
                        'hour'  => '2',
883
                        'day'   => '3',
884
                        'month' => '4',
885
                        'week'  => '5',
886
                        'disabled' => 0,
887
                    ),
888
                    '\core\task\scheduled_test2_task' => array(
889
                        'min'   => '1',
890
                        'hour'  => '2',
891
                        'day'   => '3',
892
                        'month' => '4',
893
                        'week'  => '5',
894
                        'disabled' => 0,
895
                    ),
896
                )
897
            )
898
        );
899
    }
900
 
901
 
902
    /**
903
     * Test to ensure scheduled tasks are updated by values set in config.
904
     *
905
     * @param array $overrides
906
     * @param array $tasks
907
     * @param array $expected
908
     * @dataProvider provider_schedule_overrides
909
     */
910
    public function test_scheduled_task_override_values(array $overrides, array $tasks, array $expected): void {
911
        global $CFG, $DB;
912
 
913
        $this->resetAfterTest();
914
 
915
        // Add overrides to the config.
916
        $CFG->scheduled_tasks = $overrides;
917
 
918
        // Set up test scheduled task record.
919
        $record = new \stdClass();
920
        $record->component = 'test_scheduled_task';
921
 
922
        foreach ($tasks as $task) {
923
            $record->classname = $task;
924
            $DB->insert_record('task_scheduled', $record);
925
 
926
            $scheduledtask = manager::get_scheduled_task($task);
927
            $expectedresults = $expected[$task];
928
 
929
            // Check that the task is actually overridden.
930
            $this->assertTrue($scheduledtask->is_overridden(), 'Is overridden');
931
 
932
            // Check minute is correct.
933
            $this->assertEquals($expectedresults['min'], $scheduledtask->get_minute(), 'Minute check');
934
 
935
            // Check day is correct.
936
            $this->assertEquals($expectedresults['day'], $scheduledtask->get_day(), 'Day check');
937
 
938
            // Check hour is correct.
939
            $this->assertEquals($expectedresults['hour'], $scheduledtask->get_hour(), 'Hour check');
940
 
941
            // Check week is correct.
942
            $this->assertEquals($expectedresults['week'], $scheduledtask->get_day_of_week(), 'Day of week check');
943
 
944
            // Check week is correct.
945
            $this->assertEquals($expectedresults['month'], $scheduledtask->get_month(), 'Month check');
946
 
947
            // Check to see if the task is disabled.
948
            $this->assertEquals($expectedresults['disabled'], $scheduledtask->get_disabled(), 'Disabled check');
949
        }
950
    }
951
 
952
    /**
953
     * Check that an overridden task is sent to be processed.
954
     */
955
    public function test_scheduled_task_overridden_task_can_run(): void {
956
        global $CFG, $DB;
957
 
958
        $this->resetAfterTest();
959
 
960
        // Delete all existing scheduled tasks.
961
        $DB->delete_records('task_scheduled');
962
 
963
        // Add overrides to the config.
964
        $CFG->scheduled_tasks = [
965
            '\core\task\scheduled_test_task' => [
966
                'disabled' => 1
967
            ],
968
            '\core\task\scheduled_test2_task' => [
969
                'disabled' => 0
970
            ],
971
        ];
972
 
973
        // A task that runs once per hour.
974
        $record = new \stdClass();
975
        $record->component = 'test_scheduled_task';
976
        $record->classname = '\core\task\scheduled_test_task';
977
        $record->disabled = 0;
978
        $DB->insert_record('task_scheduled', $record);
979
 
980
        // And disabled test.
981
        $record->classname = '\core\task\scheduled_test2_task';
982
        $record->disabled = 1;
983
        $DB->insert_record('task_scheduled', $record);
984
 
985
        $now = time();
986
 
987
        $scheduledtask = manager::get_next_scheduled_task($now);
988
        $this->assertInstanceOf('\core\task\scheduled_test2_task', $scheduledtask);
989
        $scheduledtask->execute();
990
        manager::scheduled_task_complete($scheduledtask);
991
    }
992
 
993
    /**
994
     * Assert that the specified tasks are equal.
995
     *
996
     * @param   \core\task\task_base $task
997
     * @param   \core\task\task_base $comparisontask
998
     */
999
    public function assertTaskEquals(task_base $task, task_base $comparisontask): void {
1000
        // Convert both to an object.
1001
        $task = manager::record_from_scheduled_task($task);
1002
        $comparisontask = manager::record_from_scheduled_task($comparisontask);
1003
 
1004
        // Reset the nextruntime field as it is intentionally dynamic.
1005
        $task->nextruntime = null;
1006
        $comparisontask->nextruntime = null;
1007
 
1008
        $args = array_merge(
1009
            [
1010
                $task,
1011
                $comparisontask,
1012
            ],
1013
            array_slice(func_get_args(), 2)
1014
        );
1015
 
1016
        call_user_func_array([$this, 'assertEquals'], $args);
1017
    }
1018
 
1019
    /**
1020
     * Assert that the specified tasks are not equal.
1021
     *
1022
     * @param   \core\task\task_base $task
1023
     * @param   \core\task\task_base $comparisontask
1024
     */
1025
    public function assertTaskNotEquals(task_base $task, task_base $comparisontask): void {
1026
        // Convert both to an object.
1027
        $task = manager::record_from_scheduled_task($task);
1028
        $comparisontask = manager::record_from_scheduled_task($comparisontask);
1029
 
1030
        // Reset the nextruntime field as it is intentionally dynamic.
1031
        $task->nextruntime = null;
1032
        $comparisontask->nextruntime = null;
1033
 
1034
        $args = array_merge(
1035
            [
1036
                $task,
1037
                $comparisontask,
1038
            ],
1039
            array_slice(func_get_args(), 2)
1040
        );
1041
 
1042
        call_user_func_array([$this, 'assertNotEquals'], $args);
1043
    }
1044
 
1045
    /**
1046
     * Assert that the lastruntime column holds an original value after a scheduled task is reset.
1047
     */
1048
    public function test_reset_scheduled_tasks_for_component_keeps_original_lastruntime(): void {
1049
        global $DB;
1050
        $this->resetAfterTest(true);
1051
 
1052
        // Set lastruntime for the scheduled task.
1053
        $DB->set_field('task_scheduled', 'lastruntime', 123456789, ['classname' => '\core\task\session_cleanup_task']);
1054
 
1055
        // Reset the task.
1056
        manager::reset_scheduled_tasks_for_component('moodle');
1057
 
1058
        // Fetch the task again.
1059
        $taskafterreset = manager::get_scheduled_task(session_cleanup_task::class);
1060
 
1061
        // Confirm, that lastruntime is still in place.
1062
        $this->assertEquals(123456789, $taskafterreset->get_last_run_time());
1063
    }
1064
 
1065
    /**
1066
     * Data provider for {@see test_is_component_enabled}
1067
     *
1068
     * @return array[]
1069
     */
1441 ariadna 1070
    public static function is_component_enabled_provider(): array {
1 efrain 1071
        return [
1441 ariadna 1072
            'Enabled component' => ['auth_email', true],
1 efrain 1073
            'Disabled component' => ['auth_ldap', false],
1074
            'Invalid component' => ['auth_invalid', false],
1075
        ];
1076
    }
1077
 
1078
    /**
1079
     * Tests whether tasks belonging to components consider the component to be enabled
1080
     *
1081
     * @param string $component
1082
     * @param bool $expected
1083
     *
1084
     * @dataProvider is_component_enabled_provider
1085
     */
1086
    public function test_is_component_enabled(string $component, bool $expected): void {
1087
        $this->resetAfterTest();
1088
 
1089
        // Set cas as the only enabled auth component.
1441 ariadna 1090
        set_config('auth', 'email');
1 efrain 1091
 
1092
        $task = new scheduled_test_task();
1093
        $task->set_component($component);
1094
 
1095
        $this->assertEquals($expected, $task->is_component_enabled());
1096
    }
1097
 
1098
    /**
1099
     * Test whether tasks belonging to core components considers the component to be enabled
1100
     */
1101
    public function test_is_component_enabled_core(): void {
1102
        $task = new scheduled_test_task();
1103
        $this->assertTrue($task->is_component_enabled());
1104
    }
1105
 
1106
    /**
1107
     * Test disabling and enabling individual tasks.
1108
     */
1109
    public function test_disable_and_enable_task(): void {
1110
        $this->resetAfterTest();
1111
 
1112
        // We use a real task because the manager doesn't know about the test tasks.
1113
        $taskname = '\core\task\send_new_user_passwords_task';
1114
 
1115
        $task = manager::get_scheduled_task($taskname);
1116
        $defaulttask = manager::get_default_scheduled_task($taskname);
1117
        $this->assertTaskEquals($task, $defaulttask);
1118
 
1119
        // Disable task and verify drift.
1120
        $task->disable();
1121
        $this->assertTaskNotEquals($task, $defaulttask);
1122
        $this->assertEquals(1, $task->get_disabled());
1123
        $this->assertEquals(false, $task->has_default_configuration());
1124
 
1125
        // Enable task and verify not drifted.
1126
        $task->enable();
1127
        $this->assertTaskEquals($task, $defaulttask);
1128
        $this->assertEquals(0, $task->get_disabled());
1129
        $this->assertEquals(true, $task->has_default_configuration());
1130
 
1131
        // Modify task and verify drift.
1132
        $task->set_hour(1);
1133
        \core\task\manager::configure_scheduled_task($task);
1134
        $this->assertTaskNotEquals($task, $defaulttask);
1135
        $this->assertEquals(1, $task->get_hour());
1136
        $this->assertEquals(false, $task->has_default_configuration());
1137
 
1138
        // Disable task and verify drift.
1139
        $task->disable();
1140
        $this->assertTaskNotEquals($task, $defaulttask);
1141
        $this->assertEquals(1, $task->get_disabled());
1142
        $this->assertEquals(1, $task->get_hour());
1143
        $this->assertEquals(false, $task->has_default_configuration());
1144
 
1145
        // Enable task and verify drift.
1146
        $task->enable();
1147
        $this->assertTaskNotEquals($task, $defaulttask);
1148
        $this->assertEquals(0, $task->get_disabled());
1149
        $this->assertEquals(1, $task->get_hour());
1150
        $this->assertEquals(false, $task->has_default_configuration());
1151
    }
1152
 
1153
    /**
1154
     * Test send messages when a task reaches the max fail delay time.
1155
     */
1156
    public function test_message_max_fail_delay(): void {
1157
        $this->resetAfterTest();
1158
        $this->setAdminUser();
1159
 
1160
        // Redirect messages.
1161
        $messagesink = $this->redirectMessages();
1162
        $cronlockfactory = \core\lock\lock_config::get_lock_factory('cron');
1163
 
1164
        // Get an example task to use for testing. Task is set to run every minute by default.
1165
        $taskname = '\core\task\send_new_user_passwords_task';
1166
        $task = manager::get_scheduled_task($taskname);
1167
        $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
1168
        $task->set_lock($lock);
1169
        // Catch the message.
1170
        manager::scheduled_task_failed($task);
1171
        $messages = $messagesink->get_messages();
1172
        $this->assertCount(0, $messages);
1173
 
1174
        // Set the max fail delay time.
1175
        $task = manager::get_scheduled_task($taskname);
1176
        $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
1177
        $task->set_lock($lock);
1178
        $task->set_fail_delay(86400);
1179
        $task->execute();
1180
        // Catch the message.
1181
        manager::scheduled_task_failed($task);
1182
        $messages = $messagesink->get_messages();
1183
        $this->assertCount(1, $messages);
1184
 
1185
        // Get the task and execute it second time.
1186
        $task = manager::get_scheduled_task($taskname);
1187
        $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
1188
        $task->set_lock($lock);
1189
        // Set the fail delay to 12 hours.
1190
        $task->set_fail_delay(43200);
1191
        $task->execute();
1192
        manager::scheduled_task_failed($task);
1193
        // Catch the message.
1194
        $messages = $messagesink->get_messages();
1195
        $this->assertCount(2, $messages);
1196
 
1197
        // Get the task and execute it third time.
1198
        $task = manager::get_scheduled_task($taskname);
1199
        $lock = $cronlockfactory->get_lock('\\' . get_class($task), 10);
1200
        $task->set_lock($lock);
1201
        // Set the fail delay to 48 hours.
1202
        $task->set_fail_delay(172800);
1203
        $task->execute();
1204
        manager::scheduled_task_failed($task);
1205
        // Catch the message.
1206
        $messages = $messagesink->get_messages();
1207
        $this->assertCount(3, $messages);
1208
 
1209
        // Check first message information.
1210
        $this->assertStringContainsString('Task failed: Send new user passwords', $messages[0]->subject);
1211
        $this->assertEquals('failedtaskmaxdelay', $messages[0]->eventtype);
1212
        $this->assertEquals('-10', $messages[0]->useridfrom);
1213
        $this->assertEquals('2', $messages[0]->useridto);
1214
 
1215
        // Close sink.
1216
        $messagesink->close();
1217
    }
1218
}