2 // This file is part of Moodle - http://moodle.org/
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.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * PHPUnit data generator class
22 * @copyright 2012 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') || die();
30 * Data generator for unit tests
34 * @copyright 2012 Petr Skoda {@link http://skodak.org}
35 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 class phpunit_data_generator {
38 protected $usercounter = 0;
39 protected $categorycount = 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 '佐藤', '鈴木', '高橋', '田中', '渡辺', '伊藤', '山本', '中村', '小林', '斎藤',
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 '翔', '大翔', '拓海', '翔太', '颯太', '陽菜', 'さくら', '美咲', '葵', '美羽',
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.
77 * To be called from data reset code only,
78 * do not use in tests.
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) {
93 * Return generator for given plugin
94 * @param string $component
95 * @return mixed plugin data generator
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");
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");
111 $classname = $type.'_'.$plugin.'_generator';
112 $this->generators[$type.'_'.$plugin] = new $classname($this);
115 return $this->generators[$type.'_'.$plugin];
120 * @param array|stdClass $record
121 * @param array $options
122 * @return stdClass user record
124 public function create_user($record=null, array $options=null) {
127 $this->usercounter++;
128 $i = $this->usercounter;
130 $record = (array)$record;
132 if (!isset($record['auth'])) {
133 $record['auth'] = 'manual';
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;
151 if (!isset($record['idnumber'])) {
152 $record['idnumber'] = '';
155 if (!isset($record['mnethostid'])) {
156 $record['mnethostid'] = $CFG->mnet_localhost_id;
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;
166 if (!isset($record['password'])) {
167 $record['password'] = 'lala';
170 if (!isset($record['email'])) {
171 $record['email'] = $record['username'].'@example.com';
174 if (!isset($record['confirmed'])) {
175 $record['confirmed'] = 1;
178 if (!isset($record['lang'])) {
179 $record['lang'] = 'en';
182 if (!isset($record['maildisplay'])) {
183 $record['maildisplay'] = 1;
186 if (!isset($record['deleted'])) {
187 $record['deleted'] = 0;
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))) {
201 $record['idnumber'] = '';
202 $record['email'] = md5($record['username']);
203 $record['username'] = $delname;
206 $userid = $DB->insert_record('user', $record);
208 if (!$record['deleted']) {
209 context_user::instance($userid);
212 return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
216 * Create a test course category
217 * @param array|stdClass $record
218 * @param array $options
219 * @return stdClass course category record
221 function create_category($record=null, array $options=null) {
223 require_once("$CFG->dirroot/course/lib.php");
225 $this->categorycount++;
226 $i = $this->categorycount;
228 $record = (array)$record;
230 if (!isset($record['name'])) {
231 $record['name'] = 'Course category '.$i;
234 if (!isset($record['idnumber'])) {
235 $record['idnumber'] = '';
238 if (!isset($record['description'])) {
239 $record['description'] = "Test course category $i\n$this->loremipsum";
242 if (!isset($record['descriptionformat'])) {
243 $record['description'] = FORMAT_MOODLE;
246 if (!isset($record['parent'])) {
247 $record['descriptionformat'] = 0;
250 if (empty($record['parent'])) {
251 $parent = new stdClass();
255 $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
257 $record['depth'] = $parent->depth+1;
259 $record['sortorder'] = 0;
260 $record['timemodified'] = time();
261 $record['timecreated'] = $record['timemodified'];
263 $catid = $DB->insert_record('course_categories', $record);
264 $path = $parent->path . '/' . $catid;
265 $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
266 context_coursecat::instance($catid);
268 fix_course_sortorder();
270 return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
274 * Create a test course
275 * @param array|stdClass $record
276 * @param array $options with keys:
277 * 'createsections'=>bool precreate all sections
278 * @return stdClass course record
280 function create_course($record=null, array $options=null) {
282 require_once("$CFG->dirroot/course/lib.php");
284 $this->coursecount++;
285 $i = $this->coursecount;
287 $record = (array)$record;
289 if (!isset($record['fullname'])) {
290 $record['fullname'] = 'Test course '.$i;
293 if (!isset($record['shortname'])) {
294 $record['shortname'] = 'tc_'.$i;
297 if (!isset($record['idnumber'])) {
298 $record['idnumber'] = '';
301 if (!isset($record['format'])) {
302 $record['format'] = 'topics';
305 if (!isset($record['newsitems'])) {
306 $record['newsitems'] = 0;
309 if (!isset($record['numsections'])) {
310 $record['numsections'] = 5;
313 if (!isset($record['description'])) {
314 $record['description'] = "Test course $i\n$this->loremipsum";
317 if (!isset($record['descriptionformat'])) {
318 $record['description'] = FORMAT_MOODLE;
321 if (!isset($record['category'])) {
322 $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
325 $course = create_course((object)$record);
326 context_course::instance($course->id);
328 if (!empty($options['createsections'])) {
329 for($i=1; $i<$record['numsections']; $i++) {
330 self::create_course_section(array('course'=>$course->id, 'section'=>$i));
338 * Create course section if does not exist yet
339 * @param mixed $record
340 * @param array|null $options
342 * @throws coding_exception
344 public function create_course_section($record = null, array $options = null) {
347 $record = (array)$record;
349 if (empty($record['course'])) {
350 throw new coding_exception('course must be present in phpunit_util::create_course_section() $record');
353 if (!isset($record['section'])) {
354 throw new coding_exception('section must be present in phpunit_util::create_course_section() $record');
357 if (!isset($record['name'])) {
358 $record['name'] = '';
361 if (!isset($record['summary'])) {
362 $record['summary'] = '';
365 if (!isset($record['summaryformat'])) {
366 $record['summaryformat'] = FORMAT_MOODLE;
369 if ($section = $DB->get_record('course_sections', array('course'=>$record['course'], 'section'=>$record['section']))) {
373 $section = new stdClass();
374 $section->course = $record['course'];
375 $section->section = $record['section'];
376 $section->name = $record['name'];
377 $section->summary = $record['summary'];
378 $section->summaryformat = $record['summaryformat'];
379 $id = $DB->insert_record('course_sections', $section);
381 return $DB->get_record('course_sections', array('id'=>$id));
385 * Create a test block
386 * @param string $blockname
387 * @param array|stdClass $record
388 * @param array $options
389 * @return stdClass block instance record
391 public function create_block($blockname, $record=null, array $options=null) {
392 $generator = $this->get_plugin_generator('block_'.$blockname);
393 return $generator->create_instance($record, $options);
397 * Create a test module
398 * @param string $modulename
399 * @param array|stdClass $record
400 * @param array $options
401 * @return stdClass activity record
403 public function create_module($modulename, $record=null, array $options=null) {
404 $generator = $this->get_plugin_generator('mod_'.$modulename);
405 return $generator->create_instance($record, $options);
409 * Create a test group for the specified course
411 * @param array|stdClass $recrd
412 * @return stdClass group record
414 public function create_group($record) {
417 require_once($CFG->dirroot . '/group/lib.php');
420 $i = $this->groupcount;
422 $record = (array)$record;
424 if (empty($record['courseid'])) {
425 throw new coding_exception('courseid must be present in phpunit_util::create_group() $record');
428 if (!isset($record['name'])) {
429 $record['name'] = 'group-' . $i;
432 if (!isset($record['description'])) {
433 $record['description'] = "Test Group $i\n{$this->loremipsum}";
436 if (!isset($record['descriptionformat'])) {
437 $record['descriptionformat'] = FORMAT_MOODLE;
440 $id = groups_create_group((object)$record);
442 return $DB->get_record('groups', array('id'=>$id));
446 * Create a test grouping for the specified course
448 * @param array|stdClass $recrd
449 * @return stdClass grouping record
451 public function create_grouping($record) {
454 require_once($CFG->dirroot . '/group/lib.php');
456 $this->groupingcount++;
457 $i = $this->groupingcount;
459 $record = (array)$record;
461 if (empty($record['courseid'])) {
462 throw new coding_exception('courseid must be present in phpunit_util::create_grouping() $record');
465 if (!isset($record['name'])) {
466 $record['name'] = 'grouping-' . $i;
469 if (!isset($record['description'])) {
470 $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
473 if (!isset($record['descriptionformat'])) {
474 $record['descriptionformat'] = FORMAT_MOODLE;
477 $id = groups_create_grouping((object)$record);
479 return $DB->get_record('groupings', array('id'=>$id));
483 * Create a test scale
484 * @param array|stdClass $record
485 * @param array $options
486 * @return stdClass block instance record
488 public function create_scale($record=null, array $options=null) {
492 $i = $this->scalecount;
494 $record = (array)$record;
496 if (!isset($record['name'])) {
497 $record['name'] = 'Test scale '.$i;
500 if (!isset($record['scale'])) {
501 $record['scale'] = 'A,B,C,D,F';
504 if (!isset($record['courseid'])) {
505 $record['courseid'] = 0;
508 if (!isset($record['userid'])) {
509 $record['userid'] = 0;
512 if (!isset($record['description'])) {
513 $record['description'] = 'Test scale description '.$i;
516 if (!isset($record['descriptionformat'])) {
517 $record['descriptionformat'] = FORMAT_MOODLE;
520 $record['timemodified'] = time();
522 if (isset($record['id'])) {
523 $DB->import_record('scale', $record);
524 $DB->get_manager()->reset_sequence('scale');
527 $id = $DB->insert_record('scale', $record);
530 return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
536 * Module generator base class.
538 * Extend in mod/xxxx/tests/generator/lib.php as class mod_xxxx_generator.
542 * @copyright 2012 Petr Skoda {@link http://skodak.org}
543 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
545 abstract class phpunit_module_generator {
546 /** @var phpunit_data_generator@var */
547 protected $datagenerator;
549 /** @var number of created instances */
550 protected $instancecount = 0;
552 public function __construct(phpunit_data_generator $datagenerator) {
553 $this->datagenerator = $datagenerator;
557 * To be called from data reset code only,
558 * do not use in tests.
561 public function reset() {
562 $this->instancecount = 0;
566 * Returns module name
567 * @return string name of module that this class describes
568 * @throws coding_exception if class invalid
570 public function get_modulename() {
572 if (!preg_match('/^mod_([a-z0-9]+)_generator$/', get_class($this), $matches)) {
573 throw new coding_exception('Invalid module generator class name: '.get_class($this));
576 if (empty($matches[1])) {
577 throw new coding_exception('Invalid module generator class name: '.get_class($this));
583 * Create course module and link it to course
584 * @param stdClass $instance
585 * @param array $options: section, visible
586 * @return stdClass $cm instance
588 protected function create_course_module(stdClass $instance, array $options) {
590 require_once("$CFG->dirroot/course/lib.php");
592 $modulename = $this->get_modulename();
594 $cm = new stdClass();
595 $cm->course = $instance->course;
596 $cm->module = $DB->get_field('modules', 'id', array('name'=>$modulename));
597 $cm->instance = $instance->id;
598 $cm->section = isset($options['section']) ? $options['section'] : 0;
599 $cm->idnumber = isset($options['idnumber']) ? $options['idnumber'] : 0;
602 $columns = $DB->get_columns('course_modules');
603 foreach ($options as $key=>$value) {
604 if ($key === 'id' or !isset($columns[$key])) {
607 if (property_exists($cm, $key)) {
613 $cm->id = $DB->insert_record('course_modules', $cm);
614 $cm->coursemodule = $cm->id;
616 add_mod_to_section($cm);
618 $cm = get_coursemodule_from_id($modulename, $cm->id, $cm->course, true, MUST_EXIST);
620 context_module::instance($cm->id);
626 * Create a test module
627 * @param array|stdClass $record
628 * @param array $options
629 * @return stdClass activity record
631 abstract public function create_instance($record = null, array $options = null);
636 * Block generator base class.
638 * Extend in blocks/xxxx/tests/generator/lib.php as class block_xxxx_generator.
642 * @copyright 2012 Petr Skoda {@link http://skodak.org}
643 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
645 abstract class phpunit_block_generator {
646 /** @var phpunit_data_generator@var */
647 protected $datagenerator;
649 /** @var number of created instances */
650 protected $instancecount = 0;
652 public function __construct(phpunit_data_generator $datagenerator) {
653 $this->datagenerator = $datagenerator;
657 * To be called from data reset code only,
658 * do not use in tests.
661 public function reset() {
662 $this->instancecount = 0;
667 * @return string name of block that this class describes
668 * @throws coding_exception if class invalid
670 public function get_blockname() {
672 if (!preg_match('/^block_([a-z0-9_]+)_generator$/', get_class($this), $matches)) {
673 throw new coding_exception('Invalid block generator class name: '.get_class($this));
676 if (empty($matches[1])) {
677 throw new coding_exception('Invalid block generator class name: '.get_class($this));
683 * Fill in record defaults
684 * @param stdClass $record
687 protected function prepare_record(stdClass $record) {
688 $record->blockname = $this->get_blockname();
689 if (!isset($record->parentcontextid)) {
690 $record->parentcontextid = context_system::instance()->id;
692 if (!isset($record->showinsubcontexts)) {
693 $record->showinsubcontexts = 1;
695 if (!isset($record->pagetypepattern)) {
696 $record->pagetypepattern = '';
698 if (!isset($record->subpagepattern)) {
699 $record->subpagepattern = null;
701 if (!isset($record->defaultregion)) {
702 $record->defaultregion = '';
704 if (!isset($record->defaultweight)) {
705 $record->defaultweight = '';
707 if (!isset($record->configdata)) {
708 $record->configdata = null;
714 * Create a test block
715 * @param array|stdClass $record
716 * @param array $options
717 * @return stdClass activity record
719 abstract public function create_instance($record = null, array $options = null);