[ Index ] |
PHP Cross Reference of Unnamed Project |
[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 $this->setTimezone(self::TIMEZONE); 61 core_date::set_default_server_timezone(); 62 $CFG->statsfirstrun = 'all'; 63 $CFG->statslastdaily = 0; 64 $CFG->statslastexecution = 0; 65 66 // Figure out the broken day start so I can figure out when to the start time should be. 67 $time = time(); 68 // This nonsense needs to be rewritten. 69 $date = new DateTime('now', core_date::get_server_timezone_object()); 70 $offset = $date->getOffset(); 71 $stime = $time + $offset; 72 $stime = intval($stime / (60*60*24)) * 60*60*24; 73 $stime -= $offset; 74 75 $shour = intval(($time - $stime) / (60*60)); 76 77 $CFG->statsruntimestarthour = $shour; 78 $CFG->statsruntimestartminute = 0; 79 80 if ($DB->record_exists('user', array('username' => 'user1'))) { 81 return; 82 } 83 84 // Set up the database. 85 $datagen = self::getDataGenerator(); 86 87 $user1 = $datagen->create_user(array('username'=>'user1')); 88 $user2 = $datagen->create_user(array('username'=>'user2')); 89 90 $course1 = $datagen->create_course(array('shortname'=>'course1')); 91 $datagen->enrol_user($user1->id, $course1->id); 92 93 $this->generate_replacement_list(); 94 95 // Reset between tests. 96 $this->resetAfterTest(); 97 } 98 99 /** 100 * Function to setup database. 101 * 102 * @param array $dataset An array of tables including the log table. 103 * @param array $tables 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 protected function generate_replacement_list() { 131 global $CFG, $DB; 132 133 if ($this->replacements !== null) { 134 return; 135 } 136 137 $this->setTimezone(self::TIMEZONE); 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 $studentrole = $DB->get_record('role', array('shortname' => 'student')); 158 159 $this->replacements = array( 160 // Start and end times. 161 '[start_0]' => $start - 14410, // 4 hours before. 162 '[start_1]' => $start + 14410, // 4 hours after. 163 '[start_2]' => $start + 14420, 164 '[start_3]' => $start + 14430, 165 '[start_4]' => $start + 100800, // 28 hours after. 166 '[end]' => stats_get_next_day_start($start), 167 '[end_no_logs]' => stats_get_next_day_start($startnolog), 168 169 // User ids. 170 '[guest_id]' => $guest->id, 171 '[user1_id]' => $user1->id, 172 '[user2_id]' => $user2->id, 173 174 // Course ids. 175 '[course1_id]' => $course1->id, 176 '[site_id]' => SITEID, 177 178 // Role ids. 179 '[frontpage_roleid]' => (int) $CFG->defaultfrontpageroleid, 180 '[guest_roleid]' => $gr->id, 181 '[student_roleid]' => $studentrole->id, 182 ); 183 } 184 185 /** 186 * Load dataset from XML file. 187 * 188 * @param string $file The name of the file to load 189 * @return array 190 */ 191 protected function load_xml_data_file($file) { 192 static $replacements = null; 193 194 $raw = $this->createXMLDataSet($file); 195 $clean = new PHPUnit_Extensions_Database_DataSet_ReplacementDataSet($raw); 196 197 foreach ($this->replacements as $placeholder => $value) { 198 $clean->addFullReplacement($placeholder, $value); 199 } 200 201 $logs = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean); 202 $logs->addIncludeTables(array('log')); 203 204 $stats = new PHPUnit_Extensions_Database_DataSet_DataSetFilter($clean); 205 $stats->addIncludeTables(array('stats_daily', 'stats_user_daily')); 206 207 return array($logs, $stats); 208 } 209 210 /** 211 * Provides the log data for test_statslib_cron_daily. 212 * 213 * @return array of fixture XML log file names. 214 */ 215 public function daily_log_provider() { 216 $logfiles = array(); 217 $fileno = array('00', '01', '02', '03', '04', '05', '06', '07', '08'); 218 219 foreach ($fileno as $no) { 220 $logfiles[] = array("statslib-test{$no}.xml"); 221 } 222 223 return $logfiles; 224 } 225 226 /** 227 * Compare the expected stats to those in the database. 228 * 229 * @param array $expected 230 * @param string $output 231 */ 232 protected function verify_stats($expected, $output = '') { 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 $message = 'Incorrect number of results returned for '. $type; 244 245 if ($output != '') { 246 $message .= "\nCron output:\n$output"; 247 } 248 249 $this->assertCount($rows, $records, $message); 250 251 for ($i = 0; $i < $rows; $i++) { 252 $row = $table->getRow($i); 253 $found = 0; 254 255 foreach ($records as $key => $record) { 256 $record = (array) $record; 257 unset($record['id']); 258 $diff = array_merge(array_diff_assoc($row, $record), 259 array_diff_assoc($record, $row)); 260 261 if (empty($diff)) { 262 $found = $key; 263 break; 264 } 265 } 266 267 $this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true) 268 ." was not found in $type ". var_export($records, true)); 269 unset($records[$found]); 270 } 271 } 272 } 273 274 /** 275 * Test progress output when debug is on. 276 */ 277 public function test_statslib_progress_debug() { 278 set_debugging(DEBUG_ALL); 279 $this->expectOutputString('1:0 '); 280 stats_progress('init'); 281 stats_progress('1'); 282 $this->resetDebugging(); 283 } 284 285 /** 286 * Test progress output when debug is off. 287 */ 288 public function test_statslib_progress_no_debug() { 289 set_debugging(DEBUG_NONE); 290 $this->expectOutputString('.'); 291 stats_progress('init'); 292 stats_progress('1'); 293 $this->resetDebugging(); 294 } 295 296 /** 297 * Test the function that gets the start date from the config. 298 */ 299 public function test_statslib_get_start_from() { 300 global $CFG, $DB; 301 302 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml"); 303 $DB->delete_records('log'); 304 305 $date = new DateTime('now', core_date::get_server_timezone_object()); 306 $day = self::DAY - $date->getOffset(); 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 + DAYSECS, 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 < 13; $x += 1) { 378 $this->setTimezone($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 $this->setTimezone(0); 394 $this->assertEquals(1272758400, stats_get_next_day_start(1272686410)); 395 } 396 397 /** 398 * Test the function that gets the action names. 399 * 400 * Note: The function results depend on installed modules. The hard coded lists are the 401 * defaults for a new Moodle 2.3 install. 402 */ 403 public function test_statslib_get_action_names() { 404 $basepostactions = array ( 405 0 => 'add', 406 1 => 'delete', 407 2 => 'edit', 408 3 => 'add mod', 409 4 => 'delete mod', 410 5 => 'edit sectionenrol', 411 6 => 'loginas', 412 7 => 'new', 413 8 => 'unenrol', 414 9 => 'update', 415 10 => 'update mod', 416 11 => 'upload', 417 12 => 'submit', 418 13 => 'submit for grading', 419 14 => 'talk', 420 15 => 'choose', 421 16 => 'choose again', 422 17 => 'record delete', 423 18 => 'add discussion', 424 19 => 'add post', 425 20 => 'delete discussion', 426 21 => 'delete post', 427 22 => 'move discussion', 428 23 => 'prune post', 429 24 => 'update post', 430 25 => 'add category', 431 26 => 'add entry', 432 27 => 'approve entry', 433 28 => 'delete category', 434 29 => 'delete entry', 435 30 => 'edit category', 436 31 => 'update entry', 437 32 => 'end', 438 33 => 'start', 439 34 => 'attempt', 440 35 => 'close attempt', 441 36 => 'preview', 442 37 => 'editquestions', 443 38 => 'delete attempt', 444 39 => 'manualgrade', 445 ); 446 447 $baseviewactions = array ( 448 0 => 'view', 449 1 => 'view all', 450 2 => 'history', 451 3 => 'view submission', 452 4 => 'view feedback', 453 5 => 'print', 454 6 => 'report', 455 7 => 'view discussion', 456 8 => 'search', 457 9 => 'forum', 458 10 => 'forums', 459 11 => 'subscribers', 460 12 => 'view forum', 461 13 => 'view entry', 462 14 => 'review', 463 15 => 'pre-view', 464 16 => 'download', 465 17 => 'view form', 466 18 => 'view graph', 467 19 => 'view report', 468 ); 469 470 $postactions = stats_get_action_names('post'); 471 472 foreach ($basepostactions as $action) { 473 $this->assertContains($action, $postactions); 474 } 475 476 $viewactions = stats_get_action_names('view'); 477 478 foreach ($baseviewactions as $action) { 479 $this->assertContains($action, $viewactions); 480 } 481 } 482 483 /** 484 * Test the temporary table creation and deletion. 485 */ 486 public function test_statslib_temp_table_create_and_drop() { 487 global $DB; 488 489 foreach ($this->tables as $table) { 490 $this->assertFalse($DB->get_manager()->table_exists($table)); 491 } 492 493 stats_temp_table_create(); 494 495 foreach ($this->tables as $table) { 496 $this->assertTrue($DB->get_manager()->table_exists($table)); 497 } 498 499 stats_temp_table_drop(); 500 501 foreach ($this->tables as $table) { 502 $this->assertFalse($DB->get_manager()->table_exists($table)); 503 } 504 } 505 506 /** 507 * Test the temporary table creation and deletion. 508 * 509 * @depends test_statslib_temp_table_create_and_drop 510 */ 511 public function test_statslib_temp_table_fill() { 512 global $CFG, $DB, $USER; 513 514 $dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml"); 515 516 $this->prepare_db($dataset[0], array('log')); 517 518 // This nonsense needs to be rewritten. 519 $date = new DateTime('now', core_date::get_server_timezone_object()); 520 $start = self::DAY - $date->getOffset(); 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: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |