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
/**
20
 * This file contains the unit tests for the database task logger.
21
 *
22
 * @package   core
23
 * @category  test
24
 * @copyright 2018 Andrew Nicols <andrew@nicols.co.uk>
25
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26
 */
1441 ariadna 27
final class database_logger_test extends \advanced_testcase {
1 efrain 28
 
29
    /**
30
     * @var \moodle_database The original database prior to mocking
31
     */
32
    protected $DB;
33
 
34
    /**
35
     * Setup to backup the database before mocking.
36
     */
37
    public function setUp(): void {
38
        global $DB;
1441 ariadna 39
        parent::setUp();
1 efrain 40
 
41
        $this->DB = $DB;
42
    }
43
 
44
    /**
45
     * Tear down to unmock the database where it was mocked.
46
     */
47
    public function tearDown(): void {
48
        global $DB;
49
 
50
        $DB = $this->DB;
51
        $this->DB = null;
1441 ariadna 52
        parent::tearDown();
1 efrain 53
    }
54
 
55
    /**
56
     * Ensure that store_log_for_task works with a passing scheduled task.
57
     */
11 efrain 58
    public function test_store_log_for_task_scheduled(): void {
1 efrain 59
        global $DB;
60
 
61
        $this->resetAfterTest();
62
 
63
        $endtime = microtime(true);
64
        $starttime = $endtime - 4;
65
 
66
        $logdir = make_request_directory();
67
        $logpath = "{$logdir}/log.txt";
68
        file_put_contents($logpath, 'Example content');
69
 
70
        $task = new \core\task\cache_cron_task();
71
        database_logger::store_log_for_task($task, $logpath, false, 1, 2, $starttime, $endtime);
72
 
73
        $logs = $DB->get_records('task_log');
74
        $this->assertCount(1, $logs);
75
 
76
        $log = reset($logs);
77
        $this->assertEquals(file_get_contents($logpath), $log->output);
78
        $this->assertEquals(0, $log->result);
79
        $this->assertEquals(database_logger::TYPE_SCHEDULED, $log->type);
80
        $this->assertEquals('core\task\cache_cron_task', $log->classname);
81
        $this->assertEquals(0, $log->userid);
82
    }
83
 
84
    /**
85
     * Ensure that store_log_for_task works with a passing adhoc task.
86
     */
11 efrain 87
    public function test_store_log_for_task_adhoc(): void {
1 efrain 88
        global $DB;
89
 
90
        $this->resetAfterTest();
91
 
92
        $endtime = microtime(true);
93
        $starttime = $endtime - 4;
94
 
95
        $logdir = make_request_directory();
96
        $logpath = "{$logdir}/log.txt";
97
        file_put_contents($logpath, 'Example content');
98
 
99
        $task = $this->getMockBuilder(\core\task\adhoc_task::class)
100
            ->onlyMethods(['get_component', 'execute'])
101
            ->getMock();
102
 
103
        $task->method('get_component')->willReturn('core_test');
104
 
105
        database_logger::store_log_for_task($task, $logpath, false, 1, 2, $starttime, $endtime);
106
 
107
        $logs = $DB->get_records('task_log');
108
        $this->assertCount(1, $logs);
109
 
110
        $log = reset($logs);
111
        $this->assertEquals(file_get_contents($logpath), $log->output);
112
        $this->assertEquals(0, $log->result);
113
        $this->assertEquals(database_logger::TYPE_ADHOC, $log->type);
114
    }
115
 
116
    /**
117
     * Ensure that store_log_for_task works with a failing scheduled task.
118
     */
11 efrain 119
    public function test_store_log_for_task_failed_scheduled(): void {
1 efrain 120
        global $DB;
121
 
122
        $this->resetAfterTest();
123
 
124
        $endtime = microtime(true);
125
        $starttime = $endtime - 4;
126
 
127
        $logdir = make_request_directory();
128
        $logpath = "{$logdir}/log.txt";
129
        file_put_contents($logpath, 'Example content');
130
 
131
        $task = new \core\task\cache_cron_task();
132
        database_logger::store_log_for_task($task, $logpath, true, 1, 2, $starttime, $endtime);
133
 
134
        $logs = $DB->get_records('task_log');
135
        $this->assertCount(1, $logs);
136
 
137
        $log = reset($logs);
138
        $this->assertEquals(file_get_contents($logpath), $log->output);
139
        $this->assertEquals(1, $log->result);
140
        $this->assertEquals(database_logger::TYPE_SCHEDULED, $log->type);
141
        $this->assertEquals('core\task\cache_cron_task', $log->classname);
142
        $this->assertEquals(0, $log->userid);
143
    }
144
 
145
    /**
146
     * Ensure that store_log_for_task works with a failing adhoc task.
147
     */
11 efrain 148
    public function test_store_log_for_task_failed_adhoc(): void {
1 efrain 149
        global $DB;
150
 
151
        $this->resetAfterTest();
152
 
153
        $endtime = microtime(true);
154
        $starttime = $endtime - 4;
155
 
156
        $logdir = make_request_directory();
157
        $logpath = "{$logdir}/log.txt";
158
        file_put_contents($logpath, 'Example content');
159
 
160
        $task = $this->getMockBuilder(\core\task\adhoc_task::class)
161
            ->onlyMethods(['get_component', 'execute'])
162
            ->getMock();
163
 
164
        $task->method('get_component')->willReturn('core_test');
165
 
166
        database_logger::store_log_for_task($task, $logpath, true, 1, 2, $starttime, $endtime);
167
 
168
        $logs = $DB->get_records('task_log');
169
        $this->assertCount(1, $logs);
170
 
171
        $log = reset($logs);
172
        $this->assertEquals(file_get_contents($logpath), $log->output);
173
        $this->assertEquals(1, $log->result);
174
        $this->assertEquals(database_logger::TYPE_ADHOC, $log->type);
175
        $this->assertEquals(0, $log->userid);
176
    }
177
    /**
178
     * Ensure that store_log_for_task works with a passing adhoc task run as a specific user.
179
     */
11 efrain 180
    public function test_store_log_for_task_adhoc_userid(): void {
1 efrain 181
        global $DB;
182
 
183
        $this->resetAfterTest();
184
 
185
        $endtime = microtime(true);
186
        $starttime = $endtime - 4;
187
 
188
        $logdir = make_request_directory();
189
        $logpath = "{$logdir}/log.txt";
190
        file_put_contents($logpath, 'Example content');
191
 
192
        $task = $this->getMockBuilder(\core\task\adhoc_task::class)
193
            ->onlyMethods(['get_component', 'execute', 'get_userid'])
194
            ->getMock();
195
 
196
        $task->method('get_component')->willReturn('core_test');
197
        $task->method('get_userid')->willReturn(99);
198
 
199
        database_logger::store_log_for_task($task, $logpath, false, 1, 2, $starttime, $endtime);
200
 
201
        $logs = $DB->get_records('task_log');
202
        $this->assertCount(1, $logs);
203
 
204
        $log = reset($logs);
205
        $this->assertEquals(file_get_contents($logpath), $log->output);
206
        $this->assertEquals(0, $log->result);
207
        $this->assertEquals(database_logger::TYPE_ADHOC, $log->type);
208
        $this->assertEquals(99, $log->userid);
209
    }
210
 
211
    /**
212
     * Ensure that the delete_task_logs function performs necessary deletion tasks.
213
     *
214
     * @dataProvider    delete_task_logs_provider
215
     * @param   mixed   $ids
216
     */
11 efrain 217
    public function test_delete_task_logs($ids): void {
1 efrain 218
        $DB = $this->mock_database();
219
        $DB->expects($this->once())
220
            ->method('delete_records_list')
221
            ->with(
222
                $this->equalTo('task_log'),
223
                $this->equalTo('id'),
224
                $this->callback(function($deletedids) use ($ids) {
225
                    sort($ids);
226
                    $idvalues = array_values($deletedids);
227
                    sort($idvalues);
228
 
229
                    return $ids == $idvalues;
230
                })
231
            );
232
 
233
        database_logger::delete_task_logs($ids);
234
    }
235
 
236
    /**
237
     * Data provider for delete_task_logs tests.
238
     *
239
     * @return  array
240
     */
1441 ariadna 241
    public static function delete_task_logs_provider(): array {
1 efrain 242
        return [
243
            [
244
                [0],
245
                [1],
246
                [1, 2, 3, 4, 5],
247
            ],
248
        ];
249
    }
250
 
251
    /**
252
     * Ensure that the retention period applies correctly.
253
     */
11 efrain 254
    public function test_cleanup_retention(): void {
1 efrain 255
        global $DB;
256
 
257
        $this->resetAfterTest();
258
 
259
        // Set a high value for task_logretainruns so that it does no interfere.
260
        set_config('task_logretainruns', 1000);
261
 
262
        // Create sample log data - 1 run per hour for 3 days - round down to the start of the hour to avoid time race conditions.
263
        $date = new \DateTime();
264
        $date->setTime($date->format('G'), 0);
265
        $baselogtime = $date->getTimestamp();
266
 
267
        for ($i = 0; $i < 3 * 24; $i++) {
268
            $task = new \core\task\cache_cron_task();
269
            $logpath = __FILE__;
270
            database_logger::store_log_for_task($task, $logpath, false, 1, 2, $date->getTimestamp(), $date->getTimestamp() + MINSECS);
271
 
272
            $date->sub(new \DateInterval('PT1H'));
273
        }
274
 
275
        // Initially there should be 72 runs.
276
        $this->assertCount(72, $DB->get_records('task_log'));
277
 
278
        // Note: We set the retention time to a period like DAYSECS minus an adjustment.
279
        // The adjustment is to account for the time taken during setup.
280
 
281
        // With a retention period of 2 * DAYSECS, there should only be 47-48 left.
282
        set_config('task_logretention', (2 * DAYSECS) - (time() - $baselogtime));
283
        \core\task\database_logger::cleanup();
284
        $this->assertGreaterThanOrEqual(47, $DB->count_records('task_log'));
285
        $this->assertLessThanOrEqual(48, $DB->count_records('task_log'));
286
 
287
        // The oldest should be no more than 48 hours old.
288
        $oldest = $DB->get_records('task_log', [], 'timestart DESC', 'timestart', 0, 1);
289
        $oldest = reset($oldest);
290
        $this->assertGreaterThan(time() - (48 * DAYSECS), $oldest->timestart);
291
 
292
        // With a retention period of DAYSECS, there should only be 23 left.
293
        set_config('task_logretention', DAYSECS - (time() - $baselogtime));
294
        \core\task\database_logger::cleanup();
295
        $this->assertGreaterThanOrEqual(23, $DB->count_records('task_log'));
296
        $this->assertLessThanOrEqual(24, $DB->count_records('task_log'));
297
 
298
        // The oldest should be no more than 24 hours old.
299
        $oldest = $DB->get_records('task_log', [], 'timestart DESC', 'timestart', 0, 1);
300
        $oldest = reset($oldest);
301
        $this->assertGreaterThan(time() - (24 * DAYSECS), $oldest->timestart);
302
 
303
        // With a retention period of 0.5 DAYSECS, there should only be 11 left.
304
        set_config('task_logretention', (DAYSECS / 2) - (time() - $baselogtime));
305
        \core\task\database_logger::cleanup();
306
        $this->assertGreaterThanOrEqual(11, $DB->count_records('task_log'));
307
        $this->assertLessThanOrEqual(12, $DB->count_records('task_log'));
308
 
309
        // The oldest should be no more than 12 hours old.
310
        $oldest = $DB->get_records('task_log', [], 'timestart DESC', 'timestart', 0, 1);
311
        $oldest = reset($oldest);
312
        $this->assertGreaterThan(time() - (12 * DAYSECS), $oldest->timestart);
313
    }
314
 
315
    /**
316
     * Ensure that the run-count retention applies.
317
     */
11 efrain 318
    public function test_cleanup_retainruns(): void {
1 efrain 319
        global $DB;
320
 
321
        $this->resetAfterTest();
322
 
323
        // Set a high value for task_logretention so that it does not interfere.
324
        set_config('task_logretention', YEARSECS);
325
 
326
        // Create sample log data - 2 tasks, once per hour for 3 days.
327
        $date = new \DateTime();
328
        $date->setTime($date->format('G'), 0);
329
        $firstdate = $date->getTimestamp();
330
 
331
        for ($i = 0; $i < 3 * 24; $i++) {
332
            $task = new \core\task\cache_cron_task();
333
            $logpath = __FILE__;
334
            database_logger::store_log_for_task($task, $logpath, false, 1, 2, $date->getTimestamp(), $date->getTimestamp() + MINSECS);
335
 
336
            $task = new \core\task\badges_cron_task();
337
            $logpath = __FILE__;
338
            database_logger::store_log_for_task($task, $logpath, false, 1, 2, $date->getTimestamp(), $date->getTimestamp() + MINSECS);
339
 
340
            $date->sub(new \DateInterval('PT1H'));
341
        }
342
        $lastdate = $date->getTimestamp();
343
 
344
        // Initially there should be 144 runs - 72 for each task.
345
        $this->assertEquals(144, $DB->count_records('task_log'));
346
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
347
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
348
 
349
        // Grab the records for comparison.
350
        $cachecronrecords = array_values($DB->get_records('task_log', ['classname' => \core\task\cache_cron_task::class], 'timestart DESC'));
351
        $badgescronrecords = array_values($DB->get_records('task_log', ['classname' => \core\task\badges_cron_task::class], 'timestart DESC'));
352
 
353
        // Configured to retain 144 should have no effect.
354
        set_config('task_logretainruns', 144);
355
        \core\task\database_logger::cleanup();
356
        $this->assertEquals(144, $DB->count_records('task_log'));
357
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
358
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
359
 
360
        // The list of records should be identical.
361
        $this->assertEquals($cachecronrecords, array_values($DB->get_records('task_log', ['classname' => \core\task\cache_cron_task::class], 'timestart DESC')));
362
        $this->assertEquals($badgescronrecords, array_values($DB->get_records('task_log', ['classname' => \core\task\badges_cron_task::class], 'timestart DESC')));
363
 
364
        // Configured to retain 72 should have no effect either.
365
        set_config('task_logretainruns', 72);
366
        \core\task\database_logger::cleanup();
367
        $this->assertEquals(144, $DB->count_records('task_log'));
368
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
369
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
370
 
371
        // The list of records should now only contain the first 72 of each.
372
        $this->assertEquals(
373
            array_slice($cachecronrecords, 0, 72),
374
            array_values($DB->get_records('task_log', ['classname' => \core\task\cache_cron_task::class], 'timestart DESC'))
375
        );
376
        $this->assertEquals(
377
            array_slice($badgescronrecords, 0, 72),
378
            array_values($DB->get_records('task_log', ['classname' => \core\task\badges_cron_task::class], 'timestart DESC'))
379
        );
380
 
381
        // Configured to only retain 24 should bring that down to a total of 48, or 24 each.
382
        set_config('task_logretainruns', 24);
383
        \core\task\database_logger::cleanup();
384
        $this->assertEquals(48, $DB->count_records('task_log'));
385
        $this->assertEquals(24, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
386
        $this->assertEquals(24, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
387
 
388
        // The list of records should now only contain the first 24 of each.
389
        $this->assertEquals(
390
            array_slice($cachecronrecords, 0, 24),
391
            array_values($DB->get_records('task_log', ['classname' => \core\task\cache_cron_task::class], 'timestart DESC'))
392
        );
393
        $this->assertEquals(
394
            array_slice($badgescronrecords, 0, 24),
395
            array_values($DB->get_records('task_log', ['classname' => \core\task\badges_cron_task::class], 'timestart DESC'))
396
        );
397
 
398
        // Configured to only retain 5 should bring that down to a total of 10, or 5 each.
399
        set_config('task_logretainruns', 5);
400
        \core\task\database_logger::cleanup();
401
        $this->assertEquals(10, $DB->count_records('task_log'));
402
        $this->assertEquals(5, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
403
        $this->assertEquals(5, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
404
 
405
        // The list of records should now only contain the first 5 of each.
406
        $this->assertEquals(
407
            array_slice($cachecronrecords, 0, 5),
408
            array_values($DB->get_records('task_log', ['classname' => \core\task\cache_cron_task::class], 'timestart DESC'))
409
        );
410
        $this->assertEquals(
411
            array_slice($badgescronrecords, 0, 5),
412
            array_values($DB->get_records('task_log', ['classname' => \core\task\badges_cron_task::class], 'timestart DESC'))
413
        );
414
 
415
        // Configured to only retain 0 should bring that down to none.
416
        set_config('task_logretainruns', 0);
417
        \core\task\database_logger::cleanup();
418
        $this->assertEquals(0, $DB->count_records('task_log'));
419
    }
420
 
421
    /**
422
     * Ensure that the retention period applies correctly when combined with the run count retention.
423
     */
11 efrain 424
    public function test_cleanup_combined(): void {
1 efrain 425
        global $DB;
426
 
427
        $this->resetAfterTest();
428
 
429
        // Calculate date to be used for logs, starting from current time rounded down to nearest hour.
430
        $date = new \DateTime();
431
        $date->setTime($date->format('G'), 0);
432
        $baselogtime = $date->getTimestamp();
433
 
434
        // Create sample log data - 2 tasks, once per hour for 3 days.
435
        for ($i = 0; $i < 3 * 24; $i++) {
436
            $task = new \core\task\cache_cron_task();
437
            $logpath = __FILE__;
438
            database_logger::store_log_for_task($task, $logpath, false, 1, 2, $date->getTimestamp(), $date->getTimestamp() + MINSECS);
439
 
440
            $task = new \core\task\badges_cron_task();
441
            $logpath = __FILE__;
442
            database_logger::store_log_for_task($task, $logpath, false, 1, 2, $date->getTimestamp(), $date->getTimestamp() + MINSECS);
443
 
444
            $date->sub(new \DateInterval('PT1H'));
445
        }
446
 
447
        // Initially there should be 144 runs - 72 for each task.
448
        $this->assertEquals(144, $DB->count_records('task_log'));
449
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
450
        $this->assertEquals(72, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
451
 
452
        // Note: We set the retention time to a period like DAYSECS minus an adjustment.
453
        // The adjustment is to account for the difference between current time and baselogtime.
454
 
455
        // With a retention period of 2 * DAYSECS, there should only be 96 left.
456
        // The run count is a higher number so it will have no effect.
457
        set_config('task_logretention', time() - ($baselogtime - (2 * DAYSECS)) - 1);
458
        set_config('task_logretainruns', 50);
459
        \core\task\database_logger::cleanup();
460
 
461
        $this->assertEquals(96, $DB->count_records('task_log'));
462
        $this->assertEquals(48, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
463
        $this->assertEquals(48, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
464
 
465
        // We should retain the most recent 48 of each task, so the oldest will be 47 hours old.
466
        $oldest = $DB->get_records('task_log', [], 'timestart ASC', 'timestart', 0, 1);
467
        $oldest = reset($oldest);
468
        $this->assertEquals($baselogtime - (47 * HOURSECS), $oldest->timestart);
469
 
470
        // Reducing the retain runs count to 10 should reduce the total logs to 20, overriding the time constraint.
471
        set_config('task_logretainruns', 10);
472
        \core\task\database_logger::cleanup();
473
 
474
        $this->assertEquals(20, $DB->count_records('task_log'));
475
        $this->assertEquals(10, $DB->count_records('task_log', ['classname' => \core\task\cache_cron_task::class]));
476
        $this->assertEquals(10, $DB->count_records('task_log', ['classname' => \core\task\badges_cron_task::class]));
477
 
478
        // We should retain the most recent 10 of each task, so the oldest will be 9 hours old.
479
        $oldest = $DB->get_records('task_log', [], 'timestart ASC', 'timestart', 0, 1);
480
        $oldest = reset($oldest);
481
        $this->assertEquals($baselogtime - (9 * HOURSECS), $oldest->timestart);
482
    }
483
 
484
    /**
485
     * Mock the database.
486
     */
487
    protected function mock_database() {
488
        global $DB;
489
 
490
        $DB = $this->getMockBuilder(\moodle_database::class)
491
            ->getMock();
492
 
493
        $DB->method('get_record')
494
            ->willReturn((object) []);
495
 
496
        return $DB;
497
    }
498
}