weekly release 3.4dev
[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
abafbc84
DM
102 $modeloutputdir = $this->model->get_output_dir(array(), true);
103 $this->assertTrue(is_dir($modeloutputdir));
104
99b84a26
DM
105 // Generate a prediction action to confirm that it is deleted when there is an important update.
106 $predictions = $DB->get_records('analytics_predictions');
107 $prediction = reset($predictions);
108 $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
109 $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
110
111 $this->model->delete();
112 $this->assertEmpty($DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
113 $this->assertEmpty($DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
114 $this->assertEmpty($DB->count_records('analytics_predictions'));
115 $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
116 $this->assertEmpty($DB->count_records('analytics_train_samples'));
117 $this->assertEmpty($DB->count_records('analytics_predict_samples'));
118 $this->assertEmpty($DB->count_records('analytics_used_files'));
abafbc84 119 $this->assertFalse(is_dir($modeloutputdir));
99b84a26
DM
120
121 set_config('enabled_stores', '', 'tool_log');
122 get_log_manager(true);
123 }
124
125 /**
126 * test_clear
127 */
128 public function test_clear() {
129 global $DB;
130
131 $this->resetAfterTest(true);
132 set_config('enabled_stores', 'logstore_standard', 'tool_log');
133
134 $coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
135 $coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
136 $coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
137 $coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
138
139 $this->model->enable('\core\analytics\time_splitting\no_splitting');
140
141 $this->model->train();
142 $this->model->predict();
143
144 // Fake evaluation results record to check that it is actually deleted.
145 $this->add_fake_log();
146
147 // Generate a prediction action to confirm that it is deleted when there is an important update.
148 $predictions = $DB->get_records('analytics_predictions');
149 $prediction = reset($predictions);
150 $prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
151 $prediction->action_executed(\core_analytics\prediction::ACTION_FIXED, $this->model->get_target());
152
abafbc84
DM
153 $modelversionoutputdir = $this->model->get_output_dir();
154 $this->assertTrue(is_dir($modelversionoutputdir));
155
99b84a26
DM
156 // Update to an empty time splitting method to force clear_model execution.
157 $this->model->update(1, false, '');
abafbc84
DM
158 $this->assertFalse(is_dir($modelversionoutputdir));
159
99b84a26
DM
160 // Restore previous time splitting method.
161 $this->model->enable('\core\analytics\time_splitting\no_splitting');
162
163 // Check that most of the stuff got deleted.
164 $this->assertEquals(1, $DB->count_records('analytics_models', array('id' => $this->modelobj->id)));
165 $this->assertEquals(1, $DB->count_records('analytics_models_log', array('modelid' => $this->modelobj->id)));
166 $this->assertEmpty($DB->count_records('analytics_predictions'));
167 $this->assertEmpty($DB->count_records('analytics_prediction_actions'));
168 $this->assertEmpty($DB->count_records('analytics_train_samples'));
169 $this->assertEmpty($DB->count_records('analytics_predict_samples'));
170 $this->assertEmpty($DB->count_records('analytics_used_files'));
171
172 set_config('enabled_stores', '', 'tool_log');
173 get_log_manager(true);
174 }
175
ff656bae
DM
176 public function test_model_manager() {
177 $this->resetAfterTest(true);
178
179 $this->assertCount(3, $this->model->get_indicators());
180 $this->assertInstanceOf('\core_analytics\local\target\binary', $this->model->get_target());
181
182 // Using evaluation as the model is not yet enabled.
183 $this->model->init_analyser(array('evaluation' => true));
184 $this->assertInstanceOf('\core_analytics\local\analyser\base', $this->model->get_analyser());
185
206d7aa9
DM
186 $this->model->enable('\core\analytics\time_splitting\quarters');
187 $this->assertInstanceOf('\core\analytics\analyser\site_courses', $this->model->get_analyser());
ff656bae
DM
188 }
189
190 public function test_output_dir() {
191 $this->resetAfterTest(true);
192
193 $dir = make_request_directory();
194 set_config('modeloutputdir', $dir, 'analytics');
195
196 $modeldir = $dir . DIRECTORY_SEPARATOR . $this->modelobj->id . DIRECTORY_SEPARATOR . $this->modelobj->version;
197 $this->assertEquals($modeldir, $this->model->get_output_dir());
abafbc84 198 $this->assertEquals($modeldir . DIRECTORY_SEPARATOR . 'testing', $this->model->get_output_dir(array('testing')));
ff656bae
DM
199 }
200
201 public function test_unique_id() {
202 global $DB;
203
204 $this->resetAfterTest(true);
205
206 $originaluniqueid = $this->model->get_unique_id();
207
208 // Same id across instances.
209 $this->model = new testable_model($this->modelobj);
210 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
211
212 // We will restore it later.
213 $originalversion = $this->modelobj->version;
214
215 // Generates a different id if timemodified changes.
216 $this->modelobj->version = $this->modelobj->version + 10;
217 $DB->update_record('analytics_models', $this->modelobj);
218 $this->model = new testable_model($this->modelobj);
219 $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
220
221 // Restore original timemodified to continue testing.
222 $this->modelobj->version = $originalversion;
223 $DB->update_record('analytics_models', $this->modelobj);
224 // Same when updating through an action that changes the model.
225 $this->model = new testable_model($this->modelobj);
226
227 $this->model->mark_as_trained();
228 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
229
230 $this->model->enable();
231 $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
232
233 // Wait 1 sec so the timestamp changes.
234 sleep(1);
206d7aa9 235 $this->model->enable('\core\analytics\time_splitting\quarters');
ff656bae 236 $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
e709e544
DM
237 }
238
239 /**
240 * test_exists
241 *
242 * @return void
243 */
244 public function test_exists() {
245 $this->resetAfterTest(true);
246
247 global $DB;
248
cab7abec 249 $count = $DB->count_records('analytics_models');
e709e544
DM
250
251 // No new models added if the builtin ones already exist.
252 \core_analytics\manager::add_builtin_models();
cab7abec 253 $this->assertCount($count, $DB->get_records('analytics_models'));
ff656bae 254
e709e544
DM
255 $target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
256 $this->assertTrue(\core_analytics\model::exists($target));
ff656bae 257 }
99b84a26
DM
258
259 /**
260 * Generates a model log record.
261 */
262 private function add_fake_log() {
263 global $DB, $USER;
264
265 $log = new stdClass();
266 $log->modelid = $this->modelobj->id;
267 $log->version = $this->modelobj->version;
268 $log->target = $this->modelobj->target;
269 $log->indicators = $this->modelobj->indicators;
270 $log->score = 1;
271 $log->info = json_encode([]);
272 $log->dir = 'not important';
273 $log->timecreated = time();
274 $log->usermodified = $USER->id;
275 $DB->insert_record('analytics_models_log', $log);
276 }
ff656bae
DM
277}
278
413f19bc
DM
279/**
280 * Testable version to change methods' visibility.
281 *
282 * @package core_analytics
283 * @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
284 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
285 */
ff656bae 286class testable_model extends \core_analytics\model {
413f19bc
DM
287
288 /**
289 * get_output_dir
290 *
291 * @param array $subdirs
abafbc84 292 * @param bool $onlymodelid
413f19bc
DM
293 * @return string
294 */
abafbc84
DM
295 public function get_output_dir($subdirs = array(), $onlymodelid = false) {
296 return parent::get_output_dir($subdirs, $onlymodelid);
ff656bae
DM
297 }
298
413f19bc
DM
299 /**
300 * init_analyser
301 *
302 * @param array $options
303 * @return void
304 */
ff656bae 305 public function init_analyser($options = array()) {
413f19bc 306 parent::init_analyser($options);
ff656bae
DM
307 }
308}