Proyectos de Subversion Moodle

Rev

Rev 1 | | 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 logstore_standard;
18
 
19
defined('MOODLE_INTERNAL') || die();
20
 
21
require_once(__DIR__ . '/fixtures/event.php');
22
require_once(__DIR__ . '/fixtures/restore_hack.php');
23
 
24
/**
25
 * Standard log store tests.
26
 *
27
 * @package    logstore_standard
28
 * @copyright  2014 Petr Skoda {@link http://skodak.org/}
29
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
30
 */
31
class store_test extends \advanced_testcase {
32
    /**
33
     * @var bool Determine if we disabled the GC, so it can be re-enabled in tearDown.
34
     */
35
    private $wedisabledgc = false;
36
 
37
    /**
38
     * Tests log writing.
39
     *
40
     * @param bool $jsonformat True to test with JSON format
41
     * @dataProvider log_writing_provider
42
     */
11 efrain 43
    public function test_log_writing(bool $jsonformat): void {
1 efrain 44
        global $DB;
45
        $this->resetAfterTest();
46
        $this->preventResetByRollback(); // Logging waits till the transaction gets committed.
47
 
48
        // Apply JSON format system setting.
49
        set_config('jsonformat', $jsonformat ? 1 : 0, 'logstore_standard');
50
 
51
        $this->setAdminUser();
52
        $user1 = $this->getDataGenerator()->create_user();
53
        $user2 = $this->getDataGenerator()->create_user();
54
        $course1 = $this->getDataGenerator()->create_course();
55
        $module1 = $this->getDataGenerator()->create_module('resource', array('course' => $course1));
56
        $course2 = $this->getDataGenerator()->create_course();
57
        $module2 = $this->getDataGenerator()->create_module('resource', array('course' => $course2));
58
 
59
        // Test all plugins are disabled by this command.
60
        set_config('enabled_stores', '', 'tool_log');
61
        $manager = get_log_manager(true);
62
        $stores = $manager->get_readers();
63
        $this->assertCount(0, $stores);
64
 
65
        // Enable logging plugin.
66
        set_config('enabled_stores', 'logstore_standard', 'tool_log');
67
        set_config('buffersize', 0, 'logstore_standard');
68
        set_config('logguests', 1, 'logstore_standard');
69
        $manager = get_log_manager(true);
70
 
71
        $stores = $manager->get_readers();
72
        $this->assertCount(1, $stores);
73
        $this->assertEquals(array('logstore_standard'), array_keys($stores));
74
        /** @var \logstore_standard\log\store $store */
75
        $store = $stores['logstore_standard'];
76
        $this->assertInstanceOf('logstore_standard\log\store', $store);
77
        $this->assertInstanceOf('tool_log\log\writer', $store);
78
        $this->assertTrue($store->is_logging());
79
 
80
        $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC');
81
        $this->assertCount(0, $logs);
82
        $exists = $store->get_events_select_exists('', array(), 'timecreated ASC', 0, 0);
83
        $this->assertFalse($exists);
84
 
85
        $this->setCurrentTimeStart();
86
 
87
        $this->setUser(0);
88
        $event1 = \logstore_standard\event\unittest_executed::create(
89
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)));
90
        $event1->trigger();
91
 
92
        $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC');
93
        $this->assertCount(1, $logs);
94
 
95
        $log1 = reset($logs);
96
        unset($log1->id);
97
        if ($jsonformat) {
98
            $log1->other = json_decode($log1->other, true);
99
        } else {
100
            $log1->other = unserialize($log1->other);
101
        }
102
        $log1 = (array)$log1;
103
        $data = $event1->get_data();
104
        $data['origin'] = 'cli';
105
        $data['ip'] = null;
106
        $data['realuserid'] = null;
107
        $this->assertEquals($data, $log1);
108
 
109
        $this->setAdminUser();
110
        \core\session\manager::loginas($user1->id, \context_system::instance());
111
        $this->assertEquals(2, $DB->count_records('logstore_standard_log'));
112
 
113
        \logstore_standard_restore::hack_executing(1);
114
        $event2 = \logstore_standard\event\unittest_executed::create(
115
            array('context' => \context_module::instance($module2->cmid), 'other' => array('sample' => 6, 'xx' => 9)));
116
        $event2->trigger();
117
        \logstore_standard_restore::hack_executing(0);
118
 
119
        \core\session\manager::init_empty_session();
120
        $this->assertFalse(\core\session\manager::is_loggedinas());
121
 
