MDL-64506 phpunit: Update unit tests to use classic
[moodle.git] / lib / tests / upgradelib_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 lib/upgradelib.php library.
19  *
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
24  */
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.
34  */
35 class core_upgradelib_testcase extends advanced_testcase {
37     /**
38      * Test the {@link upgrade_stale_php_files_present() function
39      */
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());
44     }
46     /**
47      * Populate some fake grade items into the database with specified
48      * sortorder and course id.
49      *
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.
54      *
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.
58      */
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));
77     }
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);
98             }
99         }
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});
142     }
144     /**
145      * Test the upgrade function for flagging courses with calculated grade item problems.
146      */
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});
276     }
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);
344     }
346     /**
347      * Test that the upgrade script correctly flags courses to be frozen due to letter boundary problems.
348      */
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);
367             }
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);
371             }
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');
377             }
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');
385             }
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();
399             }
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;
413         }
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);
437         }
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);
464         }
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);
494         }
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);
521         }
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);
538         }
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});
548     }
550     /**
551      * Test upgrade_letter_boundary_needs_freeze function.
552      */
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);
565         }
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));
579     }
581     /**
582      * Assigns letter boundaries with comparison problems.
583      *
584      * @param int $contextid Context ID.
585      */
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'),
600             );
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);
606         }
607     }
609     /**
610      * Assigns letter boundaries with no comparison problems.
611      *
612      * @param int $contextid Context ID.
613      */
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'),
628             );
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);
634         }
635     }
637     /**
638      * Test libcurl custom check api.
639      */
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));
650         }
651     }
653     /**
654      * Create two pages with blocks, delete one page and make sure upgrade script deletes orphaned blocks
655      */
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]);
670         }
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']));
694     }
696     /**
697      * Test the conversion of auth plugin settings names.
698      */
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'));
745     }
747     /**
748      * Create a collection of test themes to test determining parent themes.
749      *
750      * @return Url to the path containing the test themes
751      */
752     public function create_testthemes() {
753         global $CFG;
755         $themedircontent = [
756             'testtheme' => [
757                 'config.php' => '<?php $THEME->name = "testtheme"; $THEME->parents = [""];',
758             ],
759             'childoftesttheme' => [
760                 'config.php' => '<?php $THEME->name = "childofboost"; $THEME->parents = ["testtheme"];',
761             ],
762             'infinite' => [
763                 'config.php' => '<?php $THEME->name = "infinite"; $THEME->parents = ["forever"];',
764             ],
765             'forever' => [
766                 'config.php' => '<?php $THEME->name = "forever"; $THEME->parents = ["infinite", "childoftesttheme"];',
767             ],
768             'orphantheme' => [
769                 'config.php' => '<?php $THEME->name = "orphantheme"; $THEME->parents = [];',
770             ],
771             'loop' => [
772                 'config.php' => '<?php $THEME->name = "loop"; $THEME->parents = ["around"];',
773             ],
774             'around' => [
775                 'config.php' => '<?php $THEME->name = "around"; $THEME->parents = ["loop"];',
776             ],
777             'themewithbrokenparent' => [
778                 'config.php' => '<?php $THEME->name = "orphantheme"; $THEME->parents = ["nonexistent", "testtheme"];',
779             ],
780         ];
781         $vthemedir = \org\bovigo\vfs\vfsStream::setup('themes', null, $themedircontent);
783         return \org\bovigo\vfs\vfsStream::url('themes');
784     }
786     /**
787      * Test finding theme locations.
788      */
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'));
801     }
803     /**
804      * Test figuring out if theme is or is a child of a certain theme.
805      */
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');
822     }
824     /**
825      * Data provider of serialized string.
826      *
827      * @return array
828      */
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";}'
835             ],
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";}'
840             ],
841             'A setting I saw when importing a course with blocks from 1.9' => [
842                 'N;',
843                 false,
844                 'N;'
845             ],
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";}}'
850             ],
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;}'
855             ]
856         ];
857     }
859     /**
860      * Test that objects in serialized strings will be changed over to stdClass.
861      *
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.
866      */
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);
871     }
873     /**
874      * Data provider for base64_encoded block instance config data.
875      */
876     public function encoded_strings_dataprovider() {
877         return [
878             'Normal data using stdClass' => [
879                 'Tzo4OiJzdGRDbGFzcyI6NTp7czo1OiJ0aXRsZSI7czo0OiJUYWdzIjtzOjEyOiJudW1iZXJvZnRhZ3MiO3M6MjoiODAiO3M6MTI6InNob3dzdGFuZGFyZCI7czoxOiIwIjtzOjM6ImN0eCI7czozOiIyODkiO3M6MzoicmVjIjtzOjE6IjEiO30=',
880                 'Tzo4OiJzdGRDbGFzcyI6NTp7czo1OiJ0aXRsZSI7czo0OiJUYWdzIjtzOjEyOiJudW1iZXJvZnRhZ3MiO3M6MjoiODAiO3M6MTI6InNob3dzdGFuZGFyZCI7czoxOiIwIjtzOjM6ImN0eCI7czozOiIyODkiO3M6MzoicmVjIjtzOjE6IjEiO30='
881             ],
882             'No data at all' => [
883                 '',
884                 ''
885             ],
886             'Old data using object' => [
887                 'Tzo2OiJvYmplY3QiOjM6e3M6NDoidGV4dCI7czozMjoiTm90aGluZyB0aGF0IGFueW9uZSBjYXJlcyBhYm91dC4iO3M6NToidGl0bGUiO3M6MTY6IlJlYWxseSBvbGQgYmxvY2siO3M6NjoiZm9ybWF0IjtzOjE6IjEiO30=',
888                 'Tzo4OiJzdGRDbGFzcyI6Mzp7czo0OiJ0ZXh0IjtzOjMyOiJOb3RoaW5nIHRoYXQgYW55b25lIGNhcmVzIGFib3V0LiI7czo1OiJ0aXRsZSI7czoxNjoiUmVhbGx5IG9sZCBibG9jayI7czo2OiJmb3JtYXQiO3M6MToiMSI7fQ=='
889             ]
890         ];
891     }
893     /**
894      * Check that entries in the block_instances table are coverted over correctly.
895      *
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.
899      */
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);
921     }
923     /**
924      * Check that orphaned files are deleted.
925      */
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,
971             );
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,
984             );
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]);
992         }
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]);
1007     }