b6927aba86525aff7af1809ca2bc2d2e1334bdaa
[moodle.git] / lib / testing / generator / 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   test
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 that need to create fake test sites.
29  *
30  * @package    core
31  * @category   test
32  * @copyright  2012 Petr Skoda {@link http://skodak.org}
33  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
34  */
35 class testing_data_generator {
36     protected $usercounter = 0;
37     protected $categorycount = 0;
38     protected $cohortcount = 0;
39     protected $coursecount = 0;
40     protected $scalecount = 0;
41     protected $groupcount = 0;
42     protected $groupingcount = 0;
44     /** @var array list of plugin generators */
45     protected $generators = array();
47     /** @var array lis of common last names */
48     public $lastnames = array(
49         'Smith', 'Johnson', 'Williams', 'Brown', 'Jones', 'Miller', 'Davis', 'García', 'Rodríguez', 'Wilson',
50         'Müller', 'Schmidt', 'Schneider', 'Fischer', 'Meyer', 'Weber', 'Schulz', 'Wagner', 'Becker', 'Hoffmann',
51         'Novák', 'Svoboda', 'Novotný', 'Dvořák', 'Černý', 'Procházková', 'Kučerová', 'Veselá', 'Horáková', 'Němcová',
52         'Смирнов', 'Иванов', 'Кузнецов', 'Соколов', 'Попов', 'Лебедева', 'Козлова', 'Новикова', 'Морозова', 'Петрова',
53         '王', '李', '张', '刘', '陈', '楊', '黃', '趙', '吳', '周',
54         '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
55     );
57     /** @var array lis of common first names */
58     public $firstnames = array(
59         'Jacob', 'Ethan', 'Michael', 'Jayden', 'William', 'Isabella', 'Sophia', 'Emma', 'Olivia', 'Ava',
60         'Lukas', 'Leon', 'Luca', 'Timm', 'Paul', 'Leonie', 'Leah', 'Lena', 'Hanna', 'Laura',
61         'Jakub', 'Jan', 'Tomáš', 'Lukáš', 'Matěj', 'Tereza', 'Eliška', 'Anna', 'Adéla', 'Karolína',
62         'Даниил', 'Максим', 'Артем', 'Иван', 'Александр', 'София', 'Анастасия', 'Дарья', 'Мария', 'Полина',
63         '伟', '伟', '芳', '伟', '秀英', '秀英', '娜', '秀英', '伟', '敏',
64         '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
65     );
67     public $loremipsum = <<<EOD
68 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.
69 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.
70 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.
71 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.
72 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.
73 EOD;
75     /**
76      * To be called from data reset code only,
77      * do not use in tests.
78      * @return void
79      */
80     public function reset() {
81         $this->usercounter = 0;
82         $this->categorycount = 0;
83         $this->coursecount = 0;
84         $this->scalecount = 0;
86         foreach ($this->generators as $generator) {
87             $generator->reset();
88         }
89     }
91     /**
92      * Return generator for given plugin
93      * @param string $component
94      * @return mixed plugin data generator
95      */
96     public function get_plugin_generator($component) {
97         list($type, $plugin) = normalize_component($component);
99         if ($type !== 'mod' and $type !== 'block') {
100             throw new coding_exception("Plugin type $type does not support generators yet");
101         }
103         $dir = get_plugin_directory($type, $plugin);
105         if (!isset($this->generators[$type.'_'.$plugin])) {
106             $lib = "$dir/tests/generator/lib.php";
107             if (!include_once($lib)) {
108                 throw new coding_exception("Plugin $component does not support data generator, missing tests/generator/lib");
109             }
110             $classname = $type.'_'.$plugin.'_generator';
111             $this->generators[$type.'_'.$plugin] = new $classname($this);
112         }
114         return $this->generators[$type.'_'.$plugin];
115     }
117     /**
118      * Create a test user
119      * @param array|stdClass $record
120      * @param array $options
121      * @return stdClass user record
122      */
123     public function create_user($record=null, array $options=null) {
124         global $DB, $CFG;
126         $this->usercounter++;
127         $i = $this->usercounter;
129         $record = (array)$record;
131         if (!isset($record['auth'])) {
132             $record['auth'] = 'manual';
133         }
135         if (!isset($record['firstname']) and !isset($record['lastname'])) {
136             $country = rand(0, 5);
137             $firstname = rand(0, 4);
138             $lastname = rand(0, 4);
139             $female = rand(0, 1);
140             $record['firstname'] = $this->firstnames[($country*10) + $firstname + ($female*5)];
141             $record['lastname'] = $this->lastnames[($country*10) + $lastname + ($female*5)];
143         } else if (!isset($record['firstname'])) {
144             $record['firstname'] = 'Firstname'.$i;
146         } else if (!isset($record['lastname'])) {
147             $record['lastname'] = 'Lastname'.$i;
148         }
150         if (!isset($record['idnumber'])) {
151             $record['idnumber'] = '';
152         }
154         if (!isset($record['mnethostid'])) {
155             $record['mnethostid'] = $CFG->mnet_localhost_id;
156         }
158         if (!isset($record['username'])) {
159             $record['username'] = 'username'.$i;
160             $j = 2;
161             while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
162                 $record['username'] = 'username'.$i.'_'.$j;
163                 $j++;
164             }
165         }
167         if (!isset($record['password'])) {
168             $record['password'] = 'lala';
169         }
171         if (!isset($record['email'])) {
172             $record['email'] = $record['username'].'@example.com';
173         }
175         if (!isset($record['confirmed'])) {
176             $record['confirmed'] = 1;
177         }
179         if (!isset($record['lang'])) {
180             $record['lang'] = 'en';
181         }
183         if (!isset($record['maildisplay'])) {
184             $record['maildisplay'] = 1;
185         }
187         if (!isset($record['deleted'])) {
188             $record['deleted'] = 0;
189         }
191         $record['timecreated'] = time();
192         $record['timemodified'] = $record['timecreated'];
193         $record['lastip'] = '0.0.0.0';
195         $record['password'] = hash_internal_user_password($record['password']);
197         if ($record['deleted']) {
198             $delname = $record['email'].'.'.time();
199             while ($DB->record_exists('user', array('username'=>$delname))) {
200                 $delname++;
201             }
202             $record['idnumber'] = '';
203             $record['email']    = md5($record['username']);
204             $record['username'] = $delname;
205             $record['picture']  = 0;
206         }
208         $userid = $DB->insert_record('user', $record);
210         if (!$record['deleted']) {
211             context_user::instance($userid);
212         }
214         return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
215     }
217     /**
218      * Create a test course category
219      * @param array|stdClass $record
220      * @param array $options
221      * @return stdClass course category record
222      */
223     public function create_category($record=null, array $options=null) {
224         global $DB, $CFG;
225         require_once("$CFG->dirroot/course/lib.php");
227         $this->categorycount++;
228         $i = $this->categorycount;
230         $record = (array)$record;
232         if (!isset($record['name'])) {
233             $record['name'] = 'Course category '.$i;
234         }
236         if (!isset($record['idnumber'])) {
237             $record['idnumber'] = '';
238         }
240         if (!isset($record['description'])) {
241             $record['description'] = "Test course category $i\n$this->loremipsum";
242         }
244         if (!isset($record['descriptionformat'])) {
245             $record['descriptionformat'] = FORMAT_MOODLE;
246         }
248         if (!isset($record['parent'])) {
249             $record['parent'] = 0;
250         }
252         if (empty($record['parent'])) {
253             $parent = new stdClass();
254             $parent->path = '';
255             $parent->depth = 0;
256         } else {
257             $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
258         }
259         $record['depth'] = $parent->depth+1;
261         $record['sortorder'] = 0;
262         $record['timemodified'] = time();
263         $record['timecreated'] = $record['timemodified'];
265         $catid = $DB->insert_record('course_categories', $record);
266         $path = $parent->path . '/' . $catid;
267         $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
268         context_coursecat::instance($catid);
270         fix_course_sortorder();
272         return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
273     }
275     /**
276      * Create test cohort.
277      * @param array|stdClass $record
278      * @param array $options
279      * @return stdClass cohort record
280      */
281     public function create_cohort($record=null, array $options=null) {
282         global $DB, $CFG;
283         require_once("$CFG->dirroot/cohort/lib.php");
285         $this->cohortcount++;
286         $i = $this->cohortcount;
288         $record = (array)$record;
290         if (!isset($record['contextid'])) {
291             $record['contextid'] = context_system::instance()->id;
292         }
294         if (!isset($record['name'])) {
295             $record['name'] = 'Cohort '.$i;
296         }
298         if (!isset($record['idnumber'])) {
299             $record['idnumber'] = '';
300         }
302         if (!isset($record['description'])) {
303             $record['description'] = "Test cohort $i\n$this->loremipsum";
304         }
306         if (!isset($record['descriptionformat'])) {
307             $record['descriptionformat'] = FORMAT_MOODLE;
308         }
310         if (!isset($record['component'])) {
311             $record['component'] = '';
312         }
314         $id = cohort_add_cohort((object)$record);
316         return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
317     }
319     /**
320      * Create a test course
321      * @param array|stdClass $record
322      * @param array $options with keys:
323      *      'createsections'=>bool precreate all sections
324      * @return stdClass course record
325      */
326     public function create_course($record=null, array $options=null) {
327         global $DB, $CFG;
328         require_once("$CFG->dirroot/course/lib.php");
330         $this->coursecount++;
331         $i = $this->coursecount;
333         $record = (array)$record;
335         if (!isset($record['fullname'])) {
336             $record['fullname'] = 'Test course '.$i;
337         }
339         if (!isset($record['shortname'])) {
340             $record['shortname'] = 'tc_'.$i;
341         }
343         if (!isset($record['idnumber'])) {
344             $record['idnumber'] = '';
345         }
347         if (!isset($record['format'])) {
348             $record['format'] = 'topics';
349         }
351         if (!isset($record['newsitems'])) {
352             $record['newsitems'] = 0;
353         }
355         if (!isset($record['numsections'])) {
356             $record['numsections'] = 5;
357         }
359         if (!isset($record['summary'])) {
360             $record['summary'] = "Test course $i\n$this->loremipsum";
361         }
363         if (!isset($record['summaryformat'])) {
364             $record['summaryformat'] = FORMAT_MOODLE;
365         }
367         if (!isset($record['category'])) {
368             $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
369         }
371         $course = create_course((object)$record);
372         context_course::instance($course->id);
373         if (!empty($options['createsections'])) {
374             if (isset($course->numsections)) {
375                 course_create_sections_if_missing($course, range(0, $course->numsections));
376             } else {
377                 course_create_sections_if_missing($course, 0);
378             }
379         }
381         return $course;
382     }
384     /**
385      * Create course section if does not exist yet
386      * @param array|stdClass $record must contain 'course' and 'section' attributes
387      * @param array|null $options
388      * @return stdClass
389      * @throws coding_exception
390      */
391     public function create_course_section($record = null, array $options = null) {
392         global $DB;
394         $record = (array)$record;
396         if (empty($record['course'])) {
397             throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record');
398         }
400         if (!isset($record['section'])) {
401             throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record');
402         }
404         course_create_sections_if_missing($record['course'], $record['section']);
405         return get_fast_modinfo($record['course'])->get_section_info($record['section']);
406     }
408     /**
409      * Create a test block
410      * @param string $blockname
411      * @param array|stdClass $record
412      * @param array $options
413      * @return stdClass block instance record
414      */
415     public function create_block($blockname, $record=null, array $options=null) {
416         $generator = $this->get_plugin_generator('block_'.$blockname);
417         return $generator->create_instance($record, $options);
418     }
420     /**
421      * Create a test module
422      * @param string $modulename
423      * @param array|stdClass $record
424      * @param array $options
425      * @return stdClass activity record
426      */
427     public function create_module($modulename, $record=null, array $options=null) {
428         $generator = $this->get_plugin_generator('mod_'.$modulename);
429         return $generator->create_instance($record, $options);
430     }
432     /**
433      * Create a test group for the specified course
434      *
435      * $record should be either an array or a stdClass containing infomation about the group to create.
436      * At the very least it needs to contain courseid.
437      * Default values are added for name, description, and descriptionformat if they are not present.
438      *
439      * This function calls groups_create_group() to create the group within the database.
440      * @see groups_create_group
441      * @param array|stdClass $record
442      * @return stdClass group record
443      */
444     public function create_group($record) {
445         global $DB, $CFG;
447         require_once($CFG->dirroot . '/group/lib.php');
449         $this->groupcount++;
450         $i = $this->groupcount;
452         $record = (array)$record;
454         if (empty($record['courseid'])) {
455             throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record');
456         }
458         if (!isset($record['name'])) {
459             $record['name'] = 'group-' . $i;
460         }
462         if (!isset($record['description'])) {
463             $record['description'] = "Test Group $i\n{$this->loremipsum}";
464         }
466         if (!isset($record['descriptionformat'])) {
467             $record['descriptionformat'] = FORMAT_MOODLE;
468         }
470         $id = groups_create_group((object)$record);
472         return $DB->get_record('groups', array('id'=>$id));
473     }
475     /**
476      * Create a test group member
477      * @param array|stdClass $record
478      * @throws coding_exception
479      * @return boolean
480      */
481     public function create_group_member($record) {
482         global $DB, $CFG;
484         require_once($CFG->dirroot . '/group/lib.php');
486         $record = (array)$record;
488         if (empty($record['userid'])) {
489             throw new coding_exception('user must be present in testing_util::create_group_member() $record');
490         }
492         if (!isset($record['groupid'])) {
493             throw new coding_exception('group must be present in testing_util::create_group_member() $record');
494         }
496         if (!isset($record['component'])) {
497             $record['component'] = null;
498         }
499         if (!isset($record['itemid'])) {
500             $record['itemid'] = 0;
501         }
503         return groups_add_member($record['groupid'], $record['userid'], $record['component'], $record['itemid']);
504     }
506     /**
507      * Create a test grouping for the specified course
508      *
509      * $record should be either an array or a stdClass containing infomation about the grouping to create.
510      * At the very least it needs to contain courseid.
511      * Default values are added for name, description, and descriptionformat if they are not present.
512      *
513      * This function calls groups_create_grouping() to create the grouping within the database.
514      * @see groups_create_grouping
515      * @param array|stdClass $record
516      * @return stdClass grouping record
517      */
518     public function create_grouping($record) {
519         global $DB, $CFG;
521         require_once($CFG->dirroot . '/group/lib.php');
523         $this->groupingcount++;
524         $i = $this->groupingcount;
526         $record = (array)$record;
528         if (empty($record['courseid'])) {
529             throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record');
530         }
532         if (!isset($record['name'])) {
533             $record['name'] = 'grouping-' . $i;
534         }
536         if (!isset($record['description'])) {
537             $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
538         }
540         if (!isset($record['descriptionformat'])) {
541             $record['descriptionformat'] = FORMAT_MOODLE;
542         }
544         $id = groups_create_grouping((object)$record);
546         return $DB->get_record('groupings', array('id'=>$id));
547     }
549     /**
550      * Create a test grouping group
551      * @param array|stdClass $record
552      * @throws coding_exception
553      * @return boolean
554      */
555     public function create_grouping_group($record) {
556         global $DB, $CFG;
558         require_once($CFG->dirroot . '/group/lib.php');
560         $record = (array)$record;
562         if (empty($record['groupingid'])) {
563             throw new coding_exception('grouping must be present in testing::create_grouping_group() $record');
564         }
566         if (!isset($record['groupid'])) {
567             throw new coding_exception('group must be present in testing_util::create_grouping_group() $record');
568         }
570         return groups_assign_grouping($record['groupingid'], $record['groupid']);
571     }
573     /**
574      * Create a test scale
575      * @param array|stdClass $record
576      * @param array $options
577      * @return stdClass block instance record
578      */
579     public function create_scale($record=null, array $options=null) {
580         global $DB;
582         $this->scalecount++;
583         $i = $this->scalecount;
585         $record = (array)$record;
587         if (!isset($record['name'])) {
588             $record['name'] = 'Test scale '.$i;
589         }
591         if (!isset($record['scale'])) {
592             $record['scale'] = 'A,B,C,D,F';
593         }
595         if (!isset($record['courseid'])) {
596             $record['courseid'] = 0;
597         }
599         if (!isset($record['userid'])) {
600             $record['userid'] = 0;
601         }
603         if (!isset($record['description'])) {
604             $record['description'] = 'Test scale description '.$i;
605         }
607         if (!isset($record['descriptionformat'])) {
608             $record['descriptionformat'] = FORMAT_MOODLE;
609         }
611         $record['timemodified'] = time();
613         if (isset($record['id'])) {
614             $DB->import_record('scale', $record);
615             $DB->get_manager()->reset_sequence('scale');
616             $id = $record['id'];
617         } else {
618             $id = $DB->insert_record('scale', $record);
619         }
621         return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
622     }
624     /**
625      * Simplified enrolment of user to course using default options.
626      *
627      * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!!
628      *
629      * @param int $userid
630      * @param int $courseid
631      * @param int $roleid optional role id, use only with manual plugin
632      * @param string $enrol name of enrol plugin,
633      *     there must be exactly one instance in course,
634      *     it must support enrol_user() method.
635      * @return bool success
636      */
637     public function enrol_user($userid, $courseid, $roleid = null, $enrol = 'manual') {
638         global $DB;
640         if (!$plugin = enrol_get_plugin($enrol)) {
641             return false;
642         }
644         $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
645         if (count($instances) != 1) {
646             return false;
647         }
648         $instance = reset($instances);
650         if (is_null($roleid) and $instance->roleid) {
651             $roleid = $instance->roleid;
652         }
654         $plugin->enrol_user($instance, $userid, $roleid);
656         return true;
657     }
660 /**
661  * Deprecated in favour of testing_data_generator
662  *
663  * @deprecated since Moodle 2.5 MDL-37457 - please do not use this function any more.
664  * @todo       MDL-37517 This will be deleted in Moodle 2.7
665  * @see        testing_data_generator
666  * @package    core
667  * @category   test
668  * @copyright  2012 David Monllaó
669  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
670  */
671 class phpunit_data_generator extends testing_data_generator {
673     /**
674      * Dumb constructor to throw the deprecated notification
675      */
676     public function __construct() {
677         debugging('Class phpunit_data_generator is deprecated, please use class testing_module_generator instead', DEBUG_DEVELOPER);
678     }