122
        $logs = $DB->get_records('logstore_standard_log', array(), 'id ASC');
123
        $this->assertCount(3, $logs);
124
        array_shift($logs);
125
        $log2 = array_shift($logs);
126
        $this->assertSame('\core\event\user_loggedinas', $log2->eventname);
127
        $this->assertSame('cli', $log2->origin);
128
 
129
        $log3 = array_shift($logs);
130
        unset($log3->id);
131
        if ($jsonformat) {
132
            $log3->other = json_decode($log3->other, true);
133
        } else {
134
            $log3->other = unserialize($log3->other);
135
        }
136
        $log3 = (array)$log3;
137
        $data = $event2->get_data();
138
        $data['origin'] = 'restore';
139
        $data['ip'] = null;
140
        $data['realuserid'] = 2;
141
        $this->assertEquals($data, $log3);
142
 
143
        // Test table exists.
144
        $tablename = $store->get_internal_log_table_name();
145
        $this->assertTrue($DB->get_manager()->table_exists($tablename));
146
 
147
        // Test reading.
148
        $this->assertSame(3, $store->get_events_select_count('', array()));
149
        $events = $store->get_events_select('', array(), 'timecreated ASC', 0, 0); // Is actually sorted by "timecreated ASC, id ASC".
150
        $this->assertCount(3, $events);
151
        $exists = $store->get_events_select_exists('', array(), 'timecreated ASC', 0, 0);
152
        $this->assertTrue($exists);
153
        $resev1 = array_shift($events);
154
        array_shift($events);
155
        $resev2 = array_shift($events);
156
        $this->assertEquals($event1->get_data(), $resev1->get_data());
157
        $this->assertEquals($event2->get_data(), $resev2->get_data());
158
 
159
        // Test buffering.
160
        set_config('buffersize', 3, 'logstore_standard');
161
        $manager = get_log_manager(true);
162
        $stores = $manager->get_readers();
163
        /** @var \logstore_standard\log\store $store */
164
        $store = $stores['logstore_standard'];
165
        $DB->delete_records('logstore_standard_log');
166
 
