Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | Ultima modificación | Ver Log |

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