Commit | Line | Data |
---|---|---|
309ae892 DW |
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 | /** | |
0a5aa65b | 18 | * This file contains the unittests for scheduled tasks. |
309ae892 DW |
19 | * |
20 | * @package core | |
21 | * @category phpunit | |
22 | * @copyright 2013 Damyon Wiese | |
23 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
24 | */ | |
25 | ||
26 | defined('MOODLE_INTERNAL') || die(); | |
0a5aa65b | 27 | require_once(__DIR__ . '/fixtures/task_fixtures.php'); |
309ae892 DW |
28 | |
29 | /** | |
30 | * Test class for scheduled task. | |
31 | * | |
32 | * @package core | |
33 | * @category task | |
34 | * @copyright 2013 Damyon Wiese | |
35 | * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later | |
36 | */ | |
0a5aa65b | 37 | class core_scheduled_task_testcase extends advanced_testcase { |
309ae892 DW |
38 | |
39 | /** | |
40 | * Test the cron scheduling method | |
41 | */ | |
42 | public function test_eval_cron_field() { | |
0a5aa65b | 43 | $testclass = new \core\task\scheduled_test_task(); |
309ae892 DW |
44 | |
45 | $this->assertEquals(20, count($testclass->eval_cron_field('*/3', 0, 59))); | |
46 | $this->assertEquals(31, count($testclass->eval_cron_field('1,*/2', 0, 59))); | |
47 | $this->assertEquals(15, count($testclass->eval_cron_field('1-10,5-15', 0, 59))); | |
48 | $this->assertEquals(13, count($testclass->eval_cron_field('1-10,5-15/2', 0, 59))); | |
49 | $this->assertEquals(3, count($testclass->eval_cron_field('1,2,3,1,2,3', 0, 59))); | |
50 | $this->assertEquals(1, count($testclass->eval_cron_field('-1,10,80', 0, 59))); | |
51 | } | |
52 | ||
53 | public function test_get_next_scheduled_time() { | |
d6e7a63d PS |
54 | global $CFG; |
55 | $this->resetAfterTest(); | |
56 | ||
57 | $this->setTimezone('Europe/London'); | |
58 | ||
309ae892 | 59 | // Test job run at 1 am. |
0a5aa65b | 60 | $testclass = new \core\task\scheduled_test_task(); |
309ae892 DW |
61 | |
62 | // All fields default to '*'. | |
63 | $testclass->set_hour('1'); | |
64 | $testclass->set_minute('0'); | |
65 | // Next valid time should be 1am of the next day. | |
66 | $nexttime = $testclass->get_next_scheduled_time(); | |
67 | ||
d6e7a63d PS |
68 | $oneamdate = new DateTime('now', new DateTimeZone('Europe/London')); |
69 | $oneamdate->setTime(1, 0, 0); | |
309ae892 | 70 | // Make it 1 am tomorrow if the time is after 1am. |
d6e7a63d PS |
71 | if ($oneamdate->getTimestamp() < time()) { |
72 | $oneamdate->add(new DateInterval('P1D')); | |
309ae892 | 73 | } |
d6e7a63d | 74 | $oneam = $oneamdate->getTimestamp(); |
309ae892 DW |
75 | |
76 | $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.'); | |
77 | ||
0a5aa65b PŠ |
78 | // Disabled flag does not affect next time. |
79 | $testclass->set_disabled(true); | |
80 | $nexttime = $testclass->get_next_scheduled_time(); | |
81 | $this->assertEquals($oneam, $nexttime, 'Next scheduled time is 1am.'); | |
82 | ||
309ae892 | 83 | // Now test for job run every 10 minutes. |
0a5aa65b | 84 | $testclass = new \core\task\scheduled_test_task(); |
309ae892 DW |
85 | |
86 | // All fields default to '*'. | |
87 | $testclass->set_minute('*/10'); | |
88 | // Next valid time should be next 10 minute boundary. | |
89 | $nexttime = $testclass->get_next_scheduled_time(); | |
90 | ||
91 | $minutes = ((intval(date('i') / 10))+1) * 10; | |
92 | $nexttenminutes = mktime(date('H'), $minutes, 0); | |
93 | ||
94 | $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.'); | |
0a5aa65b PŠ |
95 | |
96 | // Disabled flag does not affect next time. | |
97 | $testclass->set_disabled(true); | |
98 | $nexttime = $testclass->get_next_scheduled_time(); | |
99 | $this->assertEquals($nexttenminutes, $nexttime, 'Next scheduled time is in 10 minutes.'); | |
dc561732 DM |
100 | |
101 | // Test hourly job executed on Sundays only. | |
102 | $testclass = new \core\task\scheduled_test_task(); | |
103 | $testclass->set_minute('0'); | |
104 | $testclass->set_day_of_week('7'); | |
105 | ||
106 | $nexttime = $testclass->get_next_scheduled_time(); | |
107 | ||
108 | $this->assertEquals(7, date('N', $nexttime)); | |
109 | $this->assertEquals(0, date('i', $nexttime)); | |
110 | ||
111 | // Test monthly job | |
112 | $testclass = new \core\task\scheduled_test_task(); | |
113 | $testclass->set_minute('32'); | |
114 | $testclass->set_hour('0'); | |
115 | $testclass->set_day('1'); | |
116 | ||
117 | $nexttime = $testclass->get_next_scheduled_time(); | |
118 | ||
119 | $this->assertEquals(32, date('i', $nexttime)); | |
120 | $this->assertEquals(0, date('G', $nexttime)); | |
121 | $this->assertEquals(1, date('j', $nexttime)); | |
309ae892 DW |
122 | } |
123 | ||
bbd9226c DW |
124 | public function test_timezones() { |
125 | global $CFG, $USER; | |
126 | ||
127 | // The timezones used in this test are chosen because they do not use DST - that would break the test. | |
d6e7a63d | 128 | $this->resetAfterTest(); |
bbd9226c | 129 | |
d6e7a63d | 130 | $this->setTimezone('America/Caracas'); |
bbd9226c | 131 | |
0a5aa65b | 132 | $testclass = new \core\task\scheduled_test_task(); |
bbd9226c DW |
133 | |
134 | // Scheduled tasks should always use servertime - so this is 03:30 GMT. | |
135 | $testclass->set_hour('1'); | |
136 | $testclass->set_minute('0'); | |
137 | ||
138 | // Next valid time should be 1am of the next day. | |
139 | $nexttime = $testclass->get_next_scheduled_time(); | |
140 | ||
141 | // GMT+05:45. | |
142 | $USER->timezone = 'Asia/Kathmandu'; | |
143 | $userdate = userdate($nexttime); | |
144 | ||
145 | // Should be displayed in user timezone. | |
146 | // I used http://www.timeanddate.com/worldclock/fixedtime.html?msg=Moodle+Test&iso=20140314T01&p1=58 | |
147 | // to verify this time. | |
9be8583a | 148 | $this->assertContains('11:15 AM', core_text::strtoupper($userdate)); |
bbd9226c DW |
149 | } |
150 | ||
852ff037 DW |
151 | public function test_reset_scheduled_tasks_for_component() { |
152 | global $DB; | |
153 | ||
154 | $this->resetAfterTest(true); | |
155 | // Remember the defaults. | |
156 | $defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle'); | |
157 | $initcount = count($defaulttasks); | |
158 | // Customise a task. | |
159 | $firsttask = reset($defaulttasks); | |
160 | $firsttask->set_minute('1'); | |
161 | $firsttask->set_hour('2'); | |
162 | $firsttask->set_month('3'); | |
163 | $firsttask->set_day_of_week('4'); | |
164 | $firsttask->set_day('5'); | |
165 | $firsttask->set_customised('1'); | |
166 | \core\task\manager::configure_scheduled_task($firsttask); | |
167 | $firsttaskrecord = \core\task\manager::record_from_scheduled_task($firsttask); | |
168 | // We reset this field, because we do not want to compare it. | |
169 | $firsttaskrecord->nextruntime = '0'; | |
170 | ||
0af336ef FM |
171 | // Delete a task to simulate the fact that its new. |
172 | $secondtask = next($defaulttasks); | |
173 | $DB->delete_records('task_scheduled', array('classname' => '\\' . trim(get_class($secondtask), '\\'))); | |
174 | $this->assertFalse(\core\task\manager::get_scheduled_task(get_class($secondtask))); | |
175 | ||
176 | // Edit a task to simulate a change in its definition (as if it was not customised). | |
177 | $thirdtask = next($defaulttasks); | |
178 | $thirdtask->set_minute('1'); | |
179 | $thirdtask->set_hour('2'); | |
180 | $thirdtask->set_month('3'); | |
181 | $thirdtask->set_day_of_week('4'); | |
182 | $thirdtask->set_day('5'); | |
183 | $thirdtaskbefore = \core\task\manager::get_scheduled_task(get_class($thirdtask)); | |
184 | $thirdtaskbefore->set_next_run_time(null); // Ignore this value when comparing. | |
185 | \core\task\manager::configure_scheduled_task($thirdtask); | |
186 | $thirdtask = \core\task\manager::get_scheduled_task(get_class($thirdtask)); | |
187 | $thirdtask->set_next_run_time(null); // Ignore this value when comparing. | |
188 | $this->assertNotEquals($thirdtaskbefore, $thirdtask); | |
189 | ||
852ff037 DW |
190 | // Now call reset on all the tasks. |
191 | \core\task\manager::reset_scheduled_tasks_for_component('moodle'); | |
192 | ||
193 | // Load the tasks again. | |
194 | $defaulttasks = \core\task\manager::load_scheduled_tasks_for_component('moodle'); | |
195 | $finalcount = count($defaulttasks); | |
196 | // Compare the first task. | |
197 | $newfirsttask = reset($defaulttasks); | |
198 | $newfirsttaskrecord = \core\task\manager::record_from_scheduled_task($newfirsttask); | |
199 | // We reset this field, because we do not want to compare it. | |
200 | $newfirsttaskrecord->nextruntime = '0'; | |
201 | ||
202 | // Assert a customised task was not altered by reset. | |
203 | $this->assertEquals($firsttaskrecord, $newfirsttaskrecord); | |
204 | ||
0af336ef FM |
205 | // Assert that the second task was added back. |
206 | $secondtaskafter = \core\task\manager::get_scheduled_task(get_class($secondtask)); | |
207 | $secondtaskafter->set_next_run_time(null); // Do not compare the nextruntime. | |
208 | $secondtask->set_next_run_time(null); | |
209 | $this->assertEquals($secondtask, $secondtaskafter); | |
210 | ||
211 | // Assert that the third task edits were overridden. | |
212 | $thirdtaskafter = \core\task\manager::get_scheduled_task(get_class($thirdtask)); | |
213 | $thirdtaskafter->set_next_run_time(null); | |
214 | $this->assertEquals($thirdtaskbefore, $thirdtaskafter); | |
215 | ||
852ff037 DW |
216 | // Assert we have the same number of tasks. |
217 | $this->assertEquals($initcount, $finalcount); | |
218 | } | |
219 | ||
309ae892 DW |
220 | public function test_get_next_scheduled_task() { |
221 | global $DB; | |
222 | ||
223 | $this->resetAfterTest(true); | |
224 | // Delete all existing scheduled tasks. | |
225 | $DB->delete_records('task_scheduled'); | |
226 | // Add a scheduled task. | |
227 | ||
228 | // A task that runs once per hour. | |
229 | $record = new stdClass(); | |
230 | $record->blocking = true; | |
231 | $record->minute = '0'; | |
232 | $record->hour = '0'; | |
233 | $record->dayofweek = '*'; | |
234 | $record->day = '*'; | |
235 | $record->month = '*'; | |
236 | $record->component = 'test_scheduled_task'; | |
0a5aa65b | 237 | $record->classname = '\core\task\scheduled_test_task'; |
309ae892 DW |
238 | |
239 | $DB->insert_record('task_scheduled', $record); | |
240 | // And another one to test failures. | |
0a5aa65b PŠ |
241 | $record->classname = '\core\task\scheduled_test2_task'; |
242 | $DB->insert_record('task_scheduled', $record); | |
243 | // And disabled test. | |
244 | $record->classname = '\core\task\scheduled_test3_task'; | |
245 | $record->disabled = 1; | |
309ae892 | 246 | $DB->insert_record('task_scheduled', $record); |
0a5aa65b | 247 | |
309ae892 DW |
248 | $now = time(); |
249 | ||
250 | // Should get handed the first task. | |
251 | $task = \core\task\manager::get_next_scheduled_task($now); | |
0a5aa65b | 252 | $this->assertInstanceOf('\core\task\scheduled_test_task', $task); |
309ae892 DW |
253 | $task->execute(); |
254 | ||
255 | \core\task\manager::scheduled_task_complete($task); | |
256 | // Should get handed the second task. | |
257 | $task = \core\task\manager::get_next_scheduled_task($now); | |
0a5aa65b | 258 | $this->assertInstanceOf('\core\task\scheduled_test2_task', $task); |
309ae892 DW |
259 | $task->execute(); |
260 | ||
261 | \core\task\manager::scheduled_task_failed($task); | |
262 | // Should not get any task. | |
263 | $task = \core\task\manager::get_next_scheduled_task($now); | |
264 | $this->assertNull($task); | |
265 | ||
266 | // Should get the second task (retry after delay). | |
267 | $task = \core\task\manager::get_next_scheduled_task($now + 120); | |
0a5aa65b | 268 | $this->assertInstanceOf('\core\task\scheduled_test2_task', $task); |
309ae892 DW |
269 | $task->execute(); |
270 | ||
271 | \core\task\manager::scheduled_task_complete($task); | |
272 | ||
273 | // Should not get any task. | |
274 | $task = \core\task\manager::get_next_scheduled_task($now); | |
275 | $this->assertNull($task); | |
276 | } | |
a0ac4060 DW |
277 | |
278 | public function test_get_broken_scheduled_task() { | |
279 | global $DB; | |
280 | ||
281 | $this->resetAfterTest(true); | |
282 | // Delete all existing scheduled tasks. | |
283 | $DB->delete_records('task_scheduled'); | |
284 | // Add a scheduled task. | |
285 | ||
286 | // A broken task that runs all the time. | |
287 | $record = new stdClass(); | |
288 | $record->blocking = true; | |
289 | $record->minute = '*'; | |
290 | $record->hour = '*'; | |
291 | $record->dayofweek = '*'; | |
292 | $record->day = '*'; | |
293 | $record->month = '*'; | |
294 | $record->component = 'test_scheduled_task'; | |
295 | $record->classname = '\core\task\scheduled_test_task_broken'; | |
296 | ||
297 | $DB->insert_record('task_scheduled', $record); | |
298 | ||
299 | $now = time(); | |
300 | // Should not get any task. | |
301 | $task = \core\task\manager::get_next_scheduled_task($now); | |
302 | $this->assertDebuggingCalled(); | |
303 | $this->assertNull($task); | |
304 | } | |
16078807 DP |
305 | |
306 | /** | |
307 | * Tests the use of 'R' syntax in time fields of tasks to get | |
308 | * tasks be configured with a non-uniform time. | |
309 | */ | |
310 | public function test_random_time_specification() { | |
311 | ||
312 | // Testing non-deterministic things in a unit test is not really | |
313 | // wise, so we just test the values have changed within allowed bounds. | |
314 | $testclass = new \core\task\scheduled_test_task(); | |
315 | ||
316 | // The test task defaults to '*'. | |
317 | $this->assertInternalType('string', $testclass->get_minute()); | |
318 | $this->assertInternalType('string', $testclass->get_hour()); | |
319 | ||
320 | // Set a random value. | |
321 | $testclass->set_minute('R'); | |
322 | $testclass->set_hour('R'); | |
f47e4eb4 | 323 | $testclass->set_day_of_week('R'); |
16078807 DP |
324 | |
325 | // Verify the minute has changed within allowed bounds. | |
326 | $minute = $testclass->get_minute(); | |
327 | $this->assertInternalType('int', $minute); | |
328 | $this->assertGreaterThanOrEqual(0, $minute); | |
329 | $this->assertLessThanOrEqual(59, $minute); | |
330 | ||
331 | // Verify the hour has changed within allowed bounds. | |
332 | $hour = $testclass->get_hour(); | |
333 | $this->assertInternalType('int', $hour); | |
334 | $this->assertGreaterThanOrEqual(0, $hour); | |
335 | $this->assertLessThanOrEqual(23, $hour); | |
f47e4eb4 AD |
336 | |
337 | // Verify the dayofweek has changed within allowed bounds. | |
338 | $dayofweek = $testclass->get_day_of_week(); | |
339 | $this->assertInternalType('int', $dayofweek); | |
340 | $this->assertGreaterThanOrEqual(0, $dayofweek); | |
341 | $this->assertLessThanOrEqual(6, $dayofweek); | |
16078807 | 342 | } |
b49964f7 AG |
343 | |
344 | /** | |
345 | * Test that the file_temp_cleanup_task removes directories and | |
346 | * files as expected. | |
347 | */ | |
348 | public function test_file_temp_cleanup_task() { | |
349 | global $CFG; | |
350 | ||
351 | // Create directories. | |
352 | $dir = $CFG->tempdir . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . 'courses'; | |
353 | mkdir($dir, 0777, true); | |
354 | ||
355 | // Create files to be checked and then deleted. | |
356 | $file01 = $dir . DIRECTORY_SEPARATOR . 'sections.xml'; | |
357 | file_put_contents($file01, 'test data 001'); | |
358 | $file02 = $dir . DIRECTORY_SEPARATOR . 'modules.xml'; | |
359 | file_put_contents($file02, 'test data 002'); | |
360 | // Change the time modified for the first file, to a time that will be deleted by the task (greater than seven days). | |
361 | touch($file01, time() - (8 * 24 * 3600)); | |
362 | ||
363 | $task = \core\task\manager::get_scheduled_task('\\core\\task\\file_temp_cleanup_task'); | |
364 | $this->assertInstanceOf('\core\task\file_temp_cleanup_task', $task); | |
365 | $task->execute(); | |
366 | ||
367 | // Scan the directory. Only modules.xml should be left. | |
368 | $filesarray = scandir($dir); | |
369 | $this->assertEquals('modules.xml', $filesarray[2]); | |
370 | $this->assertEquals(3, count($filesarray)); | |
371 | ||
372 | // Change the time modified on modules.xml. | |
373 | touch($file02, time() - (8 * 24 * 3600)); | |
374 | // Change the time modified on the courses directory. | |
375 | touch($CFG->tempdir . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'backup01' . DIRECTORY_SEPARATOR . | |
376 | 'courses', time() - (8 * 24 * 3600)); | |
377 | // Run the scheduled task to remove the file and directory. | |
378 | $task->execute(); | |
379 | $filesarray = scandir($CFG->tempdir . DIRECTORY_SEPARATOR . 'backup' . DIRECTORY_SEPARATOR . 'backup01'); | |
380 | // There should only be two items in the array, '.' and '..'. | |
381 | $this->assertEquals(2, count($filesarray)); | |
382 | ||
383 | // Change the time modified on all of the files and directories. | |
384 | $dir = new \RecursiveDirectoryIterator($CFG->tempdir); | |
385 | // Show all child nodes prior to their parent. | |
386 | $iter = new \RecursiveIteratorIterator($dir, \RecursiveIteratorIterator::CHILD_FIRST); | |
387 | ||
388 | for ($iter->rewind(); $iter->valid(); $iter->next()) { | |
d83a1c11 RT |
389 | if ($iter->isDir() && !$iter->isDot()) { |
390 | $node = $iter->getRealPath(); | |
391 | touch($node, time() - (8 * 24 * 3600)); | |
392 | } | |
b49964f7 AG |
393 | } |
394 | ||
395 | // Run the scheduled task again to remove all of the files and directories. | |
396 | $task->execute(); | |
397 | $filesarray = scandir($CFG->tempdir); | |
398 | // All of the files and directories should be deleted. | |
399 | // There should only be two items in the array, '.' and '..'. | |
400 | $this->assertEquals(2, count($filesarray)); | |
401 | } | |
309ae892 | 402 | } |