MDL-30643 - Added statslib test file.
[moodle.git] / lib / tests / statslib_test.php
CommitLineData
c0f00c5f
TB
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/**
18 * Tests for ../statslib.php
19 *
20 * @package core
21 * @subpackage stats
22 * @copyright 2012 Tyler Bannister
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26defined('MOODLE_INTERNAL') || die();
27
28global $CFG;
29require_once($CFG->libdir . '/adminlib.php');
30require_once($CFG->libdir . '/statslib.php');
31
32/**
33 * Test functions that affect daily stats
34 */
35class statslib_daily_testcase extends advanced_testcase {
36 /** The student role ID **/
37 const STID = 5;
38
39 /** The day to use for testing **/
40 const DAY = 1272700810; // 1272758400
41
42 /** @var array The list of temporary tables created for the statistic calculations **/
43 protected $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily');
44
45 /** @var array The replacements to be used when loading XML files **/
46 protected $replacements = null;
47
48 /**
49 * Set up the database for tests
50 *
51 * This function is needed so that daily_log_provider has the before-test set up from setUp()
52 */
53 public function setUpDB() {
54 global $DB;
55
56 if ($DB->record_exists('user', array('username' => 'user1'))) {
57 return;
58 }
59
60 $datagen = self::getDataGenerator();
61
62 $user1 = $datagen->create_user(array('username'=>'user1'));
63 $user2 = $datagen->create_user(array('username'=>'user2'));
64
65 $course1 = $datagen->create_course(array('shortname'=>'course1'));
66
67 $success = enrol_try_internal_enrol($course1->id, $user1->id, 5);
68
69 if (! $success) {
70 trigger_error('User enrollment failed', E_USER_ERROR);
71 }
72
73 $context = context_system::instance();
74 role_assign(self::STID, $user2->id, $context->id);
75
76 $this->generate_replacement_list();
77 }
78
79 /**
80 * Setup function
81 * - Allow changes to CFG->debug for testing purposes.
82 */
83 protected function setUp() {
84 global $CFG;
85 parent::setUp();
86
87 // Settings to force statistic to run during testing
88 $CFG->timezone = 99;
89 $CFG->statsfirstrun = 'all';
90 $CFG->statslastdaily = 0;
91 $CFG->statslastexecution = 0;
92 $CFG->statsruntimestarthour = date('H');
93 $CFG->statsruntimestartminute = 0;
94
95 $this->setUpDB();
96
97 $this->resetAfterTest(true);
98 }
99
100 /**
101 * Function to setup database.
102 *
103 * @param array $dataset An array of tables including the log table.
104 */
105 protected function prepare_db($dataset, $tables) {
106 global $DB;
107
108 foreach ($tables as $tablename) {
109 $DB->delete_records($tablename);
110
111 foreach ($dataset as $name => $table) {
112
113 if ($tablename == $name) {
114
115 $rows = $table->getRowCount();
116
117 for ($i = 0; $i < $rows; $i++) {
118 $row = $table->getRow($i);
119
120 $DB->insert_record($tablename, $row, false, true);
121 }
122 }
123 }
124 }
125 }
126
127 /**
128 * Load dataset from XML file
129 *
130 * @param string $file The name of the file to load
131 */
132 protected function generate_replacement_list() {
133 global $CFG, $DB;
134
135 if ($this->replacements !== null) {
136 return;
137 }
138
139 $guest = $DB->get_record('user', array('id' => $CFG->siteguest));
140 $user1 = $DB->get_record('user', array('username' => 'user1'));
141 $user2 = $DB->get_record('user', array('username' => 'user2'));
142
143 if (($guest === false) || ($user1 === false) || ($user2 === false)) {
144 trigger_error('User setup incomplete', E_USER_ERROR);
145 }
146
147 $site = $DB->get_record('course', array('id' => SITEID));
148 $course1 = $DB->get_record('course', array('shortname' => 'course1'));
149
150 if (($site === false) || ($course1 === false)) {
151 trigger_error('Course setup incomplete', E_USER_ERROR);
152 }
153
154 $start = stats_get_base_daily(self::DAY + 3600);
155 $startnolog = stats_get_base_daily(stats_get_start_from('daily'));
156 $gr = get_guest_role();
157
158 $this->replacements = array(
159 // Start and end times
160 '[start_0]' => $start - 14410, // 4 hours before
161 '[start_1]' => $start + 14410, // 4 hours after
162 '[start_2]' => $start + 14420,
163 '[start_3]' => $start + 14430,
164 '[start_4]' => $start + 100800, // 28 hours after
165 '[end]' => stats_get_next_day_start($start),
166 '[end_no_logs]' => stats_get_next_day_start($startnolog),
167
168 // User ids
169 '[guest_id]' => $guest->id,
170 '[user1_id]' => $user1->id,
171 '[user2_id]' => $user2->id,
172
173 // Course ids
174 '[course1_id]' => $course1->id,
175 '[site_id]' => SITEID,
176
177 // Role ids
178 '[frontpage_roleid]' => (int) $CFG->defaultfrontpageroleid,
179 '[guest_roleid]' => $gr->id,
180 '[student_roleid]' => self::STID,
181 );
182 }
183
184 /**
185 * Load dataset from XML file
186 *
187 * @param string $file The name of the file to load
188 */
189 protected function load_xml_data_file($file) {
190 static $replacements = null;
191
192 $raw = $this->createXMLDataSet($file);
193 $clean = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($raw);
194
195 foreach ($this->replacements as $placeholder => $value) {
196 $clean->addFullReplacement($placeholder, $value);
197 }
198
199 $logs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean);
200 $logs->addIncludeTables(array('log'));
201
202 $stats = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean);
203 $stats->addIncludeTables(array('stats_daily', 'stats_user_daily'));
204
205 return array($logs, $stats);
206 }
207
208 /**
209 * Provides the log data for test_statslib_cron_daily
210 */
211 public function daily_log_provider() {
212 global $CFG, $DB;
213
214 $this->setUpDB();
215
216 $tests = array('00', '01', '02', '03', '04', '05', '06', '07', '08');
217
218 $dataset = array();
219
220 foreach ($tests as $test) {
221 $dataset[] = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test{$test}.xml");
222 }
223
224 return $dataset;
225 }
226
227 /**
228 * Compare the expected stats to those in the database.
229 *
230 * @param array $stats An array of arrays of arrays of both types of stats
231 */
232 protected function verify_stats($expected) {
233 global $DB;
234
235 // Note: We can not use $this->assertDataSetEqual($expected, $actual) because there's no
236 // $this->getConnection() in advanced_testcase.
237
238 foreach ($expected as $type => $table) {
239 $records = $DB->get_records($type);
240
241 $rows = $table->getRowCount();
242
243 $this->assertEquals($rows, sizeof($records),
244 'Incorrect number of results returned for '. $type);
245
246 for ($i = 0; $i < $rows; $i++) {
247 $row = $table->getRow($i);
248 $found = 0;
249
250 foreach ($records as $key => $record) {
251 $record = (array) $record;
252 unset($record['id']);
253 $diff = array_merge(array_diff_assoc($row, $record),
254 array_diff_assoc($record, $row));
255
256 if (empty($diff)) {
257 $found = $key;
258 break;
259 }
260 }
261 $this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true)
262 ." was not found in $type ". var_export($records, true));
263 unset($records[$found]);
264 }
265 }
266 }
267
268 /**
269 * Test progress output when debug is on
270 */
271 public function test_statslib_progress_debug() {
272 global $CFG;
273
274 $CFG->debug = DEBUG_ALL;
275 $this->expectOutputString('1:0 ');
276 stats_progress('init');
277 stats_progress('1');
278 }
279
280 /**
281 * Test progress output when debug is off
282 */
283 public function test_statslib_progress_no_debug() {
284 global $CFG;
285
286 $CFG->debug = DEBUG_NONE;
287 $this->expectOutputString('.');
288 stats_progress('init');
289 stats_progress('1');
290 }
291
292 /**
293 * Test the function that gets the start date from the config
294 */
295 public function test_statslib_get_start_from() {
296 global $CFG, $DB;
297
298 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml");
299
300 $time = time();
301 $DB->delete_records('log');
302
303 $CFG->statsfirstrun = 'all';
304 // Allow 1 second difference in case we cross a second boundary.
305 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'All start time');
306
307 $this->prepare_db($dataset[0], array('log'));
308 $records = $DB->get_records('log');
309
310 $this->assertEquals(self::DAY, stats_get_start_from('daily'), 'Log entry start');
311
312 $CFG->statsfirstrun = 'none';
313 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (3 * 24 * 3600)), 'None start time');
314
315 $CFG->statsfirstrun = 14515200;
316 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (14515200)), 'Specified start time');
317
318 $this->prepare_db($dataset[1], array('stats_daily'));
319 $this->assertEquals(self::DAY - 14410 + (24 * 3600), stats_get_start_from('daily'), 'Daily stats start time');
320 }
321
322 /**
323 * Test the function that calculates the start of the day
324 */
325 public function test_statslib_get_base_daily() {
326 global $CFG;
327
328 $CFG->timezone = 0;
329 $this->assertEquals(1272672000, stats_get_base_daily(1272686410));
330 $CFG->timezone = 5;
331 $this->assertEquals(1272654000, stats_get_base_daily(1272686410));
332 }
333
334 /**
335 * Test the function that gets the start of the next day
336 */
337 public function test_statslib_get_next_day_start() {
338 global $CFG;
339
340 $CFG->timezone = 0;
341 $this->assertEquals(1272758400, stats_get_next_day_start(1272686410));
342 }
343
344 /**
345 * Test the function that gets the action names
346 *
347 * Note: The function results depend on installed modules. The hard coded lists are the
348 * defaults for a new Moodle 2.3 install.
349 */
350 public function test_statslib_get_action_names() {
351 $basepostactions = array (
352 0 => 'add',
353 1 => 'delete',
354 2 => 'edit',
355 3 => 'add mod',
356 4 => 'delete mod',
357 5 => 'edit sectionenrol',
358 6 => 'loginas',
359 7 => 'new',
360 8 => 'unenrol',
361 9 => 'update',
362 10 => 'update mod',
363 11 => 'upload',
364 12 => 'submit',
365 13 => 'submit for grading',
366 14 => 'talk',
367 15 => 'choose',
368 16 => 'choose again',
369 17 => 'record delete',
370 18 => 'add discussion',
371 19 => 'add post',
372 20 => 'delete discussion',
373 21 => 'delete post',
374 22 => 'move discussion',
375 23 => 'prune post',
376 24 => 'update post',
377 25 => 'add category',
378 26 => 'add entry',
379 27 => 'approve entry',
380 28 => 'delete category',
381 29 => 'delete entry',
382 30 => 'edit category',
383 31 => 'update entry',
384 32 => 'end',
385 33 => 'start',
386 34 => 'attempt',
387 35 => 'close attempt',
388 36 => 'preview',
389 37 => 'editquestions',
390 38 => 'delete attempt',
391 39 => 'manualgrade',
392 );
393
394 $baseviewactions = array (
395 0 => 'view',
396 1 => 'view all',
397 2 => 'history',
398 3 => 'view submission',
399 4 => 'view feedback',
400 5 => 'print',
401 6 => 'report',
402 7 => 'view discussion',
403 8 => 'search',
404 9 => 'forum',
405 10 => 'forums',
406 11 => 'subscribers',
407 12 => 'view forum',
408 13 => 'view entry',
409 14 => 'review',
410 15 => 'pre-view',
411 16 => 'download',
412 17 => 'view form',
413 18 => 'view graph',
414 19 => 'view report',
415 );
416
417 $postactions = stats_get_action_names('post');
418
419 foreach ($basepostactions as $action) {
420 $this->assertContains($action, $postactions);
421 }
422
423 $viewactions = stats_get_action_names('view');
424
425 foreach ($baseviewactions as $action) {
426 $this->assertContains($action, $viewactions);
427 }
428 }
429
430 /**
431 * Test the temporary table creation and deletion.
432 */
433 public function test_statslib_temp_table_create_and_drop() {
434 global $DB;
435
436 foreach ($this->tables as $table) {
437 $this->assertFalse($DB->get_manager()->table_exists($table));
438 }
439
440 stats_temp_table_create();
441
442 foreach ($this->tables as $table) {
443 $this->assertTrue($DB->get_manager()->table_exists($table));
444 }
445
446 stats_temp_table_drop();
447
448 foreach ($this->tables as $table) {
449 $this->assertFalse($DB->get_manager()->table_exists($table));
450 }
451 }
452
453 /**
454 * Test the temporary table creation and deletion.
455 *
456 * @depends test_statslib_temp_table_create_and_drop
457 */
458 public function test_statslib_temp_table_fill() {
459 global $DB;
460
461 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml");
462
463 $this->prepare_db($dataset[0], array('log'));
464
465 stats_temp_table_create();
466 stats_temp_table_fill(1272686410, 1272758400);
467
468 $this->assertEquals(1, $DB->count_records('temp_log1'));
469 $this->assertEquals(1, $DB->count_records('temp_log2'));
470
471 stats_temp_table_drop();
472 }
473
474 /**
475 * Test the temporary table creation and deletion.
476 *
477 * @depends test_statslib_temp_table_create_and_drop
478 */
479 public function test_statslib_temp_table_setup() {
480 global $DB;
481
482 $logs = array();
483 $this->prepare_db($logs, array('log'));
484
485 stats_temp_table_create();
486 stats_temp_table_setup();
487
488 $this->assertEquals(1, $DB->count_records('temp_enroled'));
489
490 stats_temp_table_drop();
491 }
492
493 /**
494 * Test the function that clean out the temporary tables.
495 *
496 * @depends test_statslib_temp_table_create_and_drop
497 */
498 public function test_statslib_temp_table_clean() {
499 global $DB;
500
501 $rows = array(
502 'temp_log1' => array('id' => 1, 'course' => 1),
503 'temp_log2' => array('id' => 1, 'course' => 1),
504 'temp_stats_daily' => array('id' => 1, 'courseid' => 1),
505 'temp_stats_user_daily' => array('id' => 1, 'courseid' => 1),
506 );
507
508 stats_temp_table_create();
509
510 foreach ($rows as $table => $row) {
511 $DB->insert_record_raw($table, $row);
512 $this->assertEquals(1, $DB->count_records($table));
513 }
514
515 stats_temp_table_clean();
516
517 foreach ($rows as $table => $row) {
518 $this->assertEquals(0, $DB->count_records($table));
519 }
520
521 $this->assertEquals(1, $DB->count_records('stats_daily'));
522 $this->assertEquals(1, $DB->count_records('stats_user_daily'));
523
524 stats_temp_table_drop();
525 }
526
527 /**
528 * Test the daily stats function
529 *
530 * @depends test_statslib_get_base_daily
531 * @depends test_statslib_get_next_day_start
532 * @depends test_statslib_temp_table_create_and_drop
533 * @depends test_statslib_temp_table_setup
534 * @depends test_statslib_temp_table_fill
535 * @dataProvider daily_log_provider
536 */
537 public function test_statslib_cron_daily($logs, $stats) {
538 global $CFG;
539
540 $CFG->debug = DEBUG_NONE;
541
542 $this->prepare_db($logs, array('log'));
543
544 // Stats cron daily uses mtrace, turn on buffering to silence output.
545 ob_start();
546 stats_cron_daily(1);
547 ob_end_clean();
548
549 $this->verify_stats($stats);
550 }
551
552 /**
553 * Test the daily stats function
554 * @depends test_statslib_get_base_daily
555 * @depends test_statslib_get_next_day_start
556 */
557 public function test_statslib_cron_daily_no_default_profile_id() {
558 global $CFG, $DB;
559 $CFG->defaultfrontpageroleid = 0;
560
561 $course1 = $DB->get_record('course', array('shortname' => 'course1'));
562 $guestid = $CFG->siteguest;
563 $start = stats_get_base_daily(1272758400);
564 $end = stats_get_next_day_start($start);
565 $fpid = (int) $CFG->defaultfrontpageroleid;
566 $gr = get_guest_role();
567
568 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test10.xml");
569
570 $CFG->debug = DEBUG_NONE;
571
572 $this->prepare_db($dataset[0], array('log'));
573
574 // Stats cron daily uses mtrace, turn on buffering to silence output.
575 ob_start();
576 stats_cron_daily($maxdays=1);
577 ob_end_clean();
578
579 $this->verify_stats($dataset[1]);
580 }
581}