fcb7eaceb83302d82ee1d675884e106e177b5d1a
[moodle.git] / analytics / tests / model_test.php
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/>.
17 /**
18  * Unit tests for the model.
19  *
20  * @package   core_analytics
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  */
25 defined('MOODLE_INTERNAL') || die();
27 require_once(__DIR__ . '/fixtures/test_indicator_max.php');
28 require_once(__DIR__ . '/fixtures/test_indicator_min.php');
29 require_once(__DIR__ . '/fixtures/test_indicator_fullname.php');
30 require_once(__DIR__ . '/fixtures/test_target_shortname.php');
32 /**
33  * Unit tests for the model.
34  *
35  * @package   core_analytics
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  */
39 class analytics_model_testcase extends advanced_testcase {
41     public function setUp() {
43         $this->setAdminUser();
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         }
51         $this->model = testable_model::create($target, $indicators);
52         $this->modelobj = $this->model->get_model_obj();
53     }
55     public function test_enable() {
56         $this->resetAfterTest(true);
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);
62         $this->model->enable('\core\analytics\time_splitting\quarters');
63         $this->assertEquals(1, $this->model->get_model_obj()->enabled);
64         $this->assertEquals(0, $this->model->get_model_obj()->trained);
65         $this->assertEquals('\core\analytics\time_splitting\quarters', $this->model->get_model_obj()->timesplitting);
66     }
68     public function test_create() {
69         $this->resetAfterTest(true);
71         $target = \core_analytics\manager::get_target('\core\analytics\target\course_dropout');
72         $indicators = array(
73             \core_analytics\manager::get_indicator('\core\analytics\indicator\any_write_action'),
74             \core_analytics\manager::get_indicator('\core\analytics\indicator\read_actions')
75         );
76         $model = \core_analytics\model::create($target, $indicators);
77         $this->assertInstanceOf('\core_analytics\model', $model);
78     }
80     /**
81      * test_delete
82      */
83     public function test_delete() {
84         global $DB;
86         $this->resetAfterTest(true);
87         set_config('enabled_stores', 'logstore_standard', 'tool_log');
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));
94         $this->model->enable('\core\analytics\time_splitting\no_splitting');
96         $this->model->train();
97         $this->model->predict();
99         // Fake evaluation results record to check that it is actually deleted.
100         $this->add_fake_log();
102         $modeloutputdir = $this->model->get_output_dir(array(), true);
103         $this->assertTrue(is_dir($modeloutputdir));
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());
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'));
119         $this->assertFalse(is_dir($modeloutputdir));
121         set_config('enabled_stores', '', 'tool_log');
122         get_log_manager(true);
123     }
125     /**
126      * test_clear
127      */
128     public function test_clear() {
129         global $DB;
131         $this->resetAfterTest(true);
132         set_config('enabled_stores', 'logstore_standard', 'tool_log');
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));
139         $this->model->enable('\core\analytics\time_splitting\no_splitting');
141         $this->model->train();
142         $this->model->predict();
144         // Fake evaluation results record to check that it is actually deleted.
145         $this->add_fake_log();
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());
153         $modelversionoutputdir = $this->model->get_output_dir();
154         $this->assertTrue(is_dir($modelversionoutputdir));
156         // Update to an empty time splitting method to force clear_model execution.
157         $this->model->update(1, false, '');
158         $this->assertFalse(is_dir($modelversionoutputdir));
160         // Restore previous time splitting method.
161         $this->model->enable('\core\analytics\time_splitting\no_splitting');
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'));
172         set_config('enabled_stores', '', 'tool_log');
173         get_log_manager(true);
174     }
176     public function test_model_manager() {
177         $this->resetAfterTest(true);
179         $this->assertCount(3, $this->model->get_indicators());
180         $this->assertInstanceOf('\core_analytics\local\target\binary', $this->model->get_target());
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());
186         $this->model->enable('\core\analytics\time_splitting\quarters');
187         $this->assertInstanceOf('\core\analytics\analyser\site_courses', $this->model->get_analyser());
188     }
190     public function test_output_dir() {
191         $this->resetAfterTest(true);
193         $dir = make_request_directory();
194         set_config('modeloutputdir', $dir, 'analytics');
196         $modeldir = $dir . DIRECTORY_SEPARATOR . $this->modelobj->id . DIRECTORY_SEPARATOR . $this->modelobj->version;
197         $this->assertEquals($modeldir, $this->model->get_output_dir());
198         $this->assertEquals($modeldir . DIRECTORY_SEPARATOR . 'testing', $this->model->get_output_dir(array('testing')));
199     }
201     public function test_unique_id() {
202         global $DB;
204         $this->resetAfterTest(true);
206         $originaluniqueid = $this->model->get_unique_id();
208         // Same id across instances.
209         $this->model = new testable_model($this->modelobj);
210         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
212         // We will restore it later.
213         $originalversion = $this->modelobj->version;
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());
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);
227         $this->model->mark_as_trained();
228         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
230         $this->model->enable();
231         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
233         // Wait 1 sec so the timestamp changes.
234         sleep(1);
235         $this->model->enable('\core\analytics\time_splitting\quarters');
236         $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
237     }
239     /**
240      * test_exists
241      *
242      * @return void
243      */
244     public function test_exists() {
245         $this->resetAfterTest(true);
247         global $DB;
249         $count = $DB->count_records('analytics_models');
251         // No new models added if the builtin ones already exist.
252         \core_analytics\manager::add_builtin_models();
253         $this->assertCount($count, $DB->get_records('analytics_models'));
255         $target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
256         $this->assertTrue(\core_analytics\model::exists($target));
257     }
259     /**
260      * Generates a model log record.
261      */
262     private function add_fake_log() {
263         global $DB, $USER;
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     }
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  */
286 class testable_model extends \core_analytics\model {
288     /**
289      * get_output_dir
290      *
291      * @param array $subdirs
292      * @param bool $onlymodelid
293      * @return string
294      */
295     public function get_output_dir($subdirs = array(), $onlymodelid = false) {
296         return parent::get_output_dir($subdirs, $onlymodelid);
297     }
299     /**
300      * init_analyser
301      *
302      * @param array $options
303      * @return void
304      */
305     public function init_analyser($options = array()) {
306         parent::init_analyser($options);
307     }