Merge branch 'MDL-64151-master' of git://github.com/marinaglancy/moodle
[moodle.git] / lib / tests / upgradelib_test.php
blob61c29af3b0e5a11dd333d6b58d4367d731ff6564
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.
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 lib/upgradelib.php library.
20 * @package core
21 * @category phpunit
22 * @copyright 2013 onwards Eloy Lafuente (stronk7) {@link http://stronk7.com}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
28 global $CFG;
29 require_once($CFG->libdir.'/upgradelib.php');
30 require_once($CFG->libdir.'/db/upgradelib.php');
32 /**
33 * Tests various classes and functions in upgradelib.php library.
35 class core_upgradelib_testcase extends advanced_testcase {
37 /**
38 * Test the {@link upgrade_stale_php_files_present() function
40 public function test_upgrade_stale_php_files_present() {
41 // Just call the function, must return bool false always
42 // if there aren't any old files in the codebase.
43 $this->assertFalse(upgrade_stale_php_files_present());
46 /**
47 * Populate some fake grade items into the database with specified
48 * sortorder and course id.
50 * NOTE: This function doesn't make much attempt to respect the
51 * gradebook internals, its simply used to fake some data for
52 * testing the upgradelib function. Please don't use it for other
53 * purposes.
55 * @param int $courseid id of course
56 * @param int $sortorder numeric sorting order of item
57 * @return stdClass grade item object from the database.
59 private function insert_fake_grade_item_sortorder($courseid, $sortorder) {
60 global $DB, $CFG;
61 require_once($CFG->libdir.'/gradelib.php');
63 $item = new stdClass();
64 $item->courseid = $courseid;
65 $item->sortorder = $sortorder;
66 $item->gradetype = GRADE_TYPE_VALUE;
67 $item->grademin = 30;
68 $item->grademax = 110;
69 $item->itemnumber = 1;
70 $item->iteminfo = '';
71 $item->timecreated = time();
72 $item->timemodified = time();
74 $item->id = $DB->insert_record('grade_items', $item);
76 return $DB->get_record('grade_items', array('id' => $item->id));
79 public function test_upgrade_extra_credit_weightoverride() {
80 global $DB, $CFG;
82 $this->resetAfterTest(true);
84 require_once($CFG->libdir . '/db/upgradelib.php');
86 $c = array();
87 $a = array();
88 $gi = array();
89 for ($i=0; $i<5; $i++) {
90 $c[$i] = $this->getDataGenerator()->create_course();
91 $a[$i] = array();
92 $gi[$i] = array();
93 for ($j=0;$j<3;$j++) {
94 $a[$i][$j] = $this->getDataGenerator()->create_module('assign', array('course' => $c[$i], 'grade' => 100));
95 $giparams = array('itemtype' => 'mod', 'itemmodule' => 'assign', 'iteminstance' => $a[$i][$j]->id,
96 'courseid' => $c[$i]->id, 'itemnumber' => 0);
97 $gi[$i][$j] = grade_item::fetch($giparams);
101 // Case 1: Course $c[0] has aggregation method different from natural.
102 $coursecategory = grade_category::fetch_course_category($c[0]->id);
103 $coursecategory->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN;
104 $coursecategory->update();
105 $gi[0][1]->aggregationcoef = 1;
106 $gi[0][1]->update();
107 $gi[0][2]->weightoverride = 1;
108 $gi[0][2]->update();
110 // Case 2: Course $c[1] has neither extra credits nor overrides
112 // Case 3: Course $c[2] has extra credits but no overrides
113 $gi[2][1]->aggregationcoef = 1;
114 $gi[2][1]->update();
116 // Case 4: Course $c[3] has no extra credits and has overrides
117 $gi[3][2]->weightoverride = 1;
118 $gi[3][2]->update();
120 // Case 5: Course $c[4] has both extra credits and overrides
121 $gi[4][1]->aggregationcoef = 1;
122 $gi[4][1]->update();
123 $gi[4][2]->weightoverride = 1;
124 $gi[4][2]->update();
126 // Run the upgrade script and make sure only course $c[4] was marked as needed to be fixed.
127 upgrade_extra_credit_weightoverride();
129 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[0]->id}));
130 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[1]->id}));
131 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[2]->id}));
132 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[3]->id}));
133 $this->assertEquals(20150619, $CFG->{'gradebook_calculations_freeze_' . $c[4]->id});
135 set_config('gradebook_calculations_freeze_' . $c[4]->id, null);
137 // Run the upgrade script for a single course only.
138 upgrade_extra_credit_weightoverride($c[0]->id);
139 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $c[0]->id}));
140 upgrade_extra_credit_weightoverride($c[4]->id);
141 $this->assertEquals(20150619, $CFG->{'gradebook_calculations_freeze_' . $c[4]->id});
145 * Test the upgrade function for flagging courses with calculated grade item problems.
147 public function test_upgrade_calculated_grade_items_freeze() {
148 global $DB, $CFG;
150 $this->resetAfterTest();
152 require_once($CFG->libdir . '/db/upgradelib.php');
154 // Create a user.
155 $user = $this->getDataGenerator()->create_user();
157 // Create a couple of courses.
158 $course1 = $this->getDataGenerator()->create_course();
159 $course2 = $this->getDataGenerator()->create_course();
160 $course3 = $this->getDataGenerator()->create_course();
162 // Enrol the user in the courses.
163 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
164 $maninstance1 = $DB->get_record('enrol', array('courseid' => $course1->id, 'enrol' => 'manual'), '*', MUST_EXIST);
165 $maninstance2 = $DB->get_record('enrol', array('courseid' => $course2->id, 'enrol' => 'manual'), '*', MUST_EXIST);
166 $maninstance3 = $DB->get_record('enrol', array('courseid' => $course3->id, 'enrol' => 'manual'), '*', MUST_EXIST);
167 $manual = enrol_get_plugin('manual');
168 $manual->enrol_user($maninstance1, $user->id, $studentrole->id);
169 $manual->enrol_user($maninstance2, $user->id, $studentrole->id);
170 $manual->enrol_user($maninstance3, $user->id, $studentrole->id);
172 // To create the data we need we freeze the grade book to use the old behaviour.
173 set_config('gradebook_calculations_freeze_' . $course1->id, 20150627);
174 set_config('gradebook_calculations_freeze_' . $course2->id, 20150627);
175 set_config('gradebook_calculations_freeze_' . $course3->id, 20150627);
176 $CFG->grade_minmaxtouse = 2;
178 // Creating a category for a grade item.
179 $gradecategory = new grade_category();
180 $gradecategory->fullname = 'calculated grade category';
181 $gradecategory->courseid = $course1->id;
182 $gradecategory->insert();
183 $gradecategoryid = $gradecategory->id;
185 // This is a manual grade item.
186 $gradeitem = new grade_item();
187 $gradeitem->itemname = 'grade item one';
188 $gradeitem->itemtype = 'manual';
189 $gradeitem->categoryid = $gradecategoryid;
190 $gradeitem->courseid = $course1->id;
191 $gradeitem->idnumber = 'gi1';
192 $gradeitem->insert();
194 // Changing the category into a calculated grade category.
195 $gradecategoryitem = grade_item::fetch(array('iteminstance' => $gradecategory->id));
196 $gradecategoryitem->calculation = '=##gi' . $gradeitem->id . '##/2';
197 $gradecategoryitem->update();
199 // Setting a grade for the student.
200 $grade = $gradeitem->get_grade($user->id, true);
201 $grade->finalgrade = 50;
202 $grade->update();
203 // Creating all the grade_grade items.
204 grade_regrade_final_grades($course1->id);
205 // Updating the grade category to a new grade max and min.
206 $gradecategoryitem->grademax = 50;
207 $gradecategoryitem->grademin = 5;
208 $gradecategoryitem->update();
210 // Different manual grade item for course 2. We are creating a course with a calculated grade item that has a grade max of
211 // 50. The grade_grade will have a rawgrademax of 100 regardless.
212 $gradeitem = new grade_item();
213 $gradeitem->itemname = 'grade item one';
214 $gradeitem->itemtype = 'manual';
215 $gradeitem->courseid = $course2->id;
216 $gradeitem->idnumber = 'gi1';
217 $gradeitem->grademax = 25;
218 $gradeitem->insert();
220 // Calculated grade item for course 2.
221 $calculatedgradeitem = new grade_item();
222 $calculatedgradeitem->itemname = 'calculated grade';
223 $calculatedgradeitem->itemtype = 'manual';
224 $calculatedgradeitem->courseid = $course2->id;
225 $calculatedgradeitem->calculation = '=##gi' . $gradeitem->id . '##*2';
226 $calculatedgradeitem->grademax = 50;
227 $calculatedgradeitem->insert();
229 // Assigning a grade for the user.
230 $grade = $gradeitem->get_grade($user->id, true);
231 $grade->finalgrade = 10;
232 $grade->update();
234 // Setting all of the grade_grade items.
235 grade_regrade_final_grades($course2->id);
237 // Different manual grade item for course 3. We are creating a course with a calculated grade item that has a grade max of
238 // 50. The grade_grade will have a rawgrademax of 100 regardless.
239 $gradeitem = new grade_item();
240 $gradeitem->itemname = 'grade item one';
241 $gradeitem->itemtype = 'manual';
242 $gradeitem->courseid = $course3->id;
243 $gradeitem->idnumber = 'gi1';
244 $gradeitem->grademax = 25;
245 $gradeitem->insert();
247 // Calculated grade item for course 2.
248 $calculatedgradeitem = new grade_item();
249 $calculatedgradeitem->itemname = 'calculated grade';
250 $calculatedgradeitem->itemtype = 'manual';
251 $calculatedgradeitem->courseid = $course3->id;
252 $calculatedgradeitem->calculation = '=##gi' . $gradeitem->id . '##*2';
253 $calculatedgradeitem->grademax = 50;
254 $calculatedgradeitem->insert();
256 // Assigning a grade for the user.
257 $grade = $gradeitem->get_grade($user->id, true);
258 $grade->finalgrade = 10;
259 $grade->update();
261 // Setting all of the grade_grade items.
262 grade_regrade_final_grades($course3->id);
263 // Need to do this first before changing the other courses, otherwise they will be flagged too early.
264 set_config('gradebook_calculations_freeze_' . $course3->id, null);
265 upgrade_calculated_grade_items($course3->id);
266 $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course3->id});
268 // Change the setting back to null.
269 set_config('gradebook_calculations_freeze_' . $course1->id, null);
270 set_config('gradebook_calculations_freeze_' . $course2->id, null);
271 // Run the upgrade.
272 upgrade_calculated_grade_items();
273 // The setting should be set again after the upgrade.
274 $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course1->id});
275 $this->assertEquals(20150627, $CFG->{'gradebook_calculations_freeze_' . $course2->id});
278 function test_upgrade_calculated_grade_items_regrade() {
279 global $DB, $CFG;
281 $this->resetAfterTest();
283 require_once($CFG->libdir . '/db/upgradelib.php');
285 // Create a user.
286 $user = $this->getDataGenerator()->create_user();
288 // Create a course.
289 $course = $this->getDataGenerator()->create_course();
291 // Enrol the user in the course.
292 $studentrole = $DB->get_record('role', array('shortname' => 'student'));
293 $maninstance1 = $DB->get_record('enrol', array('courseid' => $course->id, 'enrol' => 'manual'), '*', MUST_EXIST);
294 $manual = enrol_get_plugin('manual');
295 $manual->enrol_user($maninstance1, $user->id, $studentrole->id);
297 set_config('upgrade_calculatedgradeitemsonlyregrade', 1);
299 // Creating a category for a grade item.
300 $gradecategory = new grade_category();
301 $gradecategory->fullname = 'calculated grade category';
302 $gradecategory->courseid = $course->id;
303 $gradecategory->insert();
304 $gradecategoryid = $gradecategory->id;
306 // This is a manual grade item.
307 $gradeitem = new grade_item();
308 $gradeitem->itemname = 'grade item one';
309 $gradeitem->itemtype = 'manual';
310 $gradeitem->categoryid = $gradecategoryid;
311 $gradeitem->courseid = $course->id;
312 $gradeitem->idnumber = 'gi1';
313 $gradeitem->insert();
315 // Changing the category into a calculated grade category.
316 $gradecategoryitem = grade_item::fetch(array('iteminstance' => $gradecategory->id));
317 $gradecategoryitem->calculation = '=##gi' . $gradeitem->id . '##/2';
318 $gradecategoryitem->grademax = 50;
319 $gradecategoryitem->grademin = 15;
320 $gradecategoryitem->update();
322 // Setting a grade for the student.
323 $grade = $gradeitem->get_grade($user->id, true);
324 $grade->finalgrade = 50;
325 $grade->update();
327 grade_regrade_final_grades($course->id);
328 $grade = grade_grade::fetch(array('itemid' => $gradecategoryitem->id, 'userid' => $user->id));
329 $grade->rawgrademax = 100;
330 $grade->rawgrademin = 0;
331 $grade->update();
332 $this->assertNotEquals($gradecategoryitem->grademax, $grade->rawgrademax);
333 $this->assertNotEquals($gradecategoryitem->grademin, $grade->rawgrademin);
335 // This is the function that we are testing. If we comment out this line, then the test fails because the grade items
336 // are not flagged for regrading.
337 upgrade_calculated_grade_items();
338 grade_regrade_final_grades($course->id);
340 $grade = grade_grade::fetch(array('itemid' => $gradecategoryitem->id, 'userid' => $user->id));
342 $this->assertEquals($gradecategoryitem->grademax, $grade->rawgrademax);
343 $this->assertEquals($gradecategoryitem->grademin, $grade->rawgrademin);
347 * Test that the upgrade script correctly flags courses to be frozen due to letter boundary problems.
349 public function test_upgrade_course_letter_boundary() {
350 global $CFG, $DB;
351 $this->resetAfterTest(true);
353 require_once($CFG->libdir . '/db/upgradelib.php');
355 // Create a user.
356 $user = $this->getDataGenerator()->create_user();
358 // Create some courses.
359 $courses = array();
360 $contexts = array();
361 for ($i = 0; $i < 45; $i++) {
362 $course = $this->getDataGenerator()->create_course();
363 $context = context_course::instance($course->id);
364 if (in_array($i, array(2, 5, 10, 13, 14, 19, 23, 25, 30, 34, 36))) {
365 // Assign good letter boundaries.
366 $this->assign_good_letter_boundary($context->id);
368 if (in_array($i, array(3, 6, 11, 15, 20, 24, 26, 31, 35))) {
369 // Assign bad letter boundaries.
370 $this->assign_bad_letter_boundary($context->id);
373 if (in_array($i, array(3, 9, 10, 11, 18, 19, 20, 29, 30, 31, 40))) {
374 grade_set_setting($course->id, 'displaytype', '3');
375 } else if (in_array($i, array(8, 17, 28))) {
376 grade_set_setting($course->id, 'displaytype', '2');
379 if (in_array($i, array(37, 43))) {
380 // Show.
381 grade_set_setting($course->id, 'report_user_showlettergrade', '1');
382 } else if (in_array($i, array(38, 42))) {
383 // Hide.
384 grade_set_setting($course->id, 'report_user_showlettergrade', '0');
387 $assignrow = $this->getDataGenerator()->create_module('assign', array('course' => $course->id, 'name' => 'Test!'));
388 $gi = grade_item::fetch(
389 array('itemtype' => 'mod',
390 'itemmodule' => 'assign',
391 'iteminstance' => $assignrow->id,
392 'courseid' => $course->id));
393 if (in_array($i, array(6, 13, 14, 15, 23, 24, 34, 35, 36, 41))) {
394 grade_item::set_properties($gi, array('display' => 3));
395 $gi->update();
396 } else if (in_array($i, array(12, 21, 32))) {
397 grade_item::set_properties($gi, array('display' => 2));
398 $gi->update();
400 $gradegrade = new grade_grade();
401 $gradegrade->itemid = $gi->id;
402 $gradegrade->userid = $user->id;
403 $gradegrade->rawgrade = 55.5563;
404 $gradegrade->finalgrade = 55.5563;
405 $gradegrade->rawgrademax = 100;
406 $gradegrade->rawgrademin = 0;
407 $gradegrade->timecreated = time();
408 $gradegrade->timemodified = time();
409 $gradegrade->insert();
411 $contexts[] = $context;
412 $courses[] = $course;
415 upgrade_course_letter_boundary();
417 // No system setting for grade letter boundaries.
418 // [0] A course with no letter boundaries.
419 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[0]->id}));
420 // [1] A course with letter boundaries which are default.
421 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[1]->id}));
422 // [2] A course with letter boundaries which are custom but not affected.
423 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[2]->id}));
424 // [3] A course with letter boundaries which are custom and will be affected.
425 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[3]->id});
426 // [4] A course with no letter boundaries, but with a grade item with letter boundaries which are default.
427 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[4]->id}));
428 // [5] A course with no letter boundaries, but with a grade item with letter boundaries which are not default, but not affected.
429 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[5]->id}));
430 // [6] A course with no letter boundaries, but with a grade item with letter boundaries which are not default which will be affected.
431 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[6]->id});
433 // System setting for grade letter boundaries (default).
434 set_config('grade_displaytype', '3');
435 for ($i = 0; $i < 45; $i++) {
436 unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
438 upgrade_course_letter_boundary();
440 // [7] A course with no grade display settings for the course or grade items.
441 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[7]->id}));
442 // [8] A course with grade display settings, but for something that isn't letters.
443 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[8]->id}));
444 // [9] A course with grade display settings of letters which are default.
445 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[9]->id}));
446 // [10] A course with grade display settings of letters which are not default, but not affected.
447 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[10]->id}));
448 // [11] A course with grade display settings of letters which are not default, which will be affected.
449 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[11]->id});
450 // [12] A grade item with display settings that are not letters.
451 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[12]->id}));
452 // [13] A grade item with display settings of letters which are default.
453 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[13]->id}));
454 // [14] A grade item with display settings of letters which are not default, but not affected.
455 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[14]->id}));
456 // [15] A grade item with display settings of letters which are not default, which will be affected.
457 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[15]->id});
459 // System setting for grade letter boundaries (custom with problem).
460 $systemcontext = context_system::instance();
461 $this->assign_bad_letter_boundary($systemcontext->id);
462 for ($i = 0; $i < 45; $i++) {
463 unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
465 upgrade_course_letter_boundary();
467 // [16] A course with no grade display settings for the course or grade items.
468 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[16]->id});
469 // [17] A course with grade display settings, but for something that isn't letters.
470 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[17]->id}));
471 // [18] A course with grade display settings of letters which are default.
472 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[18]->id});
473 // [19] A course with grade display settings of letters which are not default, but not affected.
474 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[19]->id}));
475 // [20] A course with grade display settings of letters which are not default, which will be affected.
476 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[20]->id});
477 // [21] A grade item with display settings which are not letters. Grade total will be affected so should be frozen.
478 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[21]->id});
479 // [22] A grade item with display settings of letters which are default.
480 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[22]->id});
481 // [23] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting.
482 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[23]->id}));
483 // [24] A grade item with display settings of letters which are not default, which will be affected.
484 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[24]->id});
485 // [25] A course which is using the default grade display setting, but has updated the grade letter boundary (not 57) Should not be frozen.
486 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[25]->id}));
487 // [26] A course that is using the default display setting (letters) and altered the letter boundary with 57. Should be frozen.
488 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[26]->id});
490 // System setting not showing letters.
491 set_config('grade_displaytype', '2');
492 for ($i = 0; $i < 45; $i++) {
493 unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
495 upgrade_course_letter_boundary();
497 // [27] A course with no grade display settings for the course or grade items.
498 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[27]->id}));
499 // [28] A course with grade display settings, but for something that isn't letters.
500 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[28]->id}));
501 // [29] A course with grade display settings of letters which are default.
502 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[29]->id});
503 // [30] A course with grade display settings of letters which are not default, but not affected.
504 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[30]->id}));
505 // [31] A course with grade display settings of letters which are not default, which will be affected.
506 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[31]->id});
507 // [32] A grade item with display settings which are not letters.
508 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[32]->id}));
509 // [33] All system defaults.
510 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[33]->id}));
511 // [34] A grade item with display settings of letters which are not default, but not affected. Course uses new letter boundary setting.
512 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[34]->id}));
513 // [35] A grade item with display settings of letters which are not default, which will be affected.
514 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[35]->id});
515 // [36] A course with grade display settings of letters with modified and good boundary (not 57) Should not be frozen.
516 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[36]->id}));
518 // Previous site conditions still exist.
519 for ($i = 0; $i < 45; $i++) {
520 unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
522 upgrade_course_letter_boundary();
524 // [37] Site setting for not showing the letter column and course setting set to show (frozen).
525 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[37]->id});
526 // [38] Site setting for not showing the letter column and course setting set to hide.
527 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[38]->id}));
528 // [39] Site setting for not showing the letter column and course setting set to default.
529 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[39]->id}));
530 // [40] Site setting for not showing the letter column and course setting set to default. Course display set to letters (frozen).
531 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[40]->id});
532 // [41] Site setting for not showing the letter column and course setting set to default. Grade item display set to letters (frozen).
533 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[41]->id});
535 // Previous site conditions still exist.
536 for ($i = 0; $i < 45; $i++) {
537 unset_config('gradebook_calculations_freeze_' . $courses[$i]->id);
539 set_config('grade_report_user_showlettergrade', '1');
540 upgrade_course_letter_boundary();
542 // [42] Site setting for showing the letter column, but course setting set to hide.
543 $this->assertTrue(empty($CFG->{'gradebook_calculations_freeze_' . $courses[42]->id}));
544 // [43] Site setting for showing the letter column and course setting set to show (frozen).
545 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[43]->id});
546 // [44] Site setting for showing the letter column and course setting set to default (frozen).
547 $this->assertEquals(20160518, $CFG->{'gradebook_calculations_freeze_' . $courses[44]->id});
551 * Test upgrade_letter_boundary_needs_freeze function.
553 public function test_upgrade_letter_boundary_needs_freeze() {
554 global $CFG;
556 $this->resetAfterTest();
558 require_once($CFG->libdir . '/db/upgradelib.php');
560 $courses = array();
561 $contexts = array();
562 for ($i = 0; $i < 3; $i++) {
563 $courses[] = $this->getDataGenerator()->create_course();
564 $contexts[] = context_course::instance($courses[$i]->id);
567 // Course one is not using a letter boundary.
568 $this->assertFalse(upgrade_letter_boundary_needs_freeze($contexts[0]));
570 // Let's make course 2 use the bad boundary.
571 $this->assign_bad_letter_boundary($contexts[1]->id);
572 $this->assertTrue(upgrade_letter_boundary_needs_freeze($contexts[1]));
573 // Course 3 has letter boundaries that are fine.
574 $this->assign_good_letter_boundary($contexts[2]->id);
575 $this->assertFalse(upgrade_letter_boundary_needs_freeze($contexts[2]));
576 // Try the system context not using a letter boundary.
577 $systemcontext = context_system::instance();
578 $this->assertFalse(upgrade_letter_boundary_needs_freeze($systemcontext));
582 * Assigns letter boundaries with comparison problems.
584 * @param int $contextid Context ID.
586 private function assign_bad_letter_boundary($contextid) {
587 global $DB;
588 $newlettersscale = array(
589 array('contextid' => $contextid, 'lowerboundary' => 90.00000, 'letter' => 'A'),
590 array('contextid' => $contextid, 'lowerboundary' => 85.00000, 'letter' => 'A-'),
591 array('contextid' => $contextid, 'lowerboundary' => 80.00000, 'letter' => 'B+'),
592 array('contextid' => $contextid, 'lowerboundary' => 75.00000, 'letter' => 'B'),
593 array('contextid' => $contextid, 'lowerboundary' => 70.00000, 'letter' => 'B-'),
594 array('contextid' => $contextid, 'lowerboundary' => 65.00000, 'letter' => 'C+'),
595 array('contextid' => $contextid, 'lowerboundary' => 57.00000, 'letter' => 'C'),
596 array('contextid' => $contextid, 'lowerboundary' => 50.00000, 'letter' => 'C-'),
597 array('contextid' => $contextid, 'lowerboundary' => 40.00000, 'letter' => 'D+'),
598 array('contextid' => $contextid, 'lowerboundary' => 25.00000, 'letter' => 'D'),
599 array('contextid' => $contextid, 'lowerboundary' => 0.00000, 'letter' => 'F'),
602 $DB->delete_records('grade_letters', array('contextid' => $contextid));
603 foreach ($newlettersscale as $record) {
604 // There is no API to do this, so we have to manually insert into the database.
605 $DB->insert_record('grade_letters', $record);
610 * Assigns letter boundaries with no comparison problems.
612 * @param int $contextid Context ID.
614 private function assign_good_letter_boundary($contextid) {
615 global $DB;
616 $newlettersscale = array(
617 array('contextid' => $contextid, 'lowerboundary' => 90.00000, 'letter' => 'A'),
618 array('contextid' => $contextid, 'lowerboundary' => 85.00000, 'letter' => 'A-'),
619 array('contextid' => $contextid, 'lowerboundary' => 80.00000, 'letter' => 'B+'),
620 array('contextid' => $contextid, 'lowerboundary' => 75.00000, 'letter' => 'B'),
621 array('contextid' => $contextid, 'lowerboundary' => 70.00000, 'letter' => 'B-'),
622 array('contextid' => $contextid, 'lowerboundary' => 65.00000, 'letter' => 'C+'),
623 array('contextid' => $contextid, 'lowerboundary' => 54.00000, 'letter' => 'C'),
624 array('contextid' => $contextid, 'lowerboundary' => 50.00000, 'letter' => 'C-'),
625 array('contextid' => $contextid, 'lowerboundary' => 40.00000, 'letter' => 'D+'),
626 array('contextid' => $contextid, 'lowerboundary' => 25.00000, 'letter' => 'D'),
627 array('contextid' => $contextid, 'lowerboundary' => 0.00000, 'letter' => 'F'),
630 $DB->delete_records('grade_letters', array('contextid' => $contextid));
631 foreach ($newlettersscale as $record) {
632 // There is no API to do this, so we have to manually insert into the database.
633 $DB->insert_record('grade_letters', $record);
638 * Test libcurl custom check api.
640 public function test_check_libcurl_version() {
641 $supportedversion = 0x071304;
642 $curlinfo = curl_version();
643 $currentversion = $curlinfo['version_number'];
645 $result = new environment_results("custom_checks");
646 if ($currentversion < $supportedversion) {
647 $this->assertFalse(check_libcurl_version($result)->getStatus());
648 } else {
649 $this->assertNull(check_libcurl_version($result));
654 * Create two pages with blocks, delete one page and make sure upgrade script deletes orphaned blocks
656 public function test_delete_block_positions() {
657 global $DB, $CFG;
658 require_once($CFG->dirroot . '/my/lib.php');
659 $this->resetAfterTest();
661 // Make sure each block on system dashboard page has a position.
662 $systempage = $DB->get_record('my_pages', array('userid' => null, 'private' => MY_PAGE_PRIVATE));
663 $systemcontext = context_system::instance();
664 $blockinstances = $DB->get_records('block_instances', array('parentcontextid' => $systemcontext->id,
665 'pagetypepattern' => 'my-index', 'subpagepattern' => $systempage->id));
666 $this->assertNotEmpty($blockinstances);
667 foreach ($blockinstances as $bi) {
668 $DB->insert_record('block_positions', ['subpage' => $systempage->id, 'pagetype' => 'my-index', 'contextid' => $systemcontext->id,
669 'blockinstanceid' => $bi->id, 'visible' => 1, 'weight' => $bi->defaultweight]);
672 // Create two users and make two copies of the system dashboard.
673 $user1 = $this->getDataGenerator()->create_user();
674 $user2 = $this->getDataGenerator()->create_user();
675 $page1 = my_copy_page($user1->id, MY_PAGE_PRIVATE, 'my-index');
676 $page2 = my_copy_page($user2->id, MY_PAGE_PRIVATE, 'my-index');
678 $context1 = context_user::instance($user1->id);
679 $context2 = context_user::instance($user2->id);
681 // Delete second page without deleting block positions.
682 $DB->delete_records('my_pages', ['id' => $page2->id]);
684 // Blocks are still here.
685 $this->assertEquals(count($blockinstances), $DB->count_records('block_positions', ['subpage' => $page1->id, 'pagetype' => 'my-index', 'contextid' => $context1->id]));
686 $this->assertEquals(count($blockinstances), $DB->count_records('block_positions', ['subpage' => $page2->id, 'pagetype' => 'my-index', 'contextid' => $context2->id]));
688 // Run upgrade script that should delete orphaned block_positions.
689 upgrade_block_positions();
691 // First user still has all his block_positions, second user does not.
692 $this->assertEquals(count($blockinstances), $DB->count_records('block_positions', ['subpage' => $page1->id, 'pagetype' => 'my-index', 'contextid' => $context1->id]));
693 $this->assertEquals(0, $DB->count_records('block_positions', ['subpage' => $page2->id, 'pagetype' => 'my-index']));
697 * Test the conversion of auth plugin settings names.
699 public function test_upgrade_fix_config_auth_plugin_names() {
700 $this->resetAfterTest();
702 // Let the plugin auth_foo use legacy format only.
703 set_config('name1', 'val1', 'auth/foo');
704 set_config('name2', 'val2', 'auth/foo');
706 // Let the plugin auth_bar use new format only.
707 set_config('name1', 'val1', 'auth_bar');
708 set_config('name2', 'val2', 'auth_bar');
710 // Let the plugin auth_baz use a mix of legacy and new format, with no conflicts.
711 set_config('name1', 'val1', 'auth_baz');
712 set_config('name1', 'val1', 'auth/baz');
713 set_config('name2', 'val2', 'auth/baz');
714 set_config('name3', 'val3', 'auth_baz');
716 // Let the plugin auth_qux use a mix of legacy and new format, with conflicts.
717 set_config('name1', 'val1', 'auth_qux');
718 set_config('name1', 'val2', 'auth/qux');
720 // Execute the migration.
721 upgrade_fix_config_auth_plugin_names('foo');
722 upgrade_fix_config_auth_plugin_names('bar');
723 upgrade_fix_config_auth_plugin_names('baz');
724 upgrade_fix_config_auth_plugin_names('qux');
726 // Assert that legacy settings are gone and no new were introduced.
727 $this->assertEmpty((array) get_config('auth/foo'));
728 $this->assertEmpty((array) get_config('auth/bar'));
729 $this->assertEmpty((array) get_config('auth/baz'));
730 $this->assertEmpty((array) get_config('auth/qux'));
732 // Assert values were simply kept where there was no conflict.
733 $this->assertSame('val1', get_config('auth_foo', 'name1'));
734 $this->assertSame('val2', get_config('auth_foo', 'name2'));
736 $this->assertSame('val1', get_config('auth_bar', 'name1'));
737 $this->assertSame('val2', get_config('auth_bar', 'name2'));
739 $this->assertSame('val1', get_config('auth_baz', 'name1'));
740 $this->assertSame('val2', get_config('auth_baz', 'name2'));
741 $this->assertSame('val3', get_config('auth_baz', 'name3'));
743 // Assert the new format took precedence in case of conflict.
744 $this->assertSame('val1', get_config('auth_qux', 'name1'));
748 * Create a collection of test themes to test determining parent themes.
750 * @return Url to the path containing the test themes
752 public function create_testthemes() {
753 global $CFG;
755 $themedircontent = [
756 'testtheme' => [
757 'config.php' => '<?php $THEME->name = "testtheme"; $THEME->parents = [""];',
759 'childoftesttheme' => [
760 'config.php' => '<?php $THEME->name = "childofboost"; $THEME->parents = ["testtheme"];',
762 'infinite' => [
763 'config.php' => '<?php $THEME->name = "infinite"; $THEME->parents = ["forever"];',
765 'forever' => [
766 'config.php' => '<?php $THEME->name = "forever"; $THEME->parents = ["infinite", "childoftesttheme"];',
768 'orphantheme' => [
769 'config.php' => '<?php $THEME->name = "orphantheme"; $THEME->parents = [];',
771 'loop' => [
772 'config.php' => '<?php $THEME->name = "loop"; $THEME->parents = ["around"];',
774 'around' => [
775 'config.php' => '<?php $THEME->name = "around"; $THEME->parents = ["loop"];',
777 'themewithbrokenparent' => [
778 'config.php' => '<?php $THEME->name = "orphantheme"; $THEME->parents = ["nonexistent", "testtheme"];',
781 $vthemedir = \org\bovigo\vfs\vfsStream::setup('themes', null, $themedircontent);
783 return \org\bovigo\vfs\vfsStream::url('themes');
787 * Test finding theme locations.
789 public function test_upgrade_find_theme_location() {
790 global $CFG;
792 $this->resetAfterTest();
794 $CFG->themedir = $this->create_testthemes();
796 $this->assertSame($CFG->dirroot . '/theme/boost', upgrade_find_theme_location('boost'));
797 $this->assertSame($CFG->dirroot . '/theme/classic', upgrade_find_theme_location('classic'));
799 $this->assertSame($CFG->themedir . '/testtheme', upgrade_find_theme_location('testtheme'));
800 $this->assertSame($CFG->themedir . '/childoftesttheme', upgrade_find_theme_location('childoftesttheme'));
804 * Test figuring out if theme is or is a child of a certain theme.
806 public function test_upgrade_theme_is_from_family() {
807 global $CFG;
809 $this->resetAfterTest();
811 $CFG->themedir = $this->create_testthemes();
813 $this->assertTrue(upgrade_theme_is_from_family('boost', 'boost'), 'Boost is a boost theme');
814 $this->assertTrue(upgrade_theme_is_from_family('boost', 'classic'), 'Classic is a boost base theme');
815 $this->assertFalse(upgrade_theme_is_from_family('classic', 'boost'), 'Boost is not a classic theme');
817 $this->assertTrue(upgrade_theme_is_from_family('testtheme', 'childoftesttheme'), 'childoftesttheme is a testtheme');
818 $this->assertFalse(upgrade_theme_is_from_family('testtheme', 'orphantheme'), 'ofphantheme is not a testtheme');
819 $this->assertTrue(upgrade_theme_is_from_family('testtheme', 'infinite'), 'Infinite loop with testtheme parent is true');
820 $this->assertFalse(upgrade_theme_is_from_family('testtheme', 'loop'), 'Infinite loop without testtheme parent is false');
821 $this->assertTrue(upgrade_theme_is_from_family('testtheme', 'themewithbrokenparent'), 'No error on broken parent');
825 * Data provider of serialized string.
827 * @return array
829 public function serialized_strings_dataprovider() {
830 return [
831 'A configuration that uses the old object' => [
832 'O:6:"object":3:{s:4:"text";s:32:"Nothing that anyone cares about.";s:5:"title";s:16:"Really old block";s:6:"format";s:1:"1";}',
833 true,
834 'O:8:"stdClass":3:{s:4:"text";s:32:"Nothing that anyone cares about.";s:5:"title";s:16:"Really old block";s:6:"format";s:1:"1";}'
836 'A configuration that uses stdClass' => [
837 'O:8:"stdClass":5:{s:5:"title";s:4:"Tags";s:12:"numberoftags";s:2:"80";s:12:"showstandard";s:1:"0";s:3:"ctx";s:3:"289";s:3:"rec";s:1:"1";}',
838 false,
839 'O:8:"stdClass":5:{s:5:"title";s:4:"Tags";s:12:"numberoftags";s:2:"80";s:12:"showstandard";s:1:"0";s:3:"ctx";s:3:"289";s:3:"rec";s:1:"1";}'
841 'A setting I saw when importing a course with blocks from 1.9' => [
842 'N;',
843 false,
844 'N;'
846 'An object in an object' => [
847 'O:6:"object":2:{s:2:"id";i:5;s:5:"other";O:6:"object":1:{s:4:"text";s:13:"something new";}}',
848 true,
849 'O:8:"stdClass":2:{s:2:"id";i:5;s:5:"other";O:8:"stdClass":1:{s:4:"text";s:13:"something new";}}'
851 'An array with an object in it' => [
852 'a:3:{s:4:"name";s:4:"Test";s:10:"additional";O:6:"object":2:{s:2:"id";i:5;s:4:"info";s:18:"text in the object";}s:4:"type";i:1;}',
853 true,
854 'a:3:{s:4:"name";s:4:"Test";s:10:"additional";O:8:"stdClass":2:{s:2:"id";i:5;s:4:"info";s:18:"text in the object";}s:4:"type";i:1;}'
860 * Test that objects in serialized strings will be changed over to stdClass.
862 * @dataProvider serialized_strings_dataprovider
863 * @param string $initialstring The initial serialized setting.
864 * @param bool $expectededited If the string is expected to be edited.
865 * @param string $expectedresult The expected serialized setting to be returned.
867 public function test_upgrade_fix_serialized_objects($initialstring, $expectededited, $expectedresult) {
868 list($edited, $resultstring) = upgrade_fix_serialized_objects($initialstring);
869 $this->assertEquals($expectededited, $edited);
870 $this->assertEquals($expectedresult, $resultstring);
874 * Data provider for base64_encoded block instance config data.
876 public function encoded_strings_dataprovider() {
877 return [
878 'Normal data using stdClass' => [
879 'Tzo4OiJzdGRDbGFzcyI6NTp7czo1OiJ0aXRsZSI7czo0OiJUYWdzIjtzOjEyOiJudW1iZXJvZnRhZ3MiO3M6MjoiODAiO3M6MTI6InNob3dzdGFuZGFyZCI7czoxOiIwIjtzOjM6ImN0eCI7czozOiIyODkiO3M6MzoicmVjIjtzOjE6IjEiO30=',
880 'Tzo4OiJzdGRDbGFzcyI6NTp7czo1OiJ0aXRsZSI7czo0OiJUYWdzIjtzOjEyOiJudW1iZXJvZnRhZ3MiO3M6MjoiODAiO3M6MTI6InNob3dzdGFuZGFyZCI7czoxOiIwIjtzOjM6ImN0eCI7czozOiIyODkiO3M6MzoicmVjIjtzOjE6IjEiO30='
882 'No data at all' => [
886 'Old data using object' => [
887 'Tzo2OiJvYmplY3QiOjM6e3M6NDoidGV4dCI7czozMjoiTm90aGluZyB0aGF0IGFueW9uZSBjYXJlcyBhYm91dC4iO3M6NToidGl0bGUiO3M6MTY6IlJlYWxseSBvbGQgYmxvY2siO3M6NjoiZm9ybWF0IjtzOjE6IjEiO30=',
888 'Tzo4OiJzdGRDbGFzcyI6Mzp7czo0OiJ0ZXh0IjtzOjMyOiJOb3RoaW5nIHRoYXQgYW55b25lIGNhcmVzIGFib3V0LiI7czo1OiJ0aXRsZSI7czoxNjoiUmVhbGx5IG9sZCBibG9jayI7czo2OiJmb3JtYXQiO3M6MToiMSI7fQ=='
894 * Check that entries in the block_instances table are coverted over correctly.
896 * @dataProvider encoded_strings_dataprovider
897 * @param string $original The original base64_encoded block config setting.
898 * @param string $expected The expected base64_encoded block config setting.
900 public function test_upgrade_fix_block_instance_configuration($original, $expected) {
901 global $DB;
903 $this->resetAfterTest();
905 $data = new stdClass();
906 $data->blockname = 'html';
907 $data->parentcontextid = 1;
908 $data->showinsubcontexts = 0;
909 $data->requirebytheme = 0;
910 $data->pagetypepattern = 'admin-setting-frontpagesettings';
911 $data->defaultregion = 'side-post';
912 $data->defaultweight = 1;
913 $data->timecreated = time();
914 $data->timemodified = time();
916 $data->configdata = $original;
917 $entryid = $DB->insert_record('block_instances', $data);
918 upgrade_fix_block_instance_configuration();
919 $record = $DB->get_record('block_instances', ['id' => $entryid]);
920 $this->assertEquals($expected, $record->configdata);
924 * Check that orphaned files are deleted.
926 public function test_upgrade_delete_orphaned_file_records() {
927 global $DB, $CFG;
928 require_once($CFG->dirroot . '/repository/lib.php');
930 $this->resetAfterTest();
931 // Create user.
932 $generator = $this->getDataGenerator();
933 $user = $generator->create_user();
934 $this->setUser($user);
935 $usercontext = context_user::instance($user->id);
936 $syscontext = context_system::instance();
938 $fs = get_file_storage();
940 $userrepository = array();
941 $newstoredfile = array();
942 $repositorypluginname = array('user', 'areafiles');
944 // Create two repositories with one file in each.
945 foreach ($repositorypluginname as $key => $value) {
946 // Override repository permission.
947 $capability = 'repository/' . $value . ':view';
948 $guestroleid = $DB->get_field('role', 'id', array('shortname' => 'guest'));
949 assign_capability($capability, CAP_ALLOW, $guestroleid, $syscontext->id, true);
951 $args = array();
952 $args['type'] = $value;
953 $repos = repository::get_instances($args);
954 $userrepository[$key] = reset($repos);
956 $this->assertInstanceOf('repository', $userrepository[$key]);
958 $component = 'user';
959 $filearea = 'private';
960 $itemid = $key;
961 $filepath = '/';
962 $filename = 'userfile.txt';
964 $filerecord = array(
965 'contextid' => $usercontext->id,
966 'component' => $component,
967 'filearea' => $filearea,
968 'itemid' => $itemid,
969 'filepath' => $filepath,
970 'filename' => $filename,
973 $content = 'Test content';
974 $originalfile = $fs->create_file_from_string($filerecord, $content);
975 $this->assertInstanceOf('stored_file', $originalfile);
977 $newfilerecord = array(
978 'contextid' => $syscontext->id,
979 'component' => 'core',
980 'filearea' => 'phpunit',
981 'itemid' => $key,
982 'filepath' => $filepath,
983 'filename' => $filename,
985 $ref = $fs->pack_reference($filerecord);
986 $newstoredfile[$key] = $fs->create_file_from_reference($newfilerecord, $userrepository[$key]->id, $ref);
988 // Look for references by repository ID.
989 $files = $fs->get_external_files($userrepository[$key]->id);
990 $file = reset($files);
991 $this->assertEquals($file, $newstoredfile[$key]);
994 // Make one file orphaned by deleting first repository.
995 $DB->delete_records('repository_instances', array('id' => $userrepository[0]->id));
996 $DB->delete_records('repository_instance_config', array('instanceid' => $userrepository[0]->id));
998 upgrade_delete_orphaned_file_records();
1000 $files = $fs->get_external_files($userrepository[0]->id);
1001 $file = reset($files);
1002 $this->assertFalse($file);
1004 $files = $fs->get_external_files($userrepository[1]->id);
1005 $file = reset($files);
1006 $this->assertEquals($file, $newstoredfile[1]);