48bd63007627bf759bea36a2456ea3120c11e8f3
[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         // 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());
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'));
117         set_config('enabled_stores', '', 'tool_log');
118         get_log_manager(true);
119     }
121     /**
122      * test_clear
123      */
124     public function test_clear() {
125         global $DB;
127         $this->resetAfterTest(true);
128         set_config('enabled_stores', 'logstore_standard', 'tool_log');
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));
135         $this->model->enable('\core\analytics\time_splitting\no_splitting');
137         $this->model->train();
138         $this->model->predict();
140         // Fake evaluation results record to check that it is actually deleted.
141         $this->add_fake_log();
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());
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');
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'));
163         set_config('enabled_stores', '', 'tool_log');
164         get_log_manager(true);
165     }
167     public function test_model_manager() {
168         $this->resetAfterTest(true);
170         $this->assertCount(3, $this->model->get_indicators());
171         $this->assertInstanceOf('\core_analytics\local\target\binary', $this->model->get_target());
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());
177         $this->model->enable('\core\analytics\time_splitting\quarters');
178         $this->assertInstanceOf('\core\analytics\analyser\site_courses', $this->model->get_analyser());
179     }
181     public function test_output_dir() {
182         $this->resetAfterTest(true);
184         $dir = make_request_directory();
185         set_config('modeloutputdir', $dir, 'analytics');
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     }
192     public function test_unique_id() {
193         global $DB;
195         $this->resetAfterTest(true);
197         $originaluniqueid = $this->model->get_unique_id();
199         // Same id across instances.
200         $this->model = new testable_model($this->modelobj);
201         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
203         // We will restore it later.
204         $originalversion = $this->modelobj->version;
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());
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);
218         $this->model->mark_as_trained();
219         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
221         $this->model->enable();
222         $this->assertEquals($originaluniqueid, $this->model->get_unique_id());
224         // Wait 1 sec so the timestamp changes.
225         sleep(1);
226         $this->model->enable('\core\analytics\time_splitting\quarters');
227         $this->assertNotEquals($originaluniqueid, $this->model->get_unique_id());
228     }
230     /**
231      * test_exists
232      *
233      * @return void
234      */
235     public function test_exists() {
236         $this->resetAfterTest(true);
238         global $DB;
240         $count = $DB->count_records('analytics_models');
242         // No new models added if the builtin ones already exist.
243         \core_analytics\manager::add_builtin_models();
244         $this->assertCount($count, $DB->get_records('analytics_models'));
246         $target = \core_analytics\manager::get_target('\core\analytics\target\no_teaching');
247         $this->assertTrue(\core_analytics\model::exists($target));
248     }
250     /**
251      * Generates a model log record.
252      */
253     private function add_fake_log() {
254         global $DB, $USER;
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     }
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  */
277 class testable_model extends \core_analytics\model {
279     /**
280      * get_output_dir
281      *
282      * @param array $subdirs
283      * @return string
284      */
285     public function get_output_dir($subdirs = array()) {
286         return parent::get_output_dir($subdirs);
287     }
289     /**
290      * init_analyser
291      *
292      * @param array $options
293      * @return void
294      */
295     public function init_analyser($options = array()) {
296         parent::init_analyser($options);
297     }