[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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_stats 21 * @category phpunit 22 * @copyright 2012 Tyler Bannister 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 global $CFG; 29 require_once($CFG->libdir . '/adminlib.php'); 30 require_once($CFG->libdir . '/statslib.php'); 31 require_once($CFG->libdir . '/cronlib.php'); 32 require_once (__DIR__ . '/fixtures/stats_events.php'); 33 34 /** 35 * Test functions that affect daily stats. 36 */ 37 class core_statslib_testcase extends advanced_testcase { 38 /** The day to use for testing **/ 39 const DAY = 1272672000; 40 41 /** The timezone to use for testing **/ 42 const TIMEZONE = 0; 43 44 /** @var array The list of temporary tables created for the statistic calculations **/ 45 protected $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily'); 46 47 /** @var array The replacements to be used when loading XML files **/ 48 protected $replacements = null; 49 50 51 /** 52 * Setup function 53 * - Allow changes to CFG->debug for testing purposes. 54 */ 55 protected function setUp() { 56 global $CFG, $DB; 57 parent::setUp(); 58 59 // Settings to force statistic to run during testing. 60 $CFG->timezone = self::TIMEZONE; 61 $CFG->statsfirstrun = 'all'; 62 $CFG->statslastdaily = 0; 63 $CFG->statslastexecution = 0; 64 65 // Figure out the broken day start so I can figure out when to the start time should be. 66 $time = time(); 67 $offset = get_timezone_offset($CFG->timezone); 68 $stime = $time + $offset; 69 $stime = intval($stime / (60*60*24)) * 60*60*24; 70 $stime -= $offset; 71 72 $shour = intval(($time - $stime) / (60*60)); 73 74 $CFG->statsruntimestarthour = $shour; 75 $CFG->statsruntimestartminute = 0; 76 77 if ($DB->record_exists('user', array('username' => 'user1'))) { 78 return; 79 } 80 81 // Set up the database. 82 $datagen = self::getDataGenerator(); 83 84 $user1 = $datagen->create_user(array('username'=>'user1')); 85 $user2 = $datagen->create_user(array('username'=>'user2')); 86 87 $course1 = $datagen->create_course(array('shortname'=>'course1')); 88 $datagen->enrol_user($user1->id, $course1->id); 89 90 $this->generate_replacement_list(); 91 92 // Reset between tests. 93 $this->resetAfterTest(); 94 } 95 96 /** 97 * Function to setup database. 98 * 99 * @param array $dataset An array of tables including the log table. 100 * @param array $tables 101 */ 102 protected function prepare_db($dataset, $tables) { 103 global $DB; 104 105 foreach ($tables as $tablename) { 106 $DB->delete_records($tablename); 107 108 foreach ($dataset as $name => $table) { 109 110 if ($tablename == $name) { 111 112 $rows = $table->getRowCount(); 113 114 for ($i = 0; $i < $rows; $i++) { 115 $row = $table->getRow($i); 116 117 $DB->insert_record($tablename, $row, false, true); 118 } 119 } 120 } 121 } 122 } 123 124 /** 125 * Load dataset from XML file. 126 */ 127 protected function generate_replacement_list() { 128 global $CFG, $DB; 129 130 if ($this->replacements !== null) { 131 return; 132 } 133 134 $CFG->timezone = self::TIMEZONE; 135 136 $guest = $DB->get_record('user', array('id' => $CFG->siteguest)); 137 $user1 = $DB->get_record('user', array('username' => 'user1')); 138 $user2 = $DB->get_record('user', array('username' => 'user2')); 139 140 if (($guest === false) || ($user1 === false) || ($user2 === false)) { 141 trigger_error('User setup incomplete', E_USER_ERROR); 142 } 143 144 $site = $DB->get_record('course', array('id' => SITEID)); 145 $course1 = $DB->get_record('course', array('shortname' => 'course1')); 146 147 if (($site === false) || ($course1 === false)) { 148 trigger_error('Course setup incomplete', E_USER_ERROR); 149 } 150 151 $offset = get_timezone_offset($CFG->timezone); 152 153 $start = stats_get_base_daily(self::DAY + 3600); 154 $startnolog = stats_get_base_daily(stats_get_start_from('daily')); 155 $gr = get_guest_role(); 156 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 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]' => $studentrole->id, 181 ); 182 } 183 184 /** 185 * Load dataset from XML file. 186 * 187 * @param string $file The name of the file to load 188 * @return array 189 */ 190 protected function load_xml_data_file($file) { 191 static $replacements = null; 192 193 $raw = $this->createXMLDataSet($file); 194 $clean = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($raw); 195 196 foreach ($this->replacements as $placeholder => $value) { 197 $clean->addFullReplacement($placeholder, $value); 198 } 199 200 $logs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean); 201 $logs->addIncludeTables(array('log')); 202 203 $stats = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean); 204 $stats->addIncludeTables(array('stats_daily', 'stats_user_daily')); 205 206 return array($logs, $stats); 207 } 208 209 /** 210 * Provides the log data for test_statslib_cron_daily. 211 * 212 * @return array of fixture XML log file names. 213 */ 214 public function daily_log_provider() { 215 $logfiles = array(); 216 $fileno = array('00', '01', '02', '03', '04', '05', '06', '07', '08'); 217 218 foreach ($fileno as $no) { 219 $logfiles[] = array("statslib-test{$no}.xml"); 220 } 221 222 return $logfiles; 223 } 224 225 /** 226 * Compare the expected stats to those in the database. 227 * 228 * @param array $expected 229 * @param string $output 230 */ 231 protected function verify_stats($expected, $output = '') { 232 global $DB; 233 234 // Note: We can not use $this->assertDataSetEqual($expected, $actual) because there's no 235 // $this->getConnection() in advanced_testcase. 236 237 foreach ($expected as $type => $table) { 238 $records = $DB->get_records($type); 239 240 $rows = $table->getRowCount(); 241 242 $message = 'Incorrect number of results returned for '. $type; 243 244 if ($output != '') { 245 $message .= "\nCron output:\n$output"; 246 } 247 248 $this->assertCount($rows, $records, $message); 249 250 for ($i = 0; $i < $rows; $i++) { 251 $row = $table->getRow($i); 252 $found = 0; 253 254 foreach ($records as $key => $record) { 255 $record = (array) $record; 256 unset($record['id']); 257 $diff = array_merge(array_diff_assoc($row, $record), 258 array_diff_assoc($record, $row)); 259 260 if (empty($diff)) { 261 $found = $key; 262 break; 263 } 264 } 265 266 $this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true) 267 ." was not found in $type ". var_export($records, true)); 268 unset($records[$found]); 269 } 270 } 271 } 272 273 /** 274 * Test progress output when debug is on. 275 */ 276 public function test_statslib_progress_debug() { 277 set_debugging(DEBUG_ALL); 278 $this->expectOutputString('1:0 '); 279 stats_progress('init'); 280 stats_progress('1'); 281 $this->resetDebugging(); 282 } 283 284 /** 285 * Test progress output when debug is off. 286 */ 287 public function test_statslib_progress_no_debug() { 288 set_debugging(DEBUG_NONE); 289 $this->expectOutputString('.'); 290 stats_progress('init'); 291 stats_progress('1'); 292 $this->resetDebugging(); 293 } 294 295 /** 296 * Test the function that gets the start date from the config. 297 */ 298 public function test_statslib_get_start_from() { 299 global $CFG, $DB; 300 301 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml"); 302 $time = time(); 303 $DB->delete_records('log'); 304 305 // Don't ask. I don't think get_timezone_offset works correctly. 306 $day = self::DAY - get_timezone_offset($CFG->timezone); 307 308 $CFG->statsfirstrun = 'all'; 309 // Allow 1 second difference in case we cross a second boundary. 310 // Note: within 3 days of a DST change - -3 days != 3 * 24 hours (it may be more or less). 311 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - strtotime('-3 days', $time), 'All start time'); 312 313 $this->prepare_db($dataset[0], array('log')); 314 $records = $DB->get_records('log'); 315 316 $this->assertEquals($day + 14410, stats_get_start_from('daily'), 'Log entry start'); 317 318 $CFG->statsfirstrun = 'none'; 319 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - strtotime('-3 days', $time), 'None start time'); 320 321 $CFG->statsfirstrun = 14515200; 322 $this->assertLessThanOrEqual(1, stats_get_start_from('daily') - ($time - (14515200)), 'Specified start time'); 323 324 $this->prepare_db($dataset[1], array('stats_daily')); 325 $this->assertEquals($day + (24 * 3600), stats_get_start_from('daily'), 'Daily stats start time'); 326 327 // New log stores. 328 $this->preventResetByRollback(); 329 330 $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php"); 331 set_config('enabled_stores', 'logstore_standard', 'tool_log'); 332 set_config('buffersize', 0, 'logstore_standard'); 333 set_config('logguests', 1, 'logstore_standard'); 334 get_log_manager(true); 335 336 $this->assertEquals(0, $DB->count_records('logstore_standard_log')); 337 338 $DB->delete_records('stats_daily'); 339 $CFG->statsfirstrun = 'all'; 340 $firstoldtime = $DB->get_field_sql('SELECT MIN(time) FROM {log}'); 341 342 $this->assertEquals($firstoldtime, stats_get_start_from('daily')); 343 344 \core_tests\event\create_executed::create(array('context' => context_system::instance()))->trigger(); 345 \core_tests\event\read_executed::create(array('context' => context_system::instance()))->trigger(); 346 \core_tests\event\update_executed::create(array('context' => context_system::instance()))->trigger(); 347 \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); 348 349 // Fake the origin of events. 350 $DB->set_field('logstore_standard_log', 'origin', 'web', array()); 351 352 $logs = $DB->get_records('logstore_standard_log'); 353 $this->assertCount(4, $logs); 354 355 $firstnew = reset($logs); 356 $this->assertGreaterThan($firstoldtime, $firstnew->timecreated); 357 $DB->set_field('logstore_standard_log', 'timecreated', 10, array('id' => $firstnew->id)); 358 $this->assertEquals(10, stats_get_start_from('daily')); 359 360 $DB->set_field('logstore_standard_log', 'timecreated', $firstnew->timecreated, array('id' => $firstnew->id)); 361 $DB->delete_records('log'); 362 $this->assertEquals($firstnew->timecreated, stats_get_start_from('daily')); 363 364 set_config('enabled_stores', '', 'tool_log'); 365 get_log_manager(true); 366 } 367 368 /** 369 * Test the function that calculates the start of the day. 370 * 371 * NOTE: I don't think this is the way this function should work. 372 * This test documents the current functionality. 373 */ 374 public function test_statslib_get_base_daily() { 375 global $CFG; 376 377 for ($x = 0; $x < 24; $x += 1) { 378 $CFG->timezone = $x; 379 380 $start = 1272672000 - ($x * 3600); 381 if ($x >= 20) { 382 $start += (24 * 3600); 383 } 384 385 $this->assertEquals($start, stats_get_base_daily(1272686410), "Timezone $x check"); 386 } 387 } 388 389 /** 390 * Test the function that gets the start of the next day. 391 */ 392 public function test_statslib_get_next_day_start() { 393 global $CFG; 394 395 $CFG->timezone = 0; 396 $this->assertEquals(1272758400, stats_get_next_day_start(1272686410)); 397 } 398 399 /** 400 * Test the function that gets the action names. 401 * 402 * Note: The function results depend on installed modules. The hard coded lists are the 403 * defaults for a new Moodle 2.3 install. 404 */ 405 public function test_statslib_get_action_names() { 406 $basepostactions = array ( 407 0 => 'add', 408 1 => 'delete', 409 2 => 'edit', 410 3 => 'add mod', 411 4 => 'delete mod', 412 5 => 'edit sectionenrol', 413 6 => 'loginas', 414 7 => 'new', 415 8 => 'unenrol', 416 9 => 'update', 417 10 => 'update mod', 418 11 => 'upload', 419 12 => 'submit', 420 13 => 'submit for grading', 421 14 => 'talk', 422 15 => 'choose', 423 16 => 'choose again', 424 17 => 'record delete', 425 18 => 'add discussion', 426 19 => 'add post', 427 20 => 'delete discussion', 428 21 => 'delete post', 429 22 => 'move discussion', 430 23 => 'prune post', 431 24 => 'update post', 432 25 => 'add category', 433 26 => 'add entry', 434 27 => 'approve entry', 435 28 => 'delete category', 436 29 => 'delete entry', 437 30 => 'edit category', 438 31 => 'update entry', 439 32 => 'end', 440 33 => 'start', 441 34 => 'attempt', 442 35 => 'close attempt', 443 36 => 'preview', 444 37 => 'editquestions', 445 38 => 'delete attempt', 446 39 => 'manualgrade', 447 ); 448 449 $baseviewactions = array ( 450 0 => 'view', 451 1 => 'view all', 452 2 => 'history', 453 3 => 'view submission', 454 4 => 'view feedback', 455 5 => 'print', 456 6 => 'report', 457 7 => 'view discussion', 458 8 => 'search', 459 9 => 'forum', 460 10 => 'forums', 461 11 => 'subscribers', 462 12 => 'view forum', 463 13 => 'view entry', 464 14 => 'review', 465 15 => 'pre-view', 466 16 => 'download', 467 17 => 'view form', 468 18 => 'view graph', 469 19 => 'view report', 470 ); 471 472 $postactions = stats_get_action_names('post'); 473 474 foreach ($basepostactions as $action) { 475 $this->assertContains($action, $postactions); 476 } 477 478 $viewactions = stats_get_action_names('view'); 479 480 foreach ($baseviewactions as $action) { 481 $this->assertContains($action, $viewactions); 482 } 483 } 484 485 /** 486 * Test the temporary table creation and deletion. 487 */ 488 public function test_statslib_temp_table_create_and_drop() { 489 global $DB; 490 491 foreach ($this->tables as $table) { 492 $this->assertFalse($DB->get_manager()->table_exists($table)); 493 } 494 495 stats_temp_table_create(); 496 497 foreach ($this->tables as $table) { 498 $this->assertTrue($DB->get_manager()->table_exists($table)); 499 } 500 501 stats_temp_table_drop(); 502 503 foreach ($this->tables as $table) { 504 $this->assertFalse($DB->get_manager()->table_exists($table)); 505 } 506 } 507 508 /** 509 * Test the temporary table creation and deletion. 510 * 511 * @depends test_statslib_temp_table_create_and_drop 512 */ 513 public function test_statslib_temp_table_fill() { 514 global $CFG, $DB, $USER; 515 516 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml"); 517 518 $this->prepare_db($dataset[0], array('log')); 519 520 $start = self::DAY - get_timezone_offset($CFG->timezone); 521 $end = $start + (24 * 3600); 522 523 stats_temp_table_create(); 524 stats_temp_table_fill($start, $end); 525 526 $this->assertEquals(1, $DB->count_records('temp_log1')); 527 $this->assertEquals(1, $DB->count_records('temp_log2')); 528 529 stats_temp_table_drop(); 530 531 // New log stores. 532 $this->preventResetByRollback(); 533 stats_temp_table_create(); 534 535 $course = $this->getDataGenerator()->create_course(); 536 $context = context_course::instance($course->id); 537 $fcontext = context_course::instance(SITEID); 538 $user = $this->getDataGenerator()->create_user(); 539 $this->setUser($user); 540 541 $this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php"); 542 set_config('enabled_stores', 'logstore_standard', 'tool_log'); 543 set_config('buffersize', 0, 'logstore_standard'); 544 set_config('logguests', 1, 'logstore_standard'); 545 get_log_manager(true); 546 547 $DB->delete_records('logstore_standard_log'); 548 549 \core_tests\event\create_executed::create(array('context' => $fcontext, 'courseid' => SITEID))->trigger(); 550 \core_tests\event\read_executed::create(array('context' => $context, 'courseid' => $course->id))->trigger(); 551 \core_tests\event\update_executed::create(array('context' => context_system::instance()))->trigger(); 552 \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); 553 554 \core\event\user_loggedin::create( 555 array( 556 'userid' => $USER->id, 557 'objectid' => $USER->id, 558 'other' => array('username' => $USER->username), 559 ) 560 )->trigger(); 561 562 $DB->set_field('logstore_standard_log', 'timecreated', 10); 563 564 $this->assertEquals(5, $DB->count_records('logstore_standard_log')); 565 566 \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); 567 \core_tests\event\delete_executed::create(array('context' => context_system::instance()))->trigger(); 568 569 // Fake the origin of events. 570 $DB->set_field('logstore_standard_log', 'origin', 'web', array()); 571 572 $this->assertEquals(7, $DB->count_records('logstore_standard_log')); 573 574 stats_temp_table_fill(9, 11); 575 576 $logs1 = $DB->get_records('temp_log1'); 577 $logs2 = $DB->get_records('temp_log2'); 578 $this->assertCount(5, $logs1); 579 $this->assertCount(5, $logs2); 580 // The order of records in the temp tables is not guaranteed... 581 582 $viewcount = 0; 583 $updatecount = 0; 584 $logincount = 0; 585 foreach ($logs1 as $log) { 586 if ($log->course == $course->id) { 587 $this->assertEquals('view', $log->action); 588 $viewcount++; 589 } else { 590 $this->assertTrue(in_array($log->action, array('update', 'login'))); 591 if ($log->action === 'update') { 592 $updatecount++; 593 } else { 594 $logincount++; 595 } 596 $this->assertEquals(SITEID, $log->course); 597 } 598 $this->assertEquals($user->id, $log->userid); 599 } 600 $this->assertEquals(1, $viewcount); 601 $this->assertEquals(3, $updatecount); 602 $this->assertEquals(1, $logincount); 603 604 set_config('enabled_stores', '', 'tool_log'); 605 get_log_manager(true); 606 stats_temp_table_drop(); 607 } 608 609 /** 610 * Test the temporary table creation and deletion. 611 * 612 * @depends test_statslib_temp_table_create_and_drop 613 */ 614 public function test_statslib_temp_table_setup() { 615 global $DB; 616 617 $logs = array(); 618 $this->prepare_db($logs, array('log')); 619 620 stats_temp_table_create(); 621 stats_temp_table_setup(); 622 623 $this->assertEquals(1, $DB->count_records('temp_enroled')); 624 625 stats_temp_table_drop(); 626 } 627 628 /** 629 * Test the function that clean out the temporary tables. 630 * 631 * @depends test_statslib_temp_table_create_and_drop 632 */ 633 public function test_statslib_temp_table_clean() { 634 global $DB; 635 636 $rows = array( 637 'temp_log1' => array('id' => 1, 'course' => 1), 638 'temp_log2' => array('id' => 1, 'course' => 1), 639 'temp_stats_daily' => array('id' => 1, 'courseid' => 1), 640 'temp_stats_user_daily' => array('id' => 1, 'courseid' => 1), 641 ); 642 643 stats_temp_table_create(); 644 645 foreach ($rows as $table => $row) { 646 $DB->insert_record_raw($table, $row); 647 $this->assertEquals(1, $DB->count_records($table)); 648 } 649 650 stats_temp_table_clean(); 651 652 foreach ($rows as $table => $row) { 653 $this->assertEquals(0, $DB->count_records($table)); 654 } 655 656 $this->assertEquals(1, $DB->count_records('stats_daily')); 657 $this->assertEquals(1, $DB->count_records('stats_user_daily')); 658 659 stats_temp_table_drop(); 660 } 661 662 /** 663 * Test the daily stats function. 664 * 665 * @depends test_statslib_get_base_daily 666 * @depends test_statslib_get_next_day_start 667 * @depends test_statslib_get_start_from 668 * @depends test_statslib_temp_table_create_and_drop 669 * @depends test_statslib_temp_table_setup 670 * @depends test_statslib_temp_table_fill 671 * @dataProvider daily_log_provider 672 */ 673 public function test_statslib_cron_daily($xmlfile) { 674 global $CFG, $DB; 675 676 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/{$xmlfile}"); 677 678 list($logs, $stats) = $dataset; 679 $this->prepare_db($logs, array('log')); 680 681 // Stats cron daily uses mtrace, turn on buffering to silence output. 682 ob_start(); 683 stats_cron_daily(1); 684 $output = ob_get_contents(); 685 ob_end_clean(); 686 687 $this->verify_stats($stats, $output); 688 } 689 690 /** 691 * Test the daily stats function. 692 * 693 * @depends test_statslib_get_base_daily 694 * @depends test_statslib_get_next_day_start 695 */ 696 public function test_statslib_cron_daily_no_default_profile_id() { 697 global $CFG, $DB; 698 $CFG->defaultfrontpageroleid = 0; 699 700 $course1 = $DB->get_record('course', array('shortname' => 'course1')); 701 $guestid = $CFG->siteguest; 702 $start = stats_get_base_daily(1272758400); 703 $end = stats_get_next_day_start($start); 704 $fpid = (int) $CFG->defaultfrontpageroleid; 705 $gr = get_guest_role(); 706 707 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test10.xml"); 708 709 $this->prepare_db($dataset[0], array('log')); 710 711 // Stats cron daily uses mtrace, turn on buffering to silence output. 712 ob_start(); 713 stats_cron_daily($maxdays=1); 714 $output = ob_get_contents(); 715 ob_end_clean(); 716 717 $this->verify_stats($dataset[1], $output); 718 } 719 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |