Rev 1 | Ir a la última revisión | Autoría | Comparar con el anterior | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>.namespace core;defined('MOODLE_INTERNAL') || die();global $CFG;require_once($CFG->libdir . '/adminlib.php');require_once($CFG->libdir . '/statslib.php');require_once(__DIR__ . '/fixtures/stats_events.php');/*** Test functions that affect daily stats.** @package core* @category test* @copyright 2012 Tyler Bannister* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class statslib_test extends \advanced_testcase {/** The day to use for testing **/const DAY = 1272672000;/** The timezone to use for testing **/const TIMEZONE = 0;/** @var array The list of temporary tables created for the statistic calculations **/protected $tables = array('temp_log1', 'temp_log2', 'temp_stats_daily', 'temp_stats_user_daily');/** @var array The replacements to be used when loading XML files **/protected $replacements = null;/*** Setup function* - Allow changes to CFG->debug for testing purposes.*/protected function setUp(): void {global $CFG, $DB;parent::setUp();// Settings to force statistic to run during testing.$this->setTimezone(self::TIMEZONE);\core_date::set_default_server_timezone();$CFG->statsfirstrun = 'all';$CFG->statslastdaily = 0;// Figure out the broken day start so I can figure out when to the start time should be.$time = time();// This nonsense needs to be rewritten.$date = new \DateTime('now', \core_date::get_server_timezone_object());$offset = $date->getOffset();$stime = $time + $offset;$stime = intval($stime / (60*60*24)) * 60*60*24;$stime -= $offset;$shour = intval(($time - $stime) / (60*60));if ($DB->record_exists('user', array('username' => 'user1'))) {return;}// Set up the database.$datagen = self::getDataGenerator();$user1 = $datagen->create_user(array('username'=>'user1'));$user2 = $datagen->create_user(array('username'=>'user2'));$course1 = $datagen->create_course(array('shortname'=>'course1'));$datagen->enrol_user($user1->id, $course1->id);$this->generate_replacement_list();// Reset between tests.$this->resetAfterTest();}/*** Function to setup database.** @param phpunit_dataset $dataset Containing all the information loaded from fixtures.* @param array $filter Tables to be sent to database.*/protected function prepare_db($dataset, $tables) {global $DB;foreach ($tables as $tablename) {$DB->delete_records($tablename);$dataset->to_database([$tablename]);}}/*** Load dataset from XML file.*/protected function generate_replacement_list() {global $CFG, $DB;if ($this->replacements !== null) {return;}$this->setTimezone(self::TIMEZONE);$guest = $DB->get_record('user', array('id' => $CFG->siteguest));$user1 = $DB->get_record('user', array('username' => 'user1'));$user2 = $DB->get_record('user', array('username' => 'user2'));if (($guest === false) || ($user1 === false) || ($user2 === false)) {trigger_error('User setup incomplete', E_USER_ERROR);}$site = $DB->get_record('course', array('id' => SITEID));$course1 = $DB->get_record('course', array('shortname' => 'course1'));if (($site === false) || ($course1 === false)) {trigger_error('Course setup incomplete', E_USER_ERROR);}$start = stats_get_base_daily(self::DAY + 3600);$startnolog = stats_get_base_daily(stats_get_start_from('daily'));$gr = get_guest_role();$studentrole = $DB->get_record('role', array('shortname' => 'student'));$this->replacements = array(// Start and end times.'[start_0]' => $start - 14410, // 4 hours before.'[start_1]' => $start + 14410, // 4 hours after.'[start_2]' => $start + 14420,'[start_3]' => $start + 14430,'[start_4]' => $start + 100800, // 28 hours after.'[end]' => stats_get_next_day_start($start),'[end_no_logs]' => stats_get_next_day_start($startnolog),// User ids.'[guest_id]' => $guest->id,'[user1_id]' => $user1->id,'[user2_id]' => $user2->id,// Course ids.'[course1_id]' => $course1->id,'[site_id]' => SITEID,// Role ids.'[frontpage_roleid]' => (int) $CFG->defaultfrontpageroleid,'[guest_roleid]' => $gr->id,'[student_roleid]' => $studentrole->id,);}/*** Load dataset from XML file.** @param string $file The name of the file to load* @return phpunit_dataset*/protected function load_xml_data_file($file) {$xml = file_get_contents($file);// Apply all the replacements straight in xml.foreach ($this->replacements as $placeholder => $value) {$placeholder = preg_quote($placeholder, '/');$xml = preg_replace('/' . $placeholder . '/', $value, $xml);}return $this->dataset_from_string($xml, 'xml');}/*** Provides the log data for test_statslib_cron_daily.** @return array of fixture XML log file names.*/public function daily_log_provider() {$logfiles = array();$fileno = array('00', '01', '02', '03', '04', '05', '06', '07', '08');foreach ($fileno as $no) {$logfiles[] = array("statslib-test{$no}.xml");}return $logfiles;}/*** Set of data for test_statlibs_get_base_weekly** @return array Dates and timezones for which the first day of the week will be calculated*/public function get_base_weekly_provider() {return [["startwday" => 0,"timezone" => 'America/Chicago',"timestart" => '18-03-2017 22:00',"expected" => '12-03-2017 00:00:00'],["startwday" => 0,"timezone" => 'America/Chicago',"date" => '25-03-2017 22:00',"expected" => '19-03-2017 00:00:00'],["startwday" => 1,"timezone" => 'Atlantic/Canary',"date" => '06-08-2018 22:00',"expected" => '06-08-2018 00:00:00'],];}/*** Compare the expected stats to those in the database.** @param array $expected* @param string $output*/protected function verify_stats($expected, $output = '') {global $DB;// Note: We can not use $this->assertDataSetEqual($expected, $actual) because there's no// $this->getConnection() in advanced_testcase.foreach ($expected as $type => $table) {$records = $DB->get_records($type);$rows = count($table);$message = 'Incorrect number of results returned for '. $type;if ($output != '') {$message .= "\nCron output:\n$output";}$this->assertCount($rows, $records, $message);for ($i = 0; $i < $rows; $i++) {$row = $table[$i];$found = 0;foreach ($records as $key => $record) {$record = (array) $record;unset($record['id']);$diff = array_merge(array_diff_assoc($row, $record),array_diff_assoc($record, $row));if (empty($diff)) {$found = $key;break;}}$this->assertGreaterThan(0, $found, 'Expected log '. var_export($row, true)." was not found in $type ". var_export($records, true));unset($records[$found]);}}}/*** Test progress output when debug is on.*/public function test_statslib_progress_debug(): void {set_debugging(DEBUG_ALL);$this->expectOutputString('1:0 ');stats_progress('init');stats_progress('1');$this->resetDebugging();}/*** Test progress output when debug is off.*/public function test_statslib_progress_no_debug(): void {set_debugging(DEBUG_NONE);$this->expectOutputString('.');stats_progress('init');stats_progress('1');$this->resetDebugging();}/*** Test the function that gets the start date from the config.*/public function test_statslib_get_start_from(): void {global $CFG, $DB;$dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test01.xml");$DB->delete_records('log');$date = new \DateTime('now', \core_date::get_server_timezone_object());$day = self::DAY - $date->getOffset();$CFG->statsfirstrun = 'all';// Allow 1 second difference in case we cross a second boundary.// Note: within 3 days of a DST change - -3 days != 3 * 24 hours (it may be more or less).$this->assertLessThanOrEqual(1, stats_get_start_from('daily') - strtotime('-3 days', time()), 'All start time');$this->prepare_db($dataset, array('log'));$records = $DB->get_records('log');$this->assertEquals($day + 14410, stats_get_start_from('daily'), 'Log entry start');$CFG->statsfirstrun = 'none';$this->assertLessThanOrEqual(1, stats_get_start_from('daily') - strtotime('-3 days', time()), 'None start time');$CFG->statsfirstrun = 14515200;$this->assertLessThanOrEqual(1, stats_get_start_from('daily') - (time() - (14515200)), 'Specified start time');$this->prepare_db($dataset, array('stats_daily'));$this->assertEquals($day + DAYSECS, stats_get_start_from('daily'), 'Daily stats start time');// New log stores.$this->preventResetByRollback();$this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php");set_config('enabled_stores', 'logstore_standard', 'tool_log');set_config('buffersize', 0, 'logstore_standard');set_config('logguests', 1, 'logstore_standard');get_log_manager(true);$this->assertEquals(0, $DB->count_records('logstore_standard_log'));$DB->delete_records('stats_daily');$CFG->statsfirstrun = 'all';$firstoldtime = $DB->get_field_sql('SELECT MIN(time) FROM {log}');$this->assertEquals($firstoldtime, stats_get_start_from('daily'));$time = time() - 5;\core_tests\event\create_executed::create(array('context' => \context_system::instance()))->trigger();$DB->set_field('logstore_standard_log', 'timecreated', $time++, ['eventname' => '\\core_tests\\event\\create_executed',]);\core_tests\event\read_executed::create(array('context' => \context_system::instance()))->trigger();$DB->set_field('logstore_standard_log', 'timecreated', $time++, ['eventname' => '\\core_tests\\event\\read_executed',]);\core_tests\event\update_executed::create(array('context' => \context_system::instance()))->trigger();$DB->set_field('logstore_standard_log', 'timecreated', $time++, ['eventname' => '\\core_tests\\event\\update_executed',]);\core_tests\event\delete_executed::create(array('context' => \context_system::instance()))->trigger();$DB->set_field('logstore_standard_log', 'timecreated', $time++, ['eventname' => '\\core_tests\\event\\delete_executed',]);$DB->set_field('logstore_standard_log', 'origin', 'web', array());$logs = $DB->get_records('logstore_standard_log', null, 'timecreated ASC');$this->assertCount(4, $logs);$firstnew = reset($logs);$this->assertGreaterThan($firstoldtime, $firstnew->timecreated);$DB->set_field('logstore_standard_log', 'timecreated', 10, array('id' => $firstnew->id));$this->assertEquals(10, stats_get_start_from('daily'));$DB->set_field('logstore_standard_log', 'timecreated', $firstnew->timecreated, array('id' => $firstnew->id));$DB->delete_records('log');$this->assertEquals($firstnew->timecreated, stats_get_start_from('daily'));set_config('enabled_stores', '', 'tool_log');get_log_manager(true);}/*** Test the function that calculates the start of the day.** NOTE: I don't think this is the way this function should work.* This test documents the current functionality.*/public function test_statslib_get_base_daily(): void {global $CFG;for ($x = 0; $x < 13; $x += 1) {$this->setTimezone($x);$start = 1272672000 - ($x * 3600);if ($x >= 20) {$start += (24 * 3600);}$this->assertEquals($start, stats_get_base_daily(1272686410), "Timezone $x check");}}/*** Test the function that gets the start of the next day.*/public function test_statslib_get_next_day_start(): void {$this->setTimezone(0);$this->assertEquals(1272758400, stats_get_next_day_start(1272686410));// Try setting timezone to some place in the US.$this->setTimezone('America/New_York', 'America/New_York');// Then set the time for midnight before daylight savings.// 1425790800 is midnight in New York (2015-03-08) Daylight saving will occur in 2 hours time.// 1425873600 is midnight the next day.$this->assertEquals(1425873600, stats_get_next_day_start(1425790800));$this->assertEquals(23, ((1425873600 - 1425790800) / 60 ) / 60);// Then set the time for midnight before daylight savings ends.// 1446350400 is midnight in New York (2015-11-01) Daylight saving will finish in 2 hours time.// 1446440400 is midnight the next day.$this->assertEquals(1446440400, stats_get_next_day_start(1446350400));$this->assertEquals(25, ((1446440400 - 1446350400) / 60 ) / 60);// The next day should be normal.$this->assertEquals(1446526800, stats_get_next_day_start(1446440400));$this->assertEquals(24, ((1446526800 - 1446440400) / 60 ) / 60);}/*** Test the function that calculates the start of the week.** @dataProvider get_base_weekly_provider* @param int $startwday Day in which the week starts (Sunday = 0)* @param string $timezone Default timezone* @param string $timestart Date and time for which the first day of the week will be obtained* @param string $expected Expected date of the first day of the week*/public function test_statslib_get_base_weekly($startwday, $timezone, $timestart, $expected): void {$this->setTimezone($timezone);$time = strtotime($timestart);$expected = strtotime($expected);set_config('calendar_startwday', $startwday);set_config('statslastweekly', $time);$weekstarttime = stats_get_base_weekly($time);$this->assertEquals($expected, $weekstarttime);}/*** Test the function that gets the action names.** Note: The function results depend on installed modules. The hard coded lists are the* defaults for a new Moodle 2.3 install.*/public function test_statslib_get_action_names(): void {$basepostactions = array (0 => 'add',1 => 'delete',2 => 'edit',3 => 'add mod',4 => 'delete mod',5 => 'edit sectionenrol',6 => 'loginas',7 => 'new',8 => 'unenrol',9 => 'update',10 => 'update mod',11 => 'upload',12 => 'submit',13 => 'submit for grading',14 => 'talk',15 => 'choose',16 => 'choose again',17 => 'record delete',18 => 'add discussion',19 => 'add post',20 => 'delete discussion',21 => 'delete post',22 => 'move discussion',23 => 'prune post',24 => 'update post',25 => 'add category',26 => 'add entry',27 => 'approve entry',28 => 'delete category',29 => 'delete entry',30 => 'edit category',31 => 'update entry',32 => 'end',33 => 'start',34 => 'attempt',35 => 'close attempt',36 => 'preview',37 => 'editquestions',38 => 'delete attempt',39 => 'manualgrade',);$baseviewactions = array (0 => 'view',1 => 'view all',2 => 'history',3 => 'view submission',4 => 'view feedback',5 => 'print',6 => 'report',7 => 'view discussion',8 => 'search',9 => 'forum',10 => 'forums',11 => 'subscribers',12 => 'view forum',13 => 'view entry',14 => 'review',15 => 'pre-view',16 => 'download',17 => 'view form',18 => 'view graph',19 => 'view report',);$postactions = stats_get_action_names('post');foreach ($basepostactions as $action) {$this->assertContains($action, $postactions);}$viewactions = stats_get_action_names('view');foreach ($baseviewactions as $action) {$this->assertContains($action, $viewactions);}}/*** Test the temporary table creation and deletion.*/public function test_statslib_temp_table_create_and_drop(): void {global $DB;foreach ($this->tables as $table) {$this->assertFalse($DB->get_manager()->table_exists($table));}stats_temp_table_create();foreach ($this->tables as $table) {$this->assertTrue($DB->get_manager()->table_exists($table));}stats_temp_table_drop();foreach ($this->tables as $table) {$this->assertFalse($DB->get_manager()->table_exists($table));}}/*** Test the temporary table creation and deletion.** @depends test_statslib_temp_table_create_and_drop*/public function test_statslib_temp_table_fill(): void {global $CFG, $DB, $USER;$dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test09.xml");$this->prepare_db($dataset, array('log'));// This nonsense needs to be rewritten.$date = new \DateTime('now', \core_date::get_server_timezone_object());$start = self::DAY - $date->getOffset();$end = $start + (24 * 3600);stats_temp_table_create();stats_temp_table_fill($start, $end);$this->assertEquals(1, $DB->count_records('temp_log1'));$this->assertEquals(1, $DB->count_records('temp_log2'));stats_temp_table_drop();// New log stores.$this->preventResetByRollback();stats_temp_table_create();$course = $this->getDataGenerator()->create_course();$context = \context_course::instance($course->id);$fcontext = \context_course::instance(SITEID);$user = $this->getDataGenerator()->create_user();$this->setUser($user);$this->assertFileExists("$CFG->dirroot/$CFG->admin/tool/log/store/standard/version.php");set_config('enabled_stores', 'logstore_standard', 'tool_log');set_config('buffersize', 0, 'logstore_standard');set_config('logguests', 1, 'logstore_standard');get_log_manager(true);$DB->delete_records('logstore_standard_log');\core_tests\event\create_executed::create(array('context' => $fcontext, 'courseid' => SITEID))->trigger();\core_tests\event\read_executed::create(array('context' => $context, 'courseid' => $course->id))->trigger();\core_tests\event\update_executed::create(array('context' => \context_system::instance()))->trigger();\core_tests\event\delete_executed::create(array('context' => \context_system::instance()))->trigger();\core\event\user_loggedin::create(array('userid' => $USER->id,'objectid' => $USER->id,'other' => array('username' => $USER->username),))->trigger();$DB->set_field('logstore_standard_log', 'timecreated', 10);$this->assertEquals(5, $DB->count_records('logstore_standard_log'));\core_tests\event\delete_executed::create(array('context' => \context_system::instance()))->trigger();\core_tests\event\delete_executed::create(array('context' => \context_system::instance()))->trigger();// Fake the origin of events.$DB->set_field('logstore_standard_log', 'origin', 'web', array());$this->assertEquals(7, $DB->count_records('logstore_standard_log'));stats_temp_table_fill(9, 11);$logs1 = $DB->get_records('temp_log1');$logs2 = $DB->get_records('temp_log2');$this->assertCount(5, $logs1);$this->assertCount(5, $logs2);// The order of records in the temp tables is not guaranteed...$viewcount = 0;$updatecount = 0;$logincount = 0;foreach ($logs1 as $log) {if ($log->course == $course->id) {$this->assertEquals('view', $log->action);$viewcount++;} else {$this->assertTrue(in_array($log->action, array('update', 'login')));if ($log->action === 'update') {$updatecount++;} else {$logincount++;}$this->assertEquals(SITEID, $log->course);}$this->assertEquals($user->id, $log->userid);}$this->assertEquals(1, $viewcount);$this->assertEquals(3, $updatecount);$this->assertEquals(1, $logincount);set_config('enabled_stores', '', 'tool_log');get_log_manager(true);stats_temp_table_drop();}/*** Test the temporary table creation and deletion.** @depends test_statslib_temp_table_create_and_drop*/public function test_statslib_temp_table_setup(): void {global $DB;$DB->delete_records('log');stats_temp_table_create();stats_temp_table_setup();$this->assertEquals(1, $DB->count_records('temp_enroled'));stats_temp_table_drop();}/*** Test the function that clean out the temporary tables.** @depends test_statslib_temp_table_create_and_drop*/public function test_statslib_temp_table_clean(): void {global $DB;$rows = array('temp_log1' => array('id' => 1, 'course' => 1),'temp_log2' => array('id' => 1, 'course' => 1),'temp_stats_daily' => array('id' => 1, 'courseid' => 1),'temp_stats_user_daily' => array('id' => 1, 'courseid' => 1),);stats_temp_table_create();foreach ($rows as $table => $row) {$DB->insert_record_raw($table, $row);$this->assertEquals(1, $DB->count_records($table));}stats_temp_table_clean();foreach ($rows as $table => $row) {$this->assertEquals(0, $DB->count_records($table));}$this->assertEquals(1, $DB->count_records('stats_daily'));$this->assertEquals(1, $DB->count_records('stats_user_daily'));stats_temp_table_drop();}/*** Test the daily stats function.** @depends test_statslib_get_base_daily* @depends test_statslib_get_next_day_start* @depends test_statslib_get_start_from* @depends test_statslib_temp_table_create_and_drop* @depends test_statslib_temp_table_setup* @depends test_statslib_temp_table_fill* @dataProvider daily_log_provider*/public function test_statslib_cron_daily($xmlfile): void {global $CFG, $DB;$dataset = $this->load_xml_data_file(__DIR__."/fixtures/{$xmlfile}");$stats = $this->prepare_db($dataset, array('log'));$stats = $dataset->get_rows(['stats_daily', 'stats_user_daily']);// Stats cron daily uses mtrace, turn on buffering to silence output.ob_start();stats_cron_daily(1);$output = ob_get_contents();ob_end_clean();$this->verify_stats($stats, $output);}/*** Test the daily stats function.** @depends test_statslib_get_base_daily* @depends test_statslib_get_next_day_start*/public function test_statslib_cron_daily_no_default_profile_id(): void {global $CFG, $DB;$CFG->defaultfrontpageroleid = 0;$course1 = $DB->get_record('course', array('shortname' => 'course1'));$guestid = $CFG->siteguest;$start = stats_get_base_daily(1272758400);$end = stats_get_next_day_start($start);$fpid = (int) $CFG->defaultfrontpageroleid;$gr = get_guest_role();$dataset = $this->load_xml_data_file(__DIR__."/fixtures/statslib-test10.xml");$this->prepare_db($dataset, array('log'));$stats = $dataset->get_rows(['stats_user_daily']);// Stats cron daily uses mtrace, turn on buffering to silence output.ob_start();stats_cron_daily($maxdays=1);$output = ob_get_contents();ob_end_clean();$this->verify_stats($dataset, $output);}}