See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [Versions 401 and 403]
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 core_analytics; 18 19 use test_timesplitting_seconds; 20 use test_timesplitting_upcoming_seconds; 21 22 defined('MOODLE_INTERNAL') || die(); 23 24 require_once (__DIR__ . '/fixtures/test_timesplitting_seconds.php'); 25 require_once (__DIR__ . '/fixtures/test_timesplitting_upcoming_seconds.php'); 26 require_once (__DIR__ . '/../../lib/enrollib.php'); 27 28 /** 29 * Unit tests for core time splitting methods. 30 * 31 * @package core 32 * @category test 33 * @copyright 2017 David MonllaĆ³ {@link http://www.davidmonllao.com} 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class time_splittings_test extends \advanced_testcase { 37 38 /** 39 * setUp 40 * 41 * @return void 42 */ 43 public function setUp(): void { 44 45 $this->resetAfterTest(true); 46 47 // Generate training data. 48 $params = array( 49 'startdate' => mktime(8, 15, 32, 10, 24, 2015), 50 'enddate' => mktime(12, 12, 31, 10, 24, 2016), 51 ); 52 $this->course = $this->getDataGenerator()->create_course($params); 53 $this->analysable = new \core_analytics\course($this->course); 54 } 55 56 /** 57 * test_ranges 58 * 59 * @return void 60 */ 61 public function test_valid_ranges() { 62 63 // All core time splitting methods. 64 $timesplittings = array( 65 '\core\analytics\time_splitting\deciles', 66 '\core\analytics\time_splitting\deciles_accum', 67 '\core\analytics\time_splitting\no_splitting', 68 '\core\analytics\time_splitting\quarters', 69 '\core\analytics\time_splitting\quarters_accum', 70 '\core\analytics\time_splitting\single_range', 71 '\core\analytics\time_splitting\upcoming_week', 72 ); 73 74 // Check that defined ranges are valid (tested through validate_ranges). 75 foreach ($timesplittings as $timesplitting) { 76 $instance = new $timesplitting(); 77 $instance->set_analysable($this->analysable); 78 } 79 } 80 81 /** 82 * test_range_dates 83 * 84 * @return void 85 */ 86 public function test_range_dates() { 87 88 $nov2015 = mktime(0, 0, 0, 11, 24, 2015); 89 $aug2016 = mktime(0, 0, 0, 8, 29, 2016); 90 91 // Equal parts. 92 $quarters = new \core\analytics\time_splitting\quarters(); 93 $quarters->set_analysable($this->analysable); 94 $ranges = $quarters->get_all_ranges(); 95 $this->assertCount(4, $ranges); 96 $this->assertCount(4, $quarters->get_training_ranges()); 97 $this->assertCount(4, $quarters->get_distinct_ranges()); 98 99 $this->assertGreaterThan($ranges[0]['start'], $ranges[1]['start']); 100 $this->assertGreaterThan($ranges[0]['end'], $ranges[1]['start']); 101 $this->assertGreaterThan($ranges[0]['end'], $ranges[1]['end']); 102 103 $this->assertGreaterThan($ranges[1]['start'], $ranges[2]['start']); 104 $this->assertGreaterThan($ranges[1]['end'], $ranges[2]['start']); 105 $this->assertGreaterThan($ranges[1]['end'], $ranges[2]['end']); 106 107 $this->assertGreaterThan($ranges[2]['start'], $ranges[3]['start']); 108 $this->assertGreaterThan($ranges[2]['end'], $ranges[3]['end']); 109 $this->assertGreaterThan($ranges[2]['end'], $ranges[3]['start']); 110 111 // First range. 112 $this->assertLessThan($nov2015, $ranges[0]['start']); 113 $this->assertGreaterThan($nov2015, $ranges[0]['end']); 114 115 // Last range. 116 $this->assertLessThan($aug2016, $ranges[3]['start']); 117 $this->assertGreaterThan($aug2016, $ranges[3]['end']); 118 119 // Accumulative. 120 $accum = new \core\analytics\time_splitting\quarters_accum(); 121 $accum->set_analysable($this->analysable); 122 $ranges = $accum->get_all_ranges(); 123 $this->assertCount(4, $ranges); 124 $this->assertCount(4, $accum->get_training_ranges()); 125 $this->assertCount(4, $accum->get_distinct_ranges()); 126 127 $this->assertEquals($ranges[0]['start'], $ranges[1]['start']); 128 $this->assertEquals($ranges[1]['start'], $ranges[2]['start']); 129 $this->assertEquals($ranges[2]['start'], $ranges[3]['start']); 130 131 $this->assertGreaterThan($ranges[0]['end'], $ranges[1]['end']); 132 $this->assertGreaterThan($ranges[1]['end'], $ranges[2]['end']); 133 $this->assertGreaterThan($ranges[2]['end'], $ranges[3]['end']); 134 135 // Present in all ranges. 136 $this->assertLessThan($nov2015, $ranges[0]['start']); 137 $this->assertGreaterThan($nov2015, $ranges[0]['end']); 138 $this->assertGreaterThan($nov2015, $ranges[1]['end']); 139 $this->assertGreaterThan($nov2015, $ranges[2]['end']); 140 $this->assertGreaterThan($nov2015, $ranges[3]['end']); 141 142 // Only in the last range. 143 $this->assertLessThan($aug2016, $ranges[0]['end']); 144 $this->assertLessThan($aug2016, $ranges[1]['end']); 145 $this->assertLessThan($aug2016, $ranges[2]['end']); 146 $this->assertLessThan($aug2016, $ranges[3]['start']); 147 $this->assertGreaterThan($aug2016, $ranges[3]['end']); 148 } 149 150 /** 151 * test_ready_predict 152 * 153 * @return void 154 */ 155 public function test_ready_predict() { 156 157 $quarters = new \core\analytics\time_splitting\quarters(); 158 $nosplitting = new \core\analytics\time_splitting\no_splitting(); 159 $singlerange = new \core\analytics\time_splitting\single_range(); 160 161 $range = array( 162 'start' => time() - 100, 163 'end' => time() - 20, 164 ); 165 $range['time'] = $range['end']; 166 $this->assertTrue($quarters->ready_to_predict($range)); 167 $this->assertTrue($nosplitting->ready_to_predict($range)); 168 169 // Single range time is 0. 170 $range['time'] = 0; 171 $this->assertTrue($singlerange->ready_to_predict($range)); 172 173 $range = array( 174 'start' => time() + 20, 175 'end' => time() + 100, 176 ); 177 $range['time'] = $range['end']; 178 $this->assertFalse($quarters->ready_to_predict($range)); 179 $this->assertTrue($nosplitting->ready_to_predict($range)); 180 181 // Single range time is 0. 182 $range['time'] = 0; 183 $this->assertTrue($singlerange->ready_to_predict($range)); 184 } 185 186 /** 187 * test_periodic 188 * 189 * @return void 190 */ 191 public function test_periodic() { 192 193 // Using a finished course. 194 195 $pastweek = new \core\analytics\time_splitting\past_week(); 196 $pastweek->set_analysable($this->analysable); 197 $this->assertCount(1, $pastweek->get_distinct_ranges()); 198 199 $ranges = $pastweek->get_all_ranges(); 200 $this->assertEquals(52, count($ranges)); 201 $this->assertEquals($this->course->startdate, $ranges[0]['start']); 202 $this->assertNotEquals($this->course->startdate, $ranges[0]['time']); 203 204 // The analysable is finished so all ranges are available for training. 205 $this->assertCount(count($ranges), $pastweek->get_training_ranges()); 206 207 $ranges = $pastweek->get_most_recent_prediction_range(); 208 $range = reset($ranges); 209 $this->assertEquals(51, key($ranges)); 210 211 // We now use an ongoing course not yet ready to generate predictions. 212 213 $threedaysago = new \DateTime('-3 days'); 214 $params = array( 215 'startdate' => $threedaysago->getTimestamp(), 216 ); 217 $ongoingcourse = $this->getDataGenerator()->create_course($params); 218 $ongoinganalysable = new \core_analytics\course($ongoingcourse); 219 220 $pastweek = new \core\analytics\time_splitting\past_week(); 221 $pastweek->set_analysable($ongoinganalysable); 222 $ranges = $pastweek->get_all_ranges(); 223 $this->assertEquals(0, count($ranges)); 224 $this->assertCount(0, $pastweek->get_training_ranges()); 225 226 // We now use a ready-to-predict ongoing course. 227 228 $onemonthago = new \DateTime('-30 days'); 229 $params = array( 230 'startdate' => $onemonthago->getTimestamp(), 231 ); 232 $ongoingcourse = $this->getDataGenerator()->create_course($params); 233 $ongoinganalysable = new \core_analytics\course($ongoingcourse); 234 235 $pastweek = new \core\analytics\time_splitting\past_week(); 236 $pastweek->set_analysable($ongoinganalysable); 237 $this->assertCount(1, $pastweek->get_distinct_ranges()); 238 239 $ranges = $pastweek->get_all_ranges(); 240 $this->assertEquals(4, count($ranges)); 241 $this->assertCount(4, $pastweek->get_training_ranges()); 242 243 $ranges = $pastweek->get_most_recent_prediction_range(); 244 $range = reset($ranges); 245 $this->assertEquals(3, key($ranges)); 246 $this->assertEqualsWithDelta(time(), $range['time'], 1); 247 // 1 second delta for the start just in case a second passes between the set_analysable call 248 // and this checking below. 249 $time = new \DateTime(); 250 $time->sub($pastweek->periodicity()); 251 $this->assertEqualsWithDelta($time->getTimestamp(), $range['start'], 1.0); 252 $this->assertEqualsWithDelta(time(), $range['end'], 1); 253 254 $starttime = time(); 255 256 $upcomingweek = new \core\analytics\time_splitting\upcoming_week(); 257 $upcomingweek->set_analysable($ongoinganalysable); 258 $this->assertCount(1, $upcomingweek->get_distinct_ranges()); 259 260 $ranges = $upcomingweek->get_all_ranges(); 261 $this->assertEquals(1, count($ranges)); 262 $range = reset($ranges); 263 $this->assertEqualsWithDelta(time(), $range['time'], 1); 264 $this->assertEqualsWithDelta(time(), $range['start'], 1); 265 $this->assertGreaterThan(time(), $range['end']); 266 267 $this->assertCount(0, $upcomingweek->get_training_ranges()); 268 269 $ranges = $upcomingweek->get_most_recent_prediction_range(); 270 $range = reset($ranges); 271 $this->assertEquals(0, key($ranges)); 272 $this->assertEqualsWithDelta(time(), $range['time'], 1); 273 $this->assertEqualsWithDelta(time(), $range['start'], 1); 274 $this->assertGreaterThanOrEqual($starttime, $range['time']); 275 $this->assertGreaterThanOrEqual($starttime, $range['start']); 276 $this->assertGreaterThan(time(), $range['end']); 277 278 $this->assertNotEmpty($upcomingweek->get_range_by_index(0)); 279 $this->assertFalse($upcomingweek->get_range_by_index(1)); 280 281 // We now check how new ranges get added as time passes. 282 283 $fewsecsago = new \DateTime('-5 seconds'); 284 $params = array( 285 'startdate' => $fewsecsago->getTimestamp(), 286 'enddate' => (new \DateTimeImmutable('+1 year'))->getTimestamp(), 287 ); 288 $course = $this->getDataGenerator()->create_course($params); 289 $analysable = new \core_analytics\course($course); 290 291 $seconds = new test_timesplitting_seconds(); 292 $seconds->set_analysable($analysable); 293 294 // Store the ranges we just obtained. 295 $ranges = $seconds->get_all_ranges(); 296 $nranges = count($ranges); 297 $ntrainingranges = count($seconds->get_training_ranges()); 298 $mostrecentrange = $seconds->get_most_recent_prediction_range(); 299 $mostrecentrange = reset($mostrecentrange); 300 301 // We wait for the next range to be added. 302 sleep(1); 303 304 // We set the analysable again so the time ranges are recalculated. 305 $seconds->set_analysable($analysable); 306 307 $newranges = $seconds->get_all_ranges(); 308 $nnewranges = count($newranges); 309 $nnewtrainingranges = $seconds->get_training_ranges(); 310 $newmostrecentrange = $seconds->get_most_recent_prediction_range(); 311 $newmostrecentrange = reset($newmostrecentrange); 312 $this->assertGreaterThan($nranges, $nnewranges); 313 $this->assertGreaterThan($ntrainingranges, $nnewtrainingranges); 314 $this->assertGreaterThan($mostrecentrange['time'], $newmostrecentrange['time']); 315 316 // All the ranges but the last one should return the same values. 317 array_pop($ranges); 318 array_pop($newranges); 319 foreach ($ranges as $key => $range) { 320 $this->assertEquals($newranges[$key]['start'], $range['start']); 321 $this->assertEquals($newranges[$key]['end'], $range['end']); 322 $this->assertEquals($newranges[$key]['time'], $range['time']); 323 } 324 325 // Fake model id, we can use any int, we will need to reference it later. 326 $modelid = 1505347200; 327 328 $upcomingseconds = new test_timesplitting_upcoming_seconds(); 329 $upcomingseconds->set_modelid($modelid); 330 $upcomingseconds->set_analysable($analysable); 331 332 // Store the ranges we just obtained. 333 $ranges = $upcomingseconds->get_all_ranges(); 334 $nranges = count($ranges); 335 $ntrainingranges = count($upcomingseconds->get_training_ranges()); 336 $mostrecentrange = $upcomingseconds->get_most_recent_prediction_range(); 337 $mostrecentrange = reset($mostrecentrange); 338 339 // Mimic the modelfirstanalyses caching in \core_analytics\analysis. 340 $this->mock_cache_first_analysis_caching($modelid, $analysable->get_id(), end($ranges)); 341 342 // We wait for the next range to be added. 343 sleep(1); 344 345 // We set the analysable again so the time ranges are recalculated. 346 $upcomingseconds->set_analysable($analysable); 347 348 $newranges = $upcomingseconds->get_all_ranges(); 349 $nnewranges = count($newranges); 350 $nnewtrainingranges = $upcomingseconds->get_training_ranges(); 351 $newmostrecentrange = $upcomingseconds->get_most_recent_prediction_range(); 352 $newmostrecentrange = reset($newmostrecentrange); 353 $this->assertGreaterThan($nranges, $nnewranges); 354 $this->assertGreaterThan($ntrainingranges, $nnewtrainingranges); 355 $this->assertGreaterThan($mostrecentrange['time'], $newmostrecentrange['time']); 356 357 // All the ranges but the last one should return the same values. 358 array_pop($ranges); 359 array_pop($newranges); 360 foreach ($ranges as $key => $range) { 361 $this->assertEquals($newranges[$key]['start'], $range['start']); 362 $this->assertEquals($newranges[$key]['end'], $range['end']); 363 $this->assertEquals($newranges[$key]['time'], $range['time']); 364 } 365 } 366 367 /** 368 * Mocks core_analytics\analysis caching of the first time analysables were analysed. 369 * 370 * @param int $modelid 371 * @param int $analysableid 372 * @param array $range 373 * @return null 374 */ 375 private function mock_cache_first_analysis_caching($modelid, $analysableid, $range) { 376 $cache = \cache::make('core', 'modelfirstanalyses'); 377 $cache->set($modelid . '_' . $analysableid, $range['time']); 378 } 379 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body