Proyectos de Subversion Moodle

Rev

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