MDL-59630 analytics: New clean up task
[moodle.git] / analytics / tests / model_test.php
CommitLineData
ff656bae
DM
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 * Unit tests for the model.
19 *
413f19bc 20 * @package core_analytics
ff656bae
DM
21 * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25defined('MOODLE_INTERNAL') || die();
26
27require_once(__DIR__ . '/fixtures/test_indicator_max.php');
28require_once(__DIR__ . '/fixtures/test_indicator_min.php');
29require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
30require_once(__DIR__ . '/fixtures/test_target_shortname.php');
31
32/**
33 * Unit tests for the model.
34 *
413f19bc 35 * @package core_analytics
ff656bae
DM
36 * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38 */
39class analytics_model_testcase extends advanced_testcase {
40
41 public function setUp() {
42
1611308b
DM
43 $this->setAdminUser();
44
ff656bae
DM
45 $target = \core_analytics\manager::get_target('test_target_shortname');
46 $indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
47 foreach ($indicators as $key => $indicator) {
48 $indicators[$key] = \core_analytics\manager::get_indicator($indicator);
49 }
50
51 $this->model = testable_model::create($target, $indicators);
52 $this->modelobj = $this->model->get_model_obj();
53 }
54
55 public function test_enable() {
56 $this->resetAfterTest(true);
57
58 $this->assertEquals(0, $this->model->get_model_obj()->enabled);
59 $this->assertEquals(0, $this->model->get_model_obj()->trained);
60 $this->assertEquals('', $this->model->get_model_obj()->timesplitting);
61
206d7aa9 62 $this->model->enable('\core\analytics\time_splitting\quarters');
ff656bae
DM
63 $this->assertEquals(1, $this->model->get_model_obj()->enabled);
64 $this->assertEquals(0, $this->model->get_model_obj()->trained);
206d7aa9 65 $this->assertEquals('\core\analytics\time_splitting\quarters', $this->model->get_model_obj()->timesplitting);
ff656bae
DM
66 }
67
68 public function test_create() {
69 $this->resetAfterTest(true);
70
206d7aa9 71 $target = \core_analytics\manager::get_target('\core\analytics\target\course_dropout');
ff656bae 72 $indicators = array(
206d7aa9
DM
73 \core_analytics\manager::get_indicator('\core\analytics\indicator\any_write_action'),
74 \core_analytics\manager::get_indicator('\core\analytics\indicator\read_actions')
ff656bae
DM
75 );
76 $model = \core_analytics\model::create($target, $indicators);
77 $this->assertInstanceOf('\core_analytics\model', $model);
78 }
79
99b84a26
DM
80 /**
81 * test_delete
82 */
83 public function test_delete() {
84 global $DB;
85
86 $this->resetAfterTest(true);
87 set_config('enabled_stores', 'logstore_standard', 'tool_log');
88
89 $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
90 $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
91 $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
92 $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
93
94 $this->model->enable('\core\analytics\time_splitting\no_splitting');
95
96 $this->model->train();
97 $this->model->predict();
98
99 // Fake evaluation results record to check that it is actually deleted.
100 $this->add_fake_log();
101
102 // Generate a prediction action to confirm that it is deleted when there is an important update.
103 $predictions = $DB->get_records('analytics_predictions');
104 $prediction = reset($predictions);
105 $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
106 $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
107
108 $this->model->delete();
109 $this->assertEmpty($DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
110 $this->assertEmpty($DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
111 $this->assertEmpty($DB->count_records('analytics_predictions'));
112 $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
113 $this->assertEmpty($DB->count_records('analytics_train_samples'));
114 $this->assertEmpty($DB->count_records('analytics_predict_samples'));
115 $this->assertEmpty($DB->count_records('analytics_used_files'));
116
117 set_config('enabled_stores', '', 'tool_log');
118 get_log_manager(true);
119 }
120
121 /**
122 * test_clear
123 */
124 public function test_clear() {
125 global $DB;
126
127 $this->resetAfterTest(true);
128 set_config('enabled_stores', 'logstore_standard', 'tool_log');
129
130 $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
131 $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
132 $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
133 $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
134
135 $this->model->enable('\core\analytics\time_splitting\no_splitting');
136
137 $this->model->train();
138 $this->model->predict();
139
140 // Fake evaluation results record to check that it is actually deleted.
141 $this->add_fake_log();
142
143 // Generate a prediction action to confirm that it is deleted when there is an important update.
144 $predictions = $DB->get_records('analytics_predictions');
145 $prediction = reset($predictions);
146 $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
147 $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
148
149 // Update to an empty time splitting method to force clear_model execution.
150 $this->model->update(1, false, '');
151 // Restore previous time splitting method.
152 $this->model->enable('\core\analytics\time_splitting\no_splitting');
153
154 // Check that most of the stuff got deleted.
155 $this->assertEquals(1, $DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
156 $this->assertEquals(1, $DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
157 $this->assertEmpty($DB->count_records('analytics_predictions'));
158 $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
159 $this->assertEmpty($DB->count_records('analytics_train_samples'));
160 $this->assertEmpty($DB->count_records('analytics_predict_samples'));
161 $this->assertEmpty($DB->count_records('analytics_used_files'));
162
163 set_config('enabled_stores', '', 'tool_log');
164 get_log_manager(true);
165 }
166
ff656bae
DM
167 public function test_model_manager() {
168 $this->resetAfterTest(true);
169
170 $this->assertCount(3, $this->model->get_indicators());
171 $this->assertInstanceOf('\core_analytics\local\target\binary', $this->model->get_target());
172
173 // Using evaluation as the model is not yet enabled.
174 $this->model->init_analyser(array('evaluation' => true));
175 $this->assertInstanceOf('\core_analytics\local\analyser\base', $this->model->get_analyser());
176
206d7aa9
DM
177 $this->model->enable('\core\analytics\time_splitting\quarters');
178 $this->assertInstanceOf('\core\analytics\analyser\site_courses', $this->model->get_analyser());
ff656bae
DM
179 }
180
181 public function test_output_dir() {
182 $this->resetAfterTest(true);
183
184 $dir = make_request_directory();
185 set_config('modeloutputdir', $dir, 'analytics');
186
187 $modeldir = $dir . DIRECTORY_SEPARATOR . $this->modelobj->id . DIRECTORY_SEPARATOR . $this->modelobj->version;
188 $this->assertEquals($modeldir, $this->model->get_output_dir());
189 $this->assertEquals($modeldir . DIRECTORY_SEPARATOR . 'asd', $this->model->get_output_dir(array('asd')));
190 }
191
192 public function test_unique_id() {
193 global $DB;
194
195 $this->resetAfterTest(true);
196
197 $originaluniqueid = $this->model->get_unique_id();
198
199 // Same id across instances.
200 $this->model = new testable_model($this->modelobj);
201 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
202
203 // We will restore it later.
204 $originalversion = $this->modelobj->version;
205
206 // Generates a different id if timemodified changes.
207 $this->modelobj->version = $this->modelobj->version + 10;
208 $DB->update_record('analytics_models', $this->modelobj);
209 $this->model = new testable_model($this->modelobj);
210 $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
211
212 // Restore original timemodified to continue testing.
213 $this->modelobj->version = $originalversion;
214 $DB->update_record('analytics_models', $this->modelobj);
215 // Same when updating through an action that changes the model.
216 $this->model = new testable_model($this->modelobj);
217
218 $this->model->mark_as_trained();
219 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
220
221 $this->model->enable();
222 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
223
224 // Wait 1 sec so the timestamp changes.
225 sleep(1);
206d7aa9 226 $this->model->enable('\core\analytics\time_splitting\quarters');
ff656bae 227 $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
e709e544
DM
228 }
229
230 /**
231 * test_exists
232 *
233 * @return void
234 */
235 public function test_exists() {
236 $this->resetAfterTest(true);
237
238 global $DB;
239
cab7abec 240 $count = $DB->count_records('analytics_models');
e709e544
DM
241
242 // No new models added if the builtin ones already exist.
243 \core_analytics\manager::add_builtin_models();
cab7abec 244 $this->assertCount($count, $DB->get_records('analytics_models'));
ff656bae 245
e709e544
DM
246 $target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
247 $this->assertTrue(\core_analytics\model::exists($target));
ff656bae 248 }
99b84a26
DM
249
250 /**
251 * Generates a model log record.
252 */
253 private function add_fake_log() {
254 global $DB, $USER;
255
256 $log = new stdClass();
257 $log->modelid = $this->modelobj->id;
258 $log->version = $this->modelobj->version;
259 $log->target = $this->modelobj->target;
260 $log->indicators = $this->modelobj->indicators;
261 $log->score = 1;
262 $log->info = json_encode([]);
263 $log->dir = 'not important';
264 $log->timecreated = time();
265 $log->usermodified = $USER->id;
266 $DB->insert_record('analytics_models_log', $log);
267 }
ff656bae
DM
268}
269
413f19bc
DM
270/**
271 * Testable version to change methods' visibility.
272 *
273 * @package core_analytics
274 * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
275 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
276 */
ff656bae 277class testable_model extends \core_analytics\model {
413f19bc
DM
278
279 /**
280 * get_output_dir
281 *
282 * @param array $subdirs
283 * @return string
284 */
ff656bae
DM
285 public function get_output_dir($subdirs = array()) {
286 return parent::get_output_dir($subdirs);
287 }
288
413f19bc
DM
289 /**
290 * init_analyser
291 *
292 * @param array $options
293 * @return void
294 */
ff656bae 295 public function init_analyser($options = array()) {
413f19bc 296 parent::init_analyser($options);
ff656bae
DM
297 }
298}