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