MDL-37457 testing Refactoring references
[moodle.git] / lib / testing / generator / data_generator.php
CommitLineData
7e7cfe7a
PS
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/>.
16
17/**
18 * Data generator.
19 *
20 * @package core
f16e1af2 21 * @category testing
7e7cfe7a
PS
22 * @copyright 2012 Petr Skoda {@link http://skodak.org}
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26
27/**
28 * Data generator class for unit tests and other tools
29 * that need to create fake test sites.
30 *
31 * @package core
f16e1af2 32 * @category testing
7e7cfe7a
PS
33 * @copyright 2012 Petr Skoda {@link http://skodak.org}
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
5c3c2c81 36class testing_data_generator {
7e7cfe7a
PS
37 protected $usercounter = 0;
38 protected $categorycount = 0;
4729332b 39 protected $cohortcount = 0;
7e7cfe7a
PS
40 protected $coursecount = 0;
41 protected $scalecount = 0;
42 protected $groupcount = 0;
43 protected $groupingcount = 0;
44
45 /** @var array list of plugin generators */
46 protected $generators = array();
47
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 );
57
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 );
67
68 public $loremipsum = <<<EOD
69Lorem 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.
70Temporibus 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.
71Vivamus 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.
72Integer 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.
73In 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.
74EOD;
75
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;
86
5c3c2c81 87 foreach ($this->generators as $generator) {
7e7cfe7a
PS
88 $generator->reset();
89 }
90 }
91
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);
99
100 if ($type !== 'mod' and $type !== 'block') {
101 throw new coding_exception("Plugin type $type does not support generators yet");
102 }
103
104 $dir = get_plugin_directory($type, $plugin);
105
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 }
114
115 return $this->generators[$type.'_'.$plugin];
116 }
117
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;
126
127 $this->usercounter++;
128 $i = $this->usercounter;
129
130 $record = (array)$record;
131
132 if (!isset($record['auth'])) {
133 $record['auth'] = 'manual';
134 }
135
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)];
143
144 } else if (!isset($record['firstname'])) {
145 $record['firstname'] = 'Firstname'.$i;
146
147 } else if (!isset($record['lastname'])) {
148 $record['lastname'] = 'Lastname'.$i;
149 }
150
151 if (!isset($record['idnumber'])) {
152 $record['idnumber'] = '';
153 }
154
155 if (!isset($record['mnethostid'])) {
156 $record['mnethostid'] = $CFG->mnet_localhost_id;
157 }
158
159 if (!isset($record['username'])) {
fe67134e
PS
160 $record['username'] = 'username'.$i;
161 $j = 2;
7e7cfe7a 162 while ($DB->record_exists('user', array('username'=>$record['username'], 'mnethostid'=>$record['mnethostid']))) {
fe67134e
PS
163 $record['username'] = 'username'.$i.'_'.$j;
164 $j++;
7e7cfe7a
PS
165 }
166 }
167
168 if (!isset($record['password'])) {
169 $record['password'] = 'lala';
170 }
171
172 if (!isset($record['email'])) {
173 $record['email'] = $record['username'].'@example.com';
174 }
175
176 if (!isset($record['confirmed'])) {
177 $record['confirmed'] = 1;
178 }
179
180 if (!isset($record['lang'])) {
181 $record['lang'] = 'en';
182 }
183
184 if (!isset($record['maildisplay'])) {
185 $record['maildisplay'] = 1;
186 }
187
188 if (!isset($record['deleted'])) {
189 $record['deleted'] = 0;
190 }
191
192 $record['timecreated'] = time();
193 $record['timemodified'] = $record['timecreated'];
194 $record['lastip'] = '0.0.0.0';
195
196 $record['password'] = hash_internal_user_password($record['password']);
197
198 if ($record['deleted']) {
199 $delname = $record['email'].'.'.time();
200 while ($DB->record_exists('user', array('username'=>$delname))) {
201 $delname++;
202 }
203 $record['idnumber'] = '';
204 $record['email'] = md5($record['username']);
205 $record['username'] = $delname;
206 $record['picture'] = 0;
207 }
208
209 $userid = $DB->insert_record('user', $record);
210
211 if (!$record['deleted']) {
212 context_user::instance($userid);
213 }
214
215 return $DB->get_record('user', array('id'=>$userid), '*', MUST_EXIST);
216 }
217
218 /**
219 * Create a test course category
220 * @param array|stdClass $record
221 * @param array $options
222 * @return stdClass course category record
223 */
4729332b 224 public function create_category($record=null, array $options=null) {
7e7cfe7a
PS
225 global $DB, $CFG;
226 require_once("$CFG->dirroot/course/lib.php");
227
228 $this->categorycount++;
229 $i = $this->categorycount;
230
231 $record = (array)$record;
232
233 if (!isset($record['name'])) {
234 $record['name'] = 'Course category '.$i;
235 }
236
237 if (!isset($record['idnumber'])) {
238 $record['idnumber'] = '';
239 }
240
241 if (!isset($record['description'])) {
242 $record['description'] = "Test course category $i\n$this->loremipsum";
243 }
244
245 if (!isset($record['descriptionformat'])) {
4a38e659 246 $record['descriptionformat'] = FORMAT_MOODLE;
7e7cfe7a
PS
247 }
248
249 if (!isset($record['parent'])) {
4a38e659 250 $record['parent'] = 0;
7e7cfe7a
PS
251 }
252
253 if (empty($record['parent'])) {
254 $parent = new stdClass();
255 $parent->path = '';
256 $parent->depth = 0;
257 } else {
258 $parent = $DB->get_record('course_categories', array('id'=>$record['parent']), '*', MUST_EXIST);
259 }
260 $record['depth'] = $parent->depth+1;
261
262 $record['sortorder'] = 0;
263 $record['timemodified'] = time();
264 $record['timecreated'] = $record['timemodified'];
265
266 $catid = $DB->insert_record('course_categories', $record);
267 $path = $parent->path . '/' . $catid;
268 $DB->set_field('course_categories', 'path', $path, array('id'=>$catid));
269 context_coursecat::instance($catid);
270
271 fix_course_sortorder();
272
273 return $DB->get_record('course_categories', array('id'=>$catid), '*', MUST_EXIST);
274 }
275
4729332b
PS
276 /**
277 * Create test cohort.
278 * @param array|stdClass $record
279 * @param array $options
280 * @return stdClass cohort record
281 */
282 public function create_cohort($record=null, array $options=null) {
283 global $DB, $CFG;
284 require_once("$CFG->dirroot/cohort/lib.php");
285
286 $this->cohortcount++;
287 $i = $this->cohortcount;
288
289 $record = (array)$record;
290
291 if (!isset($record['contextid'])) {
292 $record['contextid'] = context_system::instance()->id;
293 }
294
295 if (!isset($record['name'])) {
296 $record['name'] = 'Cohort '.$i;
297 }
298
299 if (!isset($record['idnumber'])) {
300 $record['idnumber'] = '';
301 }
302
303 if (!isset($record['description'])) {
304 $record['description'] = "Test cohort $i\n$this->loremipsum";
305 }
306
307 if (!isset($record['descriptionformat'])) {
308 $record['descriptionformat'] = FORMAT_MOODLE;
309 }
310
311 if (!isset($record['component'])) {
312 $record['component'] = '';
313 }
314
315 $id = cohort_add_cohort((object)$record);
316
317 return $DB->get_record('cohort', array('id'=>$id), '*', MUST_EXIST);
318 }
319
7e7cfe7a
PS
320 /**
321 * Create a test course
322 * @param array|stdClass $record
323 * @param array $options with keys:
324 * 'createsections'=>bool precreate all sections
325 * @return stdClass course record
326 */
4729332b 327 public function create_course($record=null, array $options=null) {
7e7cfe7a
PS
328 global $DB, $CFG;
329 require_once("$CFG->dirroot/course/lib.php");
330
331 $this->coursecount++;
332 $i = $this->coursecount;
333
334 $record = (array)$record;
335
336 if (!isset($record['fullname'])) {
337 $record['fullname'] = 'Test course '.$i;
338 }
339
340 if (!isset($record['shortname'])) {
341 $record['shortname'] = 'tc_'.$i;
342 }
343
344 if (!isset($record['idnumber'])) {
345 $record['idnumber'] = '';
346 }
347
348 if (!isset($record['format'])) {
349 $record['format'] = 'topics';
350 }
351
352 if (!isset($record['newsitems'])) {
353 $record['newsitems'] = 0;
354 }
355
356 if (!isset($record['numsections'])) {
357 $record['numsections'] = 5;
358 }
359
4a38e659
PS
360 if (!isset($record['summary'])) {
361 $record['summary'] = "Test course $i\n$this->loremipsum";
7e7cfe7a
PS
362 }
363
4a38e659
PS
364 if (!isset($record['summaryformat'])) {
365 $record['summaryformat'] = FORMAT_MOODLE;
7e7cfe7a
PS
366 }
367
368 if (!isset($record['category'])) {
369 $record['category'] = $DB->get_field_select('course_categories', "MIN(id)", "parent=0");
370 }
371
372 $course = create_course((object)$record);
373 context_course::instance($course->id);
7e7cfe7a 374 if (!empty($options['createsections'])) {
384c3510
MG
375 if (isset($course->numsections)) {
376 course_create_sections_if_missing($course, range(0, $course->numsections));
377 } else {
378 course_create_sections_if_missing($course, 0);
7e7cfe7a
PS
379 }
380 }
381
382 return $course;
383 }
384
385 /**
386 * Create course section if does not exist yet
384c3510 387 * @param array|stdClass $record must contain 'course' and 'section' attributes
7e7cfe7a
PS
388 * @param array|null $options
389 * @return stdClass
390 * @throws coding_exception
391 */
392 public function create_course_section($record = null, array $options = null) {
393 global $DB;
394
395 $record = (array)$record;
396
397 if (empty($record['course'])) {
5c3c2c81 398 throw new coding_exception('course must be present in testing_data_generator::create_course_section() $record');
7e7cfe7a
PS
399 }
400
401 if (!isset($record['section'])) {
5c3c2c81 402 throw new coding_exception('section must be present in testing_data_generator::create_course_section() $record');
7e7cfe7a
PS
403 }
404
b46be6ad
MG
405 course_create_sections_if_missing($record['course'], $record['section']);
406 return get_fast_modinfo($record['course'])->get_section_info($record['section']);
7e7cfe7a
PS
407 }
408
409 /**
410 * Create a test block
411 * @param string $blockname
412 * @param array|stdClass $record
413 * @param array $options
414 * @return stdClass block instance record
415 */
416 public function create_block($blockname, $record=null, array $options=null) {
417 $generator = $this->get_plugin_generator('block_'.$blockname);
418 return $generator->create_instance($record, $options);
419 }
420
421 /**
422 * Create a test module
423 * @param string $modulename
424 * @param array|stdClass $record
425 * @param array $options
426 * @return stdClass activity record
427 */
428 public function create_module($modulename, $record=null, array $options=null) {
429 $generator = $this->get_plugin_generator('mod_'.$modulename);
430 return $generator->create_instance($record, $options);
431 }
432
433 /**
434 * Create a test group for the specified course
435 *
436 * $record should be either an array or a stdClass containing infomation about the group to create.
437 * At the very least it needs to contain courseid.
438 * Default values are added for name, description, and descriptionformat if they are not present.
439 *
440 * This function calls {@see groups_create_group()} to create the group within the database.
441 *
442 * @param array|stdClass $record
443 * @return stdClass group record
444 */
445 public function create_group($record) {
446 global $DB, $CFG;
447
448 require_once($CFG->dirroot . '/group/lib.php');
449
450 $this->groupcount++;
451 $i = $this->groupcount;
452
453 $record = (array)$record;
454
455 if (empty($record['courseid'])) {
5c3c2c81 456 throw new coding_exception('courseid must be present in testing_data_generator::create_group() $record');
7e7cfe7a
PS
457 }
458
459 if (!isset($record['name'])) {
460 $record['name'] = 'group-' . $i;
461 }
462
463 if (!isset($record['description'])) {
464 $record['description'] = "Test Group $i\n{$this->loremipsum}";
465 }
466
467 if (!isset($record['descriptionformat'])) {
468 $record['descriptionformat'] = FORMAT_MOODLE;
469 }
470
471 $id = groups_create_group((object)$record);
472
473 return $DB->get_record('groups', array('id'=>$id));
474 }
475
476 /**
477 * Create a test grouping for the specified course
478 *
479 * $record should be either an array or a stdClass containing infomation about the grouping to create.
480 * At the very least it needs to contain courseid.
481 * Default values are added for name, description, and descriptionformat if they are not present.
482 *
483 * This function calls {@see groups_create_grouping()} to create the grouping within the database.
484 *
485 * @param array|stdClass $record
486 * @return stdClass grouping record
487 */
488 public function create_grouping($record) {
489 global $DB, $CFG;
490
491 require_once($CFG->dirroot . '/group/lib.php');
492
493 $this->groupingcount++;
494 $i = $this->groupingcount;
495
496 $record = (array)$record;
497
498 if (empty($record['courseid'])) {
5c3c2c81 499 throw new coding_exception('courseid must be present in testing_data_generator::create_grouping() $record');
7e7cfe7a
PS
500 }
501
502 if (!isset($record['name'])) {
503 $record['name'] = 'grouping-' . $i;
504 }
505
506 if (!isset($record['description'])) {
507 $record['description'] = "Test Grouping $i\n{$this->loremipsum}";
508 }
509
510 if (!isset($record['descriptionformat'])) {
511 $record['descriptionformat'] = FORMAT_MOODLE;
512 }
513
514 $id = groups_create_grouping((object)$record);
515
516 return $DB->get_record('groupings', array('id'=>$id));
517 }
518
519 /**
520 * Create a test scale
521 * @param array|stdClass $record
522 * @param array $options
523 * @return stdClass block instance record
524 */
525 public function create_scale($record=null, array $options=null) {
526 global $DB;
527
528 $this->scalecount++;
529 $i = $this->scalecount;
530
531 $record = (array)$record;
532
533 if (!isset($record['name'])) {
534 $record['name'] = 'Test scale '.$i;
535 }
536
537 if (!isset($record['scale'])) {
538 $record['scale'] = 'A,B,C,D,F';
539 }
540
541 if (!isset($record['courseid'])) {
542 $record['courseid'] = 0;
543 }
544
545 if (!isset($record['userid'])) {
546 $record['userid'] = 0;
547 }
548
549 if (!isset($record['description'])) {
550 $record['description'] = 'Test scale description '.$i;
551 }
552
553 if (!isset($record['descriptionformat'])) {
554 $record['descriptionformat'] = FORMAT_MOODLE;
555 }
556
557 $record['timemodified'] = time();
558
559 if (isset($record['id'])) {
560 $DB->import_record('scale', $record);
561 $DB->get_manager()->reset_sequence('scale');
562 $id = $record['id'];
563 } else {
564 $id = $DB->insert_record('scale', $record);
565 }
566
567 return $DB->get_record('scale', array('id'=>$id), '*', MUST_EXIST);
568 }
4f5789ea
PS
569
570 /**
571 * Simplified enrolment of user to course using default options.
572 *
573 * It is strongly recommended to use only this method for 'manual' and 'self' plugins only!!!
574 *
575 * @param int $userid
576 * @param int $courseid
577 * @param int $roleid optional role id, use only with manual plugin
578 * @param string $enrol name of enrol plugin,
579 * there must be exactly one instance in course,
580 * it must support enrol_user() method.
581 * @return bool success
582 */
583 public function enrol_user($userid, $courseid, $roleid = null, $enrol = 'manual') {
584 global $DB;
585
586 if (!$plugin = enrol_get_plugin($enrol)) {
587 return false;
588 }
589
590 $instances = $DB->get_records('enrol', array('courseid'=>$courseid, 'enrol'=>$enrol));
591 if (count($instances) != 1) {
592 return false;
593 }
594 $instance = reset($instances);
595
596 if (is_null($roleid) and $instance->roleid) {
597 $roleid = $instance->roleid;
598 }
599
600 $plugin->enrol_user($instance, $userid, $roleid);
601
602 return true;
603 }
7e7cfe7a 604}