MDL-35339 Better cache reset for get_fast_modinfo(), allow exec get_fast_modinfo...
[moodle.git] / lib / phpunit / classes / data_generator.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  * Data generator.
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  2012 Petr Skoda {@link http://skodak.org}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
27 /**
28  * Data generator class for unit tests and other tools
29  * that need to create fake test sites.
30  *
31  * @package    core
32  * @category   phpunit
33  * @copyright  2012 Petr Skoda {@link http://skodak.org}
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class phpunit_data_generator {
37     protected $usercounter = 0;
38     protected $categorycount = 0;
39     protected $cohortcount = 0;
40     protected $coursecount = 0;
41     protected $scalecount = 0;
42     protected $groupcount = 0;
43     protected $groupingcount = 0;
45     /** @var array list of plugin generators */
46     protected $generators = array();
48     /** @var array lis of common last names */
49     public $lastnames = array(
50         'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
51         'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
52         'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
53         'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
54         '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
55         '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
56     );
58     /** @var array lis of common first names */
59     public $firstnames = array(
60         'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
61         'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
62         'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
63         'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
64         '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
65         '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
66     );
68     public $loremipsum = <<<EOD
69 Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Nulla non arcu lacinia neque faucibus fringilla. Vivamus porttitor turpis ac leo. Integer in sapien. Nullam eget nisl. Aliquam erat volutpat. Cras elementum. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Integer malesuada. Nullam lectus justo, vulputate eget mollis sed, tempor sed magna. Mauris elementum mauris vitae tortor. Aliquam erat volutpat.
70 Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Pellentesque ipsum. Cras pede libero, dapibus nec, pretium sit amet, tempor quis. Aliquam ante. Proin in tellus sit amet nibh dignissim sagittis. Vivamus porttitor turpis ac leo. Duis bibendum, lectus ut viverra rhoncus, dolor nunc faucibus libero, eget facilisis enim ipsum id lacus. In sem justo, commodo ut, suscipit at, pharetra vitae, orci. Aliquam erat volutpat. Nulla est.
71 Vivamus luctus egestas leo. Aenean fermentum risus id tortor. Mauris dictum facilisis augue. Aliquam erat volutpat. Aliquam ornare wisi eu metus. Aliquam id dolor. Duis condimentum augue id magna semper rutrum. Donec iaculis gravida nulla. Pellentesque ipsum. Etiam dictum tincidunt diam. Quisque tincidunt scelerisque libero. Etiam egestas wisi a erat.
72 Integer lacinia. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Mauris tincidunt sem sed arcu. Nullam feugiat, turpis at pulvinar vulputate, erat libero tristique tellus, nec bibendum odio risus sit amet ante. Aliquam id dolor. Maecenas sollicitudin. Et harum quidem rerum facilis est et expedita distinctio. Mauris suscipit, ligula sit amet pharetra semper, nibh ante cursus purus, vel sagittis velit mauris vel metus. Nullam dapibus fermentum ipsum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Pellentesque sapien. Duis risus. Mauris elementum mauris vitae tortor. Suspendisse nisl. Integer rutrum, orci vestibulum ullamcorper ultricies, lacus quam ultricies odio, vitae placerat pede sem sit amet enim.
73 In laoreet, magna id viverra tincidunt, sem odio bibendum justo, vel imperdiet sapien wisi sed libero. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Nullam justo enim, consectetuer nec, ullamcorper ac, vestibulum in, elit. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur? Maecenas lorem. Etiam posuere lacus quis dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Curabitur ligula sapien, pulvinar a vestibulum quis, facilisis vel sapien. Nam sed tellus id magna elementum tincidunt. Suspendisse nisl. Vivamus luctus egestas leo. Nulla non arcu lacinia neque faucibus fringilla. Etiam dui sem, fermentum vitae, sagittis id, malesuada in, quam. Etiam dictum tincidunt diam. Etiam commodo dui eget wisi. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Proin pede metus, vulputate nec, fermentum fringilla, vehicula vitae, justo. Duis ante orci, molestie vitae vehicula venenatis, tincidunt ac pede. Pellentesque sapien.
74 EOD;
76     /**
77      * To be called from data reset code only,
78      * do not use in tests.
79      * @return void
80      */
81     public function reset() {
82         $this->usercounter = 0;
83         $this->categorycount = 0;
84         $this->coursecount = 0;
85         $this->scalecount = 0;
87         foreach($this->generators as $generator) {
88             $generator->reset();
89         }
90     }
92     /**
93      * Return generator for given plugin
94      * @param string $component
95      * @return mixed plugin data generator
96      */
97     public function get_plugin_generator($component) {
98         list($type, $plugin) = normalize_component($component);
100         if ($type !== 'mod' and $type !== 'block') {
101             throw new coding_exception("Plugin type $type does not support generators yet");
102         }
104         $dir = get_plugin_directory($type, $plugin);
106         if (!isset($this->generators[$type.'_'.$plugin])) {
107             $lib = "$dir/tests/generator/lib.php";
108             if (!include_once($lib)) {
109                 throw new coding_exception("Plugin $component does not support data generator, missing tests/generator/lib");
110             }
111             $classname = $type.'_'.$plugin.'_generator';
112             $this->generators[$type.'_'.$plugin] = new $classname($this);
113         }
115         return $this->generators[$type.'_'.$plugin];
116     }
118     /**
119      * Create a test user
120      * @param array|stdClass $record
121      * @param array $options
122      * @return stdClass user record
123      */
124     public function create_user($record=null, array $options=null) {
125         global $DB, $CFG;
127         $this->usercounter++;
128         $i = $this->usercounter;
130         $record = (array)$record;
132         if (!isset($record['auth'])) {
133             $record['auth'] = 'manual';
134         }
136         if (!isset($record['firstname']) and !isset($record['lastname'])) {
137             $country = rand(0, 5);
138             $firstname = rand(0, 4);
139             $lastname = rand(0, 4);
140             $female = rand(0, 1);
141             $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)];
142             $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)];
144         } else if (!isset($record['firstname'])) {
145             $record['firstname'] = 'Firstname'.$i;
147         } else if (!isset($record['lastname'])) {
148             $record['lastname'] = 'Lastname'.$i;
149         }
151         if (!isset($record['idnumber'])) {
152             $record['idnumber'] = '';
153         }
155         if (!isset($record['mnethostid'])) {
156             $record['mnethostid'] = $CFG->mnet_localhost_id;
157         }
159         if (!isset($record['username'])) {
160             $record['username'] = textlib::strtolower($record['firstname']).textlib::strtolower($record['lastname']);
161             while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
162                 $record['username'] = $record['username'].'_'.$i;
163             }
164         }
166         if (!isset($record['password'])) {
167             $record['password'] = 'lala';
168         }
170         if (!isset($record['email'])) {
171             $record['email'] = $record['username'].'@example.com';
172         }
174         if (!isset($record['confirmed'])) {
175             $record['confirmed'] = 1;
176         }
178         if (!isset($record['lang'])) {
179             $record['lang'] = 'en';
180         }
182         if (!isset($record['maildisplay'])) {
183             $record['maildisplay'] = 1;
184         }
186         if (!isset($record['deleted'])) {
187             $record['deleted'] = 0;
188         }
190         $record['timecreated'] = time();
191         $record['timemodified'] = $record['timecreated'];
192         $record['lastip'] = '0.0.0.0';
194         $record['password'] = hash_internal_user_password($record['password']);
196         if ($record['deleted']) {
197             $delname = $record['email'].'.'.time();
198             while ($DB->record_exists('user', array('username'=>$delname))) {
199                 $delname++;
200             }
201             $record['idnumber'] = '';
202             $record['email']    = md5($record['username']);
203             $record['username'] = $delname;
204             $record['picture']  = 0;
205         }
207         $userid = $DB->insert_record('user', $record);
209         if (!$record['deleted']) {
210             context_user::instance($userid);
211         }
213         return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
214     }
216     /**
217      * Create a test course category
218      * @param array|stdClass $record
219      * @param array $options
220      * @return stdClass course category record
221      */
222     public function create_category($record=null, array $options=null) {
223         global $DB, $CFG;
224         require_once("$CFG->dirroot/course/lib.php");
226         $this->categorycount++;
227         $i = $this->categorycount;
229         $record = (array)$record;
231         if (!isset($record['name'])) {
232             $record['name'] = 'Course category '.$i;
233         }
235         if (!isset($record['idnumber'])) {
236             $record['idnumber'] = '';
237         }
239         if (!isset($record['description'])) {
240             $record['description'] = "Test course category $i\n$this->loremipsum";
241         }
243         if (!isset($record['descriptionformat'])) {
244             $record['descriptionformat'] = FORMAT_MOODLE;
245         }
247         if (!isset($record['parent'])) {
248             $record['parent'] = 0;
249         }
251         if (empty($record['parent'])) {
252             $parent = new stdClass();
253             $parent->path = '';
254             $parent->depth = 0;
255         } else {
256             $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
257         }
258         $record['depth'] = $parent->depth+1;
260         $record['sortorder'] = 0;
261         $record['timemodified'] = time();
262         $record['timecreated'] = $record['timemodified'];
264         $catid = $DB->insert_record('course_categories', $record);
265         $path = $parent->path . '/' . $catid;
266         $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
267         context_coursecat::instance($catid);
269         fix_course_sortorder();
271         return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
272     }
274     /**
275      * Create test cohort.
276      * @param array|stdClass $record
277      * @param array $options
278      * @return stdClass cohort record
279      */
280     public function create_cohort($record=null, array $options=null) {
281         global $DB, $CFG;
282         require_once("$CFG->dirroot/cohort/lib.php");
284         $this->cohortcount++;
285         $i = $this->cohortcount;
287         $record = (array)$record;
289         if (!isset($record['contextid'])) {
290             $record['contextid'] = context_system::instance()->id;
291         }
293         if (!isset($record['name'])) {
294             $record['name'] = 'Cohort '.$i;
295         }
297         if (!isset($record['idnumber'])) {
298             $record['idnumber'] = '';
299         }
301         if (!isset($record['description'])) {
302             $record['description'] = "Test cohort $i\n$this->loremipsum";
303         }
305         if (!isset($record['descriptionformat'])) {
306             $record['descriptionformat'] = FORMAT_MOODLE;
307         }
309         if (!isset($record['component'])) {
310             $record['component'] = '';
311         }
313         $id = cohort_add_cohort((object)$record);
315         return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
316     }
318     /**
319      * Create a test course
320      * @param array|stdClass $record
321      * @param array $options with keys:
322      *      'createsections'=>bool precreate all sections
323      * @return stdClass course record
324      */
325     public function create_course($record=null, array $options=null) {
326         global $DB, $CFG;
327         require_once("$CFG->dirroot/course/lib.php");
329         $this->coursecount++;
330         $i = $this->coursecount;
332         $record = (array)$record;
334         if (!isset($record['fullname'])) {
335             $record['fullname'] = 'Test course '.$i;
336         }
338         if (!isset($record['shortname'])) {
339             $record['shortname'] = 'tc_'.$i;
340         }
342         if (!isset($record['idnumber'])) {
343             $record['idnumber'] = '';
344         }
346         if (!isset($record['format'])) {
347             $record['format'] = 'topics';
348         }
350         if (!isset($record['newsitems'])) {
351             $record['newsitems'] = 0;
352         }
354         if (!isset($record['numsections'])) {
355             $record['numsections'] = 5;
356         }
358         if (!isset($record['summary'])) {
359             $record['summary'] = "Test course $i\n$this->loremipsum";
360         }
362         if (!isset($record['summaryformat'])) {
363             $record['summaryformat'] = FORMAT_MOODLE;
364         }
366         if (!isset($record['category'])) {
367             $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
368         }
370         $course = create_course((object)$record);
371         context_course::instance($course->id);
372         if (!empty($options['createsections'])) {
373             if (isset($course->numsections)) {
374                 course_create_sections_if_missing($course, range(0, $course->numsections));
375             } else {
376                 course_create_sections_if_missing($course, 0);
377             }
378         }
380         return $course;
381     }
383     /**
384      * Create course section if does not exist yet
385      * @param array|stdClass $record must contain 'course' and 'section' attributes
386      * @param array|null $options
387      * @return stdClass
388      * @throws coding_exception
389      */
390     public function create_course_section($record = null, array $options = null) {
391         global $DB;
393         $record = (array)$record;
395         if (empty($record['course'])) {
396             throw new coding_exception('course must be present in phpunit_util::create_course_section() $record');
397         }
399         if (!isset($record['section'])) {
400             throw new coding_exception('section must be present in phpunit_util::create_course_section() $record');
401         }
403         course_create_sections_if_missing($record['course'], $record['section']);
404         return get_fast_modinfo($record['course'])->get_section_info($record['section']);
405     }
407     /**
408      * Create a test block
409      * @param string $blockname
410      * @param array|stdClass $record
411      * @param array $options
412      * @return stdClass block instance record
413      */
414     public function create_block($blockname, $record=null, array $options=null) {
415         $generator = $this->get_plugin_generator('block_'.$blockname);
416         return $generator->create_instance($record, $options);
417     }
419     /**
420      * Create a test module
421      * @param string $modulename
422      * @param array|stdClass $record
423      * @param array $options
424      * @return stdClass activity record
425      */
426     public function create_module($modulename, $record=null, array $options=null) {
427         $generator = $this->get_plugin_generator('mod_'.$modulename);
428         return $generator->create_instance($record, $options);
429     }
431     /**
432      * Create a test group for the specified course
433      *
434      * $record should be either an array or a stdClass containing infomation about the group to create.
435      * At the very least it needs to contain courseid.
436      * Default values are added for name, description, and descriptionformat if they are not present.
437      *
438      * This function calls {@see groups_create_group()} to create the group within the database.
439      *
440      * @param array|stdClass $record
441      * @return stdClass group record
442      */
443     public function create_group($record) {
444         global $DB, $CFG;
446         require_once($CFG->dirroot . '/group/lib.php');
448         $this->groupcount++;
449         $i = $this->groupcount;
451         $record = (array)$record;
453         if (empty($record['courseid'])) {
454             throw new coding_exception('courseid must be present in phpunit_util::create_group() $record');
455         }
457         if (!isset($record['name'])) {
458             $record['name'] = 'group-' . $i;
459         }
461         if (!isset($record['description'])) {
462             $record['description'] = "Test Group $i\n{$this->loremipsum}";
463         }
465         if (!isset($record['descriptionformat'])) {
466             $record['descriptionformat'] = FORMAT_MOODLE;
467         }
469         $id = groups_create_group((object)$record);
471         return $DB->get_record('groups', array('id'=>$id));
472     }
474     /**
475      * Create a test grouping for the specified course
476      *
477      * $record should be either an array or a stdClass containing infomation about the grouping to create.
478      * At the very least it needs to contain courseid.
479      * Default values are added for name, description, and descriptionformat if they are not present.
480      *
481      * This function calls {@see groups_create_grouping()} to create the grouping within the database.
482      *
483      * @param array|stdClass $record
484      * @return stdClass grouping record
485      */
486     public function create_grouping($record) {
487         global $DB, $CFG;
489         require_once($CFG->dirroot . '/group/lib.php');
491         $this->groupingcount++;
492         $i = $this->groupingcount;
494         $record = (array)$record;
496         if (empty($record['courseid'])) {
497             throw new coding_exception('courseid must be present in phpunit_util::create_grouping() $record');
498         }
500         if (!isset($record['name'])) {
501             $record['name'] = 'grouping-' . $i;
502         }
504         if (!isset($record['description'])) {
505             $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
506         }
508         if (!isset($record['descriptionformat'])) {
509             $record['descriptionformat'] = FORMAT_MOODLE;
510         }
512         $id = groups_create_grouping((object)$record);
514         return $DB->get_record('groupings', array('id'=>$id));
515     }
517     /**
518      * Create a test scale
519      * @param array|stdClass $record
520      * @param array $options
521      * @return stdClass block instance record
522      */
523     public function create_scale($record=null, array $options=null) {
524         global $DB;
526         $this->scalecount++;
527         $i = $this->scalecount;
529         $record = (array)$record;
531         if (!isset($record['name'])) {
532             $record['name'] = 'Test scale '.$i;
533         }
535         if (!isset($record['scale'])) {
536             $record['scale'] = 'A,B,C,D,F';
537         }
539         if (!isset($record['courseid'])) {
540             $record['courseid'] = 0;
541         }
543         if (!isset($record['userid'])) {
544             $record['userid'] = 0;
545         }
547         if (!isset($record['description'])) {
548             $record['description'] = 'Test scale description '.$i;
549         }
551         if (!isset($record['descriptionformat'])) {
552             $record['descriptionformat'] = FORMAT_MOODLE;
553         }
555         $record['timemodified'] = time();
557         if (isset($record['id'])) {
558             $DB->import_record('scale', $record);
559             $DB->get_manager()->reset_sequence('scale');
560             $id = $record['id'];
561         } else {
562             $id = $DB->insert_record('scale', $record);
563         }
565         return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
566     }