167
        \logstore_standard\event\unittest_executed::create(
168
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
169
        $this->assertEquals(0, $DB->count_records('logstore_standard_log'));
170
        \logstore_standard\event\unittest_executed::create(
171
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
172
        $this->assertEquals(0, $DB->count_records('logstore_standard_log'));
173
        $store->flush();
174
        $this->assertEquals(2, $DB->count_records('logstore_standard_log'));
175
        \logstore_standard\event\unittest_executed::create(
176
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
177
        $this->assertEquals(2, $DB->count_records('logstore_standard_log'));
178
        \logstore_standard\event\unittest_executed::create(
179
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
180
        $this->assertEquals(2, $DB->count_records('logstore_standard_log'));
181
        \logstore_standard\event\unittest_executed::create(
182
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
183
        $this->assertEquals(5, $DB->count_records('logstore_standard_log'));
184
        \logstore_standard\event\unittest_executed::create(
185
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
186
        $this->assertEquals(5, $DB->count_records('logstore_standard_log'));
187
        \logstore_standard\event\unittest_executed::create(
188
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
189
        $this->assertEquals(5, $DB->count_records('logstore_standard_log'));
190
        \logstore_standard\event\unittest_executed::create(
191
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
192
        $this->assertEquals(8, $DB->count_records('logstore_standard_log'));
193
 
194
        // Test guest logging setting.
195
        set_config('logguests', 0, 'logstore_standard');
196
        set_config('buffersize', 0, 'logstore_standard');
197
        get_log_manager(true);
198
        $DB->delete_records('logstore_standard_log');
199
        get_log_manager(true);
200
 
201
        $this->setUser(null);
202
        \logstore_standard\event\unittest_executed::create(
203
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
204
        $this->assertEquals(0, $DB->count_records('logstore_standard_log'));
205
 
206
        $this->setGuestUser();
207
        \logstore_standard\event\unittest_executed::create(
208
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
209
        $this->assertEquals(0, $DB->count_records('logstore_standard_log'));
210
 
211
        $this->setUser($user1);
212
        \logstore_standard\event\unittest_executed::create(
213
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
214
        $this->assertEquals(1, $DB->count_records('logstore_standard_log'));
215
 
216
        $this->setUser($user2);
217
        \logstore_standard\event\unittest_executed::create(
218
            array('context' => \context_module::instance($module1->cmid), 'other' => array('sample' => 5, 'xx' => 10)))->trigger();
219
        $this->assertEquals(2, $DB->count_records('logstore_standard_log'));
220
 
221
        set_config('enabled_stores', '', 'tool_log');
222
        get_log_manager(true);
223
    }
224
 
225
    /**
226
     * Returns different JSON format settings so the test can be run with JSON format either on or
227
     * off.
228
     *
229
     * @return bool[] Array of true/false
230
     */
231
    public static function log_writing_provider(): array {
232
        return [
233
            [false],
234
            [true]
235
        ];
236
    }
237
 
238
    /**
239
     * Test logmanager::get_supported_reports returns all reports that require this store.
240
     */
11 efrain 241
    public function test_get_supported_reports(): void {
1 efrain 242
        $logmanager = get_log_manager();
243
        $allreports = \core_component::get_plugin_list('report');
244
 
245
        // Make sure all supported reports are installed.
246
        $expectedreports = array_intersect_key([
247
            'log' => 'report_log',
248
            'loglive' => 'report_loglive',
249
            'outline' => 'report_outline',
250
            'participation' => 'report_participation',
251
            'stats' => 'report_stats'
252
        ], $allreports);
253
 
254
        $supportedreports = $logmanager->get_supported_reports('logstore_standard');
255
 
256
        foreach ($expectedreports as $expectedreport) {
257
            $this->assertArrayHasKey($expectedreport, $supportedreports);
258
        }
259
    }
260
 
261
    /**
262
     * Verify that gc disabling works
263
     */
11 efrain 264
    public function test_gc_enabled_as_expected(): void {
1 efrain 265
        if (!gc_enabled()) {
266
            $this->markTestSkipped('Garbage collector (gc) is globally disabled.');
267
        }
268
 
269
        $this->disable_gc();
270
        $this->assertTrue($this->wedisabledgc);
271
        $this->assertFalse(gc_enabled());
272
    }
273
 
274
    /**
275
     * Test sql_reader::get_events_select_iterator.
276
     * @return void
277
     */
11 efrain 278
    public function test_events_traversable(): void {
1 efrain 279
        global $DB;
280
 
281
        $this->disable_gc();
282
 
283
        $this->resetAfterTest();
284
        $this->preventResetByRollback();
285
        $this->setAdminUser();
286
 
287
        set_config('enabled_stores', 'logstore_standard', 'tool_log');
288
 
289
        $manager = get_log_manager(true);
290
        $stores = $manager->get_readers();
291
        $store = $stores['logstore_standard'];
292
 
293
        $events = $store->get_events_select_iterator('', array(), '', 0, 0);
294
        $this->assertFalse($events->valid());
295
 
296
        // Here it should be already closed, but we should be allowed to
297
        // over-close it without exception.
298
        $events->close();
299
 
300
        $user = $this->getDataGenerator()->create_user();
301
        for ($i = 0; $i < 1000; $i++) {
302
            \core\event\user_created::create_from_userid($user->id)->trigger();
303
        }
304
        $store->flush();
305
 
306
        // Check some various sizes get the right number of elements.
307
        $this->assertEquals(1, iterator_count($store->get_events_select_iterator('', array(), '', 0, 1)));
308
        $this->assertEquals(2, iterator_count($store->get_events_select_iterator('', array(), '', 0, 2)));
309
 
310
        $iterator = $store->get_events_select_iterator('', array(), '', 0, 500);
311
        $this->assertInstanceOf('\core\event\base', $iterator->current());
312
        $this->assertEquals(500, iterator_count($iterator));
313
        $iterator->close();
314
 
315
        // Look for non-linear memory usage for the iterator version.
316
        $mem = memory_get_usage();
317
        $events = $store->get_events_select('', array(), '', 0, 0);
318
        $arraymemusage = memory_get_usage() - $mem;
319
 
320
        $mem = memory_get_usage();
321
        $eventsit = $store->get_events_select_iterator('', array(), '', 0, 0);
322
        $eventsit->close();
323
        $itmemusage = memory_get_usage() - $mem;
324
 
325
        $this->assertInstanceOf('\Traversable', $eventsit);
326
 
327
        $this->assertLessThan($arraymemusage / 10, $itmemusage);
328
        set_config('enabled_stores', '', 'tool_log');
329
        get_log_manager(true);
330
    }
331
 
332
    /**
333
     * Test that the standard log cleanup works correctly.
334
     */
11 efrain 335
    public function test_cleanup_task(): void {
1 efrain 336
        global $DB;
337
 
338
        $this->resetAfterTest();
339
 
340
        // Create some records spread over various days; test multiple iterations in cleanup.
341
        $ctx = \context_course::instance(1);
342
        $record = (object) array(
343
            'edulevel' => 0,
344
            'contextid' => $ctx->id,
345
            'contextlevel' => $ctx->contextlevel,
346
            'contextinstanceid' => $ctx->instanceid,
347
            'userid' => 1,
348
            'timecreated' => time(),
349
        );
350
        $DB->insert_record('logstore_standard_log', $record);
351
        $record->timecreated -= 3600 * 24 * 30;
352
        $DB->insert_record('logstore_standard_log', $record);
353
        $record->timecreated -= 3600 * 24 * 30;
354
        $DB->insert_record('logstore_standard_log', $record);
355
        $record->timecreated -= 3600 * 24 * 30;
356
        $DB->insert_record('logstore_standard_log', $record);
357
        $this->assertEquals(4, $DB->count_records('logstore_standard_log'));
358
 
359
        // Remove all logs before "today".
360
        set_config('loglifetime', 1, 'logstore_standard');
361
 
362
        $this->expectOutputString(" Deleted old log records from standard store.\n");
363
        $clean = new \logstore_standard\task\cleanup_task();
364
        $clean->execute();
365
 
366
        $this->assertEquals(1, $DB->count_records('logstore_standard_log'));
367
    }
368
 
369
    /**
370
     * Tests the decode_other function can cope with both JSON and PHP serialized format.
371
     *
372
     * @param mixed $value Value to encode and decode
373
     * @dataProvider decode_other_provider
374
     */
11 efrain 375
    public function test_decode_other($value): void {
1 efrain 376
        $this->assertEquals($value, \logstore_standard\log\store::decode_other(serialize($value)));
377
        $this->assertEquals($value, \logstore_standard\log\store::decode_other(json_encode($value)));
378
    }
379
 
11 efrain 380
    public function test_decode_other_with_wrongly_encoded_contents(): void {
1 efrain 381
        $this->assertSame(null, \logstore_standard\log\store::decode_other(null));
382
    }
383
 
384
    /**
385
     * List of possible values for 'other' field.
386
     *
387
     * I took these types from our logs based on the different first character of PHP serialized
388
     * data - my query found only these types. The normal case is an array.
389
     *
390
     * @return array Array of parameters
391
     */
392
    public function decode_other_provider(): array {
393
        return [
394
            [['info' => 'd2819896', 'logurl' => 'discuss.php?d=2819896']],
395
            [null],
396
            ['just a string'],
397
            [32768]
398
        ];
399
    }
400
 
401
    /**
402
     * Checks that backup and restore of log data works correctly.
403
     *
404
     * @param bool $jsonformat True to test with JSON format
405
     * @dataProvider log_writing_provider
406
     */
11 efrain 407
    public function test_backup_restore(bool $jsonformat): void {
1 efrain 408
        global $DB;
409
        $this->resetAfterTest();
410
        $this->preventResetByRollback();
411
 
412
        // Enable logging plugin.
413
        set_config('enabled_stores', 'logstore_standard', 'tool_log');
414
        set_config('buffersize', 0, 'logstore_standard');
415
        $manager = get_log_manager(true);
416
 
417
        // User must be enrolled in course.
418
        $generator = $this->getDataGenerator();
419
        $course = $generator->create_course();
420
        $user = $generator->create_user();
421
        $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
422
        $this->setUser($user);
423
 
424
        // Apply JSON format system setting.
425
        set_config('jsonformat', $jsonformat ? 1 : 0, 'logstore_standard');
426
 
427
        // Create some log data in a course - one with other data, one without.
428
        \logstore_standard\event\unittest_executed::create([
429
                'context' => \context_course::instance($course->id),
430
                'other' => ['sample' => 5, 'xx' => 10]])->trigger();
431
        $this->waitForSecond();
432
        \logstore_standard\event\unittest_executed::create([
433
                'context' => \context_course::instance($course->id)])->trigger();
434
 
435
        $records = array_values($DB->get_records('logstore_standard_log',
436
                ['courseid' => $course->id, 'target' => 'unittest'], 'timecreated'));
437
        $this->assertCount(2, $records);
438
 
439
        // Work out expected 'other' values based on JSON format.
440
        $expected0 = [
441
            false => 'a:2:{s:6:"sample";i:5;s:2:"xx";i:10;}',
442
            true => '{"sample":5,"xx":10}'
443
        ];
444
        $expected1 = [
445
            false => 'N;',
446
            true => 'null'
447
        ];
448
 
449
        // Backup the course twice, including log data.
450
        $this->setAdminUser();
451
        $backupid1 = $this->backup($course);
452
        $backupid2 = $this->backup($course);
453
 
454
        // Restore it with same jsonformat.
455
        $newcourseid = $this->restore($backupid1, $course, '_A');
456
 
457
        // Check entries are correctly encoded.
458
        $records = array_values($DB->get_records('logstore_standard_log',
459
                ['courseid' => $newcourseid, 'target' => 'unittest'], 'timecreated'));
460
        $this->assertCount(2, $records);
461
        $this->assertEquals($expected0[$jsonformat], $records[0]->other);
462
        $this->assertEquals($expected1[$jsonformat], $records[1]->other);
463
 
464
        // Change JSON format to opposite value and restore again.
465
        set_config('jsonformat', $jsonformat ? 0 : 1, 'logstore_standard');
466
        $newcourseid = $this->restore($backupid2, $course, '_B');
467
 
468
        // Check entries are correctly encoded in other format.
469
        $records = array_values($DB->get_records('logstore_standard_log',
470
                ['courseid' => $newcourseid, 'target' => 'unittest'], 'timecreated'));
471
        $this->assertEquals($expected0[!$jsonformat], $records[0]->other);
472
        $this->assertEquals($expected1[!$jsonformat], $records[1]->other);
473
    }
474
 
475
    /**
476
     * Backs a course up to temp directory.
477
     *
478
     * @param \stdClass $course Course object to backup
479
     * @return string ID of backup
480
     */
481
    protected function backup($course): string {
482
        global $USER, $CFG;
483
        require_once($CFG->dirroot . '/backup/util/includes/backup_includes.php');
484
 
485
        // Turn off file logging, otherwise it can't delete the file (Windows).
486
        $CFG->backup_file_logger_level = \backup::LOG_NONE;
487
 
488
        // Do backup with default settings. MODE_IMPORT means it will just
489
        // create the directory and not zip it.
490
        $bc = new \backup_controller(\backup::TYPE_1COURSE, $course->id,
491
                \backup::FORMAT_MOODLE, \backup::INTERACTIVE_NO, \backup::MODE_IMPORT,
492
                $USER->id);
493
        $bc->get_plan()->get_setting('users')->set_status(\backup_setting::NOT_LOCKED);
494
        $bc->get_plan()->get_setting('users')->set_value(true);
495
        $bc->get_plan()->get_setting('logs')->set_value(true);
496
        $backupid = $bc->get_backupid();
497
 
498
        $bc->execute_plan();
499
        $bc->destroy();
500
        return $backupid;
501
    }
502
 
503
    /**
504
     * Restores a course from temp directory.
505
     *
506
     * @param string $backupid Backup id
507
     * @param \stdClass $course Original course object
508
     * @param string $suffix Suffix to add after original course shortname and fullname
509
     * @return int New course id
510
     * @throws \restore_controller_exception
511
     */
512
    protected function restore(string $backupid, $course, string $suffix): int {
513
        global $USER, $CFG;
514
        require_once($CFG->dirroot . '/backup/util/includes/restore_includes.php');
515
 
516
        // Do restore to new course with default settings.
517
        $newcourseid = \restore_dbops::create_new_course(
518
                $course->fullname . $suffix, $course->shortname . $suffix, $course->category);
519
        $rc = new \restore_controller($backupid, $newcourseid,
520
                \backup::INTERACTIVE_NO, \backup::MODE_GENERAL, $USER->id,
521
                \backup::TARGET_NEW_COURSE);
522
        $rc->get_plan()->get_setting('logs')->set_value(true);
523
        $rc->get_plan()->get_setting('users')->set_value(true);
524
 
525
        $this->assertTrue($rc->execute_precheck());
526
        $rc->execute_plan();
527
        $rc->destroy();
528
 
529
        return $newcourseid;
530
    }
531
 
532
    /**
533
     * Disable the garbage collector if it's enabled to ensure we don't adjust memory statistics.
534
     */
535
    private function disable_gc() {
536
        if (gc_enabled()) {
537
            $this->wedisabledgc = true;
538
            gc_disable();
539
        }
540
    }
541
 
542
    /**
543
     * Reset any garbage collector changes to the previous state at the end of the test.
544
     */
545
    public function tearDown(): void {
546
        if ($this->wedisabledgc) {
547
            gc_enable();
548
        }
549
        $this->wedisabledgc = false;
550
    }
551
}