Línea 14... |
Línea 14... |
14 |
// You should have received a copy of the GNU General Public License
|
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/>.
|
15 |
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
Línea 16... |
Línea 16... |
16 |
|
16 |
|
Línea -... |
Línea 17... |
- |
|
17 |
namespace core_analytics;
|
- |
|
18 |
|
17 |
namespace core_analytics;
|
19 |
use core_analytics\tests\mlbackend_helper_trait;
|
Línea 18... |
Línea 20... |
18 |
|
20 |
|
19 |
defined('MOODLE_INTERNAL') || die();
|
21 |
defined('MOODLE_INTERNAL') || die();
|
20 |
|
22 |
|
Línea 29... |
Línea 31... |
29 |
* @package core_analytics
|
31 |
* @package core_analytics
|
30 |
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
32 |
* @copyright 2017 David Monllaó {@link http://www.davidmonllao.com}
|
31 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
33 |
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
32 |
* @covers \core_analytics\manager
|
34 |
* @covers \core_analytics\manager
|
33 |
*/
|
35 |
*/
|
34 |
class manager_test extends \advanced_testcase {
|
36 |
final class manager_test extends \advanced_testcase {
|
- |
|
37 |
use mlbackend_helper_trait;
|
Línea 35... |
Línea 38... |
35 |
|
38 |
|
36 |
/**
|
39 |
/**
|
37 |
* test_deleted_context
|
40 |
* test_deleted_context
|
38 |
*/
|
41 |
*/
|
39 |
public function test_deleted_context(): void {
|
42 |
public function test_deleted_context(): void {
|
Línea -... |
Línea 43... |
- |
|
43 |
global $DB;
|
- |
|
44 |
|
- |
|
45 |
if (!self::is_mlbackend_python_configured()) {
|
- |
|
46 |
$this->markTestSkipped('mlbackend_python is not configured.');
|
40 |
global $DB;
|
47 |
}
|
41 |
|
48 |
|
42 |
$this->resetAfterTest(true);
|
49 |
$this->resetAfterTest(true);
|
Línea -... |
Línea 50... |
- |
|
50 |
$this->setAdminuser();
|
- |
|
51 |
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
- |
|
52 |
|
- |
|
53 |
// Create some courses.
|
43 |
$this->setAdminuser();
|
54 |
$this->generate_courses(2, ['visible' => 0]);
|
44 |
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
55 |
$this->generate_courses(2, ['visible' => 1]);
|
45 |
|
56 |
|
46 |
$target = \core_analytics\manager::get_target('test_target_course_level_shortname');
|
57 |
$target = \core_analytics\manager::get_target('test_target_course_level_shortname');
|
47 |
$indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
|
58 |
$indicators = ['test_indicator_max', 'test_indicator_min', 'test_indicator_fullname'];
|
Línea 48... |
Línea 59... |
48 |
foreach ($indicators as $key => $indicator) {
|
59 |
foreach ($indicators as $key => $indicator) {
|
49 |
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
|
60 |
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
|
Línea 50... |
Línea -... |
50 |
}
|
- |
|
51 |
|
- |
|
52 |
$model = \core_analytics\model::create($target, $indicators);
|
- |
|
53 |
$modelobj = $model->get_model_obj();
|
- |
|
54 |
|
- |
|
55 |
$coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
|
61 |
}
|
Línea 56... |
Línea 62... |
56 |
$coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
|
62 |
|
57 |
$coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
|
63 |
$model = \core_analytics\model::create($target, $indicators);
|
Línea 58... |
Línea 64... |
58 |
$coursetrain2 = $this->getDataGenerator()->create_course(array('visible' => 1));
|
64 |
$modelobj = $model->get_model_obj();
|
59 |
|
65 |
|
60 |
$model->enable('\core\analytics\time_splitting\no_splitting');
|
66 |
$model->enable('\core\analytics\time_splitting\no_splitting');
|
61 |
|
67 |
|
62 |
$model->train();
|
68 |
$model->train();
|
Línea 63... |
Línea 69... |
63 |
$model->predict();
|
69 |
$model->predict();
|
Línea 64... |
Línea 70... |
64 |
|
70 |
|
65 |
// Generate a prediction action to confirm that it is deleted when there is an important update.
|
71 |
// Generate a prediction action to confirm that it is deleted when there is an important update.
|
- |
|
72 |
$predictions = $DB->get_records('analytics_predictions');
|
66 |
$predictions = $DB->get_records('analytics_predictions');
|
73 |
$prediction = reset($predictions);
|
- |
|
74 |
$prediction = new \core_analytics\prediction($prediction, ['whatever' => 'not used']);
|
67 |
$prediction = reset($predictions);
|
75 |
$prediction->action_executed(\core_analytics\prediction::ACTION_USEFUL, $model->get_target());
|
Línea 68... |
Línea 76... |
68 |
$prediction = new \core_analytics\prediction($prediction, array('whatever' => 'not used'));
|
76 |
|
Línea 69... |
Línea 77... |
69 |
$prediction->action_executed(\core_analytics\prediction::ACTION_USEFUL, $model->get_target());
|
77 |
$predictioncontextid = $prediction->get_prediction_data()->contextid;
|
70 |
|
78 |
|
- |
|
79 |
$npredictions = $DB->count_records('analytics_predictions', ['contextid' => $predictioncontextid]);
|
71 |
$predictioncontextid = $prediction->get_prediction_data()->contextid;
|
80 |
$npredictionactions = $DB->count_records(
|
- |
|
81 |
'analytics_prediction_actions',
|
72 |
|
82 |
['predictionid' => $prediction->get_prediction_data()->id]
|
- |
|
83 |
);
|
73 |
$npredictions = $DB->count_records('analytics_predictions', array('contextid' => $predictioncontextid));
|
84 |
$nindicatorcalc = $DB->count_records('analytics_indicator_calc', ['contextid' => $predictioncontextid]);
|
- |
|
85 |
|
74 |
$npredictionactions = $DB->count_records('analytics_prediction_actions',
|
86 |
\core_analytics\manager::cleanup();
|
- |
|
87 |
|
75 |
array('predictionid' => $prediction->get_prediction_data()->id));
|
88 |
// Nothing is incorrectly deleted.
|
- |
|
89 |
$this->assertEquals($npredictions, $DB->count_records(
|
Línea 76... |
Línea 90... |
76 |
$nindicatorcalc = $DB->count_records('analytics_indicator_calc', array('contextid' => $predictioncontextid));
|
90 |
'analytics_predictions',
|
77 |
|
91 |
['contextid' => $predictioncontextid]
|
78 |
\core_analytics\manager::cleanup();
|
92 |
));
|
Línea 79... |
Línea 93... |
79 |
|
93 |
$this->assertEquals($npredictionactions, $DB->count_records(
|
Línea 80... |
Línea 94... |
80 |
// Nothing is incorrectly deleted.
|
94 |
'analytics_prediction_actions',
|
81 |
$this->assertEquals($npredictions, $DB->count_records('analytics_predictions',
|
95 |
['predictionid' => $prediction->get_prediction_data()->id]
|
- |
|
96 |
));
|
82 |
array('contextid' => $predictioncontextid)));
|
97 |
$this->assertEquals($nindicatorcalc, $DB->count_records(
|
- |
|
98 |
'analytics_indicator_calc',
|
83 |
$this->assertEquals($npredictionactions, $DB->count_records('analytics_prediction_actions',
|
99 |
['contextid' => $predictioncontextid]
|
Línea 84... |
Línea 100... |
84 |
array('predictionid' => $prediction->get_prediction_data()->id)));
|
100 |
));
|
85 |
$this->assertEquals($nindicatorcalc, $DB->count_records('analytics_indicator_calc',
|
101 |
|
86 |
array('contextid' => $predictioncontextid)));
|
102 |
// Now we delete a context, the course predictions and prediction actions should be deleted.
|
Línea 104... |
Línea 120... |
104 |
* test_deleted_analysable
|
120 |
* test_deleted_analysable
|
105 |
*/
|
121 |
*/
|
106 |
public function test_deleted_analysable(): void {
|
122 |
public function test_deleted_analysable(): void {
|
107 |
global $DB;
|
123 |
global $DB;
|
Línea -... |
Línea 124... |
- |
|
124 |
|
- |
|
125 |
if (!self::is_mlbackend_python_configured()) {
|
- |
|
126 |
$this->markTestSkipped('mlbackend_python is not configured.');
|
- |
|
127 |
}
|
108 |
|
128 |
|
109 |
$this->resetAfterTest(true);
|
129 |
$this->resetAfterTest(true);
|
110 |
$this->setAdminuser();
|
130 |
$this->setAdminuser();
|
Línea 111... |
Línea 131... |
111 |
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
131 |
set_config('enabled_stores', 'logstore_standard', 'tool_log');
|
112 |
|
132 |
|
113 |
$target = \core_analytics\manager::get_target('test_target_course_level_shortname');
|
133 |
$target = \core_analytics\manager::get_target('test_target_course_level_shortname');
|
114 |
$indicators = array('test_indicator_max', 'test_indicator_min', 'test_indicator_fullname');
|
134 |
$indicators = ['test_indicator_max', 'test_indicator_min', 'test_indicator_fullname'];
|
115 |
foreach ($indicators as $key => $indicator) {
|
135 |
foreach ($indicators as $key => $indicator) {
|
Línea 116... |
Línea 136... |
116 |
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
|
136 |
$indicators[$key] = \core_analytics\manager::get_indicator($indicator);
|
117 |
}
|
137 |
}
|
Línea 118... |
Línea 138... |
118 |
|
138 |
|
119 |
$model = \core_analytics\model::create($target, $indicators);
|
139 |
$model = \core_analytics\model::create($target, $indicators);
|
120 |
$modelobj = $model->get_model_obj();
|
140 |
$modelobj = $model->get_model_obj();
|
121 |
|
141 |
|
Línea 122... |
Línea 142... |
122 |
$coursepredict1 = $this->getDataGenerator()->create_course(array('visible' => 0));
|
142 |
$coursepredict1 = $this->getDataGenerator()->create_course(['visible' => 0]);
|
Línea 123... |
Línea 143... |
123 |
$coursepredict2 = $this->getDataGenerator()->create_course(array('visible' => 0));
|
143 |
$coursepredict2 = $this->getDataGenerator()->create_course(['visible' => 0]);
|
124 |
$coursetrain1 = $this->getDataGenerator()->create_course(array('visible' => 1));
|
144 |
$coursetrain1 = $this->getDataGenerator()->create_course(['visible' => 1]);
|
Línea 137... |
Línea 157... |
137 |
$deletedcontext = \context_course::instance($coursepredict1->id);
|
157 |
$deletedcontext = \context_course::instance($coursepredict1->id);
|
138 |
delete_course($coursepredict1, false);
|
158 |
delete_course($coursepredict1, false);
|
Línea 139... |
Línea 159... |
139 |
|
159 |
|
Línea 140... |
Línea 160... |
140 |
\core_analytics\manager::cleanup();
|
160 |
\core_analytics\manager::cleanup();
|
141 |
|
161 |
|
142 |
$this->assertEmpty($DB->count_records('analytics_predict_samples', array('analysableid' => $coursepredict1->id)));
|
162 |
$this->assertEmpty($DB->count_records('analytics_predict_samples', ['analysableid' => $coursepredict1->id]));
|
Línea 143... |
Línea 163... |
143 |
$this->assertEmpty($DB->count_records('analytics_train_samples', array('analysableid' => $coursepredict1->id)));
|
163 |
$this->assertEmpty($DB->count_records('analytics_train_samples', ['analysableid' => $coursepredict1->id]));
|
144 |
$this->assertEmpty($DB->count_records('analytics_used_analysables', array('analysableid' => $coursepredict1->id)));
|
164 |
$this->assertEmpty($DB->count_records('analytics_used_analysables', ['analysableid' => $coursepredict1->id]));
|
145 |
|
165 |
|
Línea 188... |
Línea 208... |
188 |
*/
|
208 |
*/
|
189 |
public function test_validate_models_declaration(): void {
|
209 |
public function test_validate_models_declaration(): void {
|
190 |
$this->resetAfterTest();
|
210 |
$this->resetAfterTest();
|
Línea 191... |
Línea 211... |
191 |
|
211 |
|
192 |
// This is expected to run without an exception.
|
212 |
// This is expected to run without an exception.
|
193 |
$models = $this->load_models_from_fixture_file('no_teaching');
|
213 |
$models = self::load_models_from_fixture_file('no_teaching');
|
194 |
\core_analytics\manager::validate_models_declaration($models);
|
214 |
\core_analytics\manager::validate_models_declaration($models);
|
Línea 195... |
Línea 215... |
195 |
}
|
215 |
}
|
196 |
|
216 |
|
Línea 212... |
Línea 232... |
212 |
/**
|
232 |
/**
|
213 |
* Data provider for the {@link self::test_validate_models_declaration_exceptions()}.
|
233 |
* Data provider for the {@link self::test_validate_models_declaration_exceptions()}.
|
214 |
*
|
234 |
*
|
215 |
* @return array of (string)testcase => [(array)models, (string)expected exception message]
|
235 |
* @return array of (string)testcase => [(array)models, (string)expected exception message]
|
216 |
*/
|
236 |
*/
|
217 |
public function validate_models_declaration_exceptions_provider() {
|
237 |
public static function validate_models_declaration_exceptions_provider(): array {
|
218 |
return [
|
238 |
return [
|
219 |
'missing_target' => [
|
239 |
'missing_target' => [
|
220 |
$this->load_models_from_fixture_file('missing_target'),
|
240 |
self::load_models_from_fixture_file('missing_target'),
|
221 |
'Missing target declaration',
|
241 |
'Missing target declaration',
|
222 |
],
|
242 |
],
|
223 |
'invalid_target' => [
|
243 |
'invalid_target' => [
|
224 |
$this->load_models_from_fixture_file('invalid_target'),
|
244 |
self::load_models_from_fixture_file('invalid_target'),
|
225 |
'Invalid target classname',
|
245 |
'Invalid target classname',
|
226 |
],
|
246 |
],
|
227 |
'missing_indicators' => [
|
247 |
'missing_indicators' => [
|
228 |
$this->load_models_from_fixture_file('missing_indicators'),
|
248 |
self::load_models_from_fixture_file('missing_indicators'),
|
229 |
'Missing indicators declaration',
|
249 |
'Missing indicators declaration',
|
230 |
],
|
250 |
],
|
231 |
'invalid_indicators' => [
|
251 |
'invalid_indicators' => [
|
232 |
$this->load_models_from_fixture_file('invalid_indicators'),
|
252 |
self::load_models_from_fixture_file('invalid_indicators'),
|
233 |
'Invalid indicator classname',
|
253 |
'Invalid indicator classname',
|
234 |
],
|
254 |
],
|
235 |
'invalid_time_splitting' => [
|
255 |
'invalid_time_splitting' => [
|
236 |
$this->load_models_from_fixture_file('invalid_time_splitting'),
|
256 |
self::load_models_from_fixture_file('invalid_time_splitting'),
|
237 |
'Invalid time splitting classname',
|
257 |
'Invalid time splitting classname',
|
238 |
],
|
258 |
],
|
239 |
'invalid_time_splitting_fq' => [
|
259 |
'invalid_time_splitting_fq' => [
|
240 |
$this->load_models_from_fixture_file('invalid_time_splitting_fq'),
|
260 |
self::load_models_from_fixture_file('invalid_time_splitting_fq'),
|
241 |
'Expecting fully qualified time splitting classname',
|
261 |
'Expecting fully qualified time splitting classname',
|
242 |
],
|
262 |
],
|
243 |
'invalid_enabled' => [
|
263 |
'invalid_enabled' => [
|
244 |
$this->load_models_from_fixture_file('invalid_enabled'),
|
264 |
self::load_models_from_fixture_file('invalid_enabled'),
|
245 |
'Cannot enable a model without time splitting method specified',
|
265 |
'Cannot enable a model without time splitting method specified',
|
246 |
],
|
266 |
],
|
247 |
];
|
267 |
];
|
248 |
}
|
268 |
}
|
Línea 251... |
Línea 271... |
251 |
* Loads models as declared in the given fixture file.
|
271 |
* Loads models as declared in the given fixture file.
|
252 |
*
|
272 |
*
|
253 |
* @param string $filename
|
273 |
* @param string $filename
|
254 |
* @return array
|
274 |
* @return array
|
255 |
*/
|
275 |
*/
|
256 |
protected function load_models_from_fixture_file(string $filename) {
|
276 |
protected static function load_models_from_fixture_file(string $filename) {
|
257 |
global $CFG;
|
277 |
global $CFG;
|
Línea 258... |
Línea 278... |
258 |
|
278 |
|
Línea 259... |
Línea 279... |
259 |
$models = null;
|
279 |
$models = null;
|
Línea 260... |
Línea 280... |
260 |
|
280 |
|
261 |
require($CFG->dirroot.'/analytics/tests/fixtures/db_analytics_php/'.$filename.'.php');
|
281 |
require("{$CFG->dirroot}/analytics/tests/fixtures/db_analytics_php/{$filename}.php");
|
Línea 262... |
Línea 282... |
262 |
|
282 |
|
Línea 428... |
Línea 448... |
428 |
/**
|
448 |
/**
|
429 |
* Test the implementation of the {@link \core_analytics\manager::model_declaration_identifier()}.
|
449 |
* Test the implementation of the {@link \core_analytics\manager::model_declaration_identifier()}.
|
430 |
*/
|
450 |
*/
|
431 |
public function test_model_declaration_identifier(): void {
|
451 |
public function test_model_declaration_identifier(): void {
|
Línea 432... |
Línea 452... |
432 |
|
452 |
|
433 |
$noteaching1 = $this->load_models_from_fixture_file('no_teaching');
|
453 |
$noteaching1 = self::load_models_from_fixture_file('no_teaching');
|
434 |
$noteaching2 = $this->load_models_from_fixture_file('no_teaching');
|
454 |
$noteaching2 = self::load_models_from_fixture_file('no_teaching');
|
Línea 435... |
Línea 455... |
435 |
$noteaching3 = $this->load_models_from_fixture_file('no_teaching');
|
455 |
$noteaching3 = self::load_models_from_fixture_file('no_teaching');
|
436 |
|
456 |
|
437 |
// Same model declaration should always lead to same identifier.
|
457 |
// Same model declaration should always lead to same identifier.
|
438 |
$this->assertEquals(
|
458 |
$this->assertEquals(
|
Línea 472... |
Línea 492... |
472 |
* Tests for the {@link \core_analytics\manager::get_declared_target_and_indicators_instances()}.
|
492 |
* Tests for the {@link \core_analytics\manager::get_declared_target_and_indicators_instances()}.
|
473 |
*/
|
493 |
*/
|
474 |
public function test_get_declared_target_and_indicators_instances(): void {
|
494 |
public function test_get_declared_target_and_indicators_instances(): void {
|
475 |
$this->resetAfterTest();
|
495 |
$this->resetAfterTest();
|
Línea 476... |
Línea 496... |
476 |
|
496 |
|
Línea 477... |
Línea 497... |
477 |
$definition = $this->load_models_from_fixture_file('no_teaching');
|
497 |
$definition = self::load_models_from_fixture_file('no_teaching');
|
Línea 478... |
Línea 498... |
478 |
|
498 |
|
479 |
list($target, $indicators) = \core_analytics\manager::get_declared_target_and_indicators_instances($definition[0]);
|
499 |
[$target, $indicators] = \core_analytics\manager::get_declared_target_and_indicators_instances($definition[0]);
|
480 |
|
500 |
|
481 |
$this->assertTrue($target instanceof \core_analytics\local\target\base);
|
501 |
$this->assertTrue($target instanceof \core_analytics\local\target\base);
|