MDL-42151 behat: Adding the version to the testplan
[moodle.git] / admin / tool / generator / classes / testplan_backend.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  * Test plan generator.
19  *
20  * @package tool_generator
21  * @copyright 2013 David MonllaĆ³
22  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23  */
25 defined('MOODLE_INTERNAL') || die();
27 /**
28  * Generates the files required by JMeter.
29  *
30  * @package tool_generator
31  * @copyright 2013 David MonllaĆ³
32  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33  */
34 class tool_generator_testplan_backend extends tool_generator_backend {
36     /**
37      * @var The URL to the repository of the external project.
38      */
39     protected static $repourl = 'https://github.com/moodlehq/moodle-performance-comparison';
41     /**
42      * @var Number of users depending on the selected size.
43      */
44     protected static $users = array(1, 30, 200, 1000, 5000, 10000);
46     /**
47      * @var Number of loops depending on the selected size.
48      */
49     protected static $loops = array(1, 1, 2, 3, 3, 5);
51     /**
52      * @var Rampup period depending on the selected size.
53      */
54     protected static $rampups = array(1, 6, 40, 100, 500, 800);
56     /**
57      * Gets a list of size choices supported by this backend.
58      *
59      * @return array List of size (int) => text description for display
60      */
61     public static function get_size_choices() {
63         $options = array();
64         for ($size = self::MIN_SIZE; $size <= self::MAX_SIZE; $size++) {
65             $a = new stdClass();
66             $a->users = self::$users[$size];
67             $a->loops = self::$loops[$size];
68             $a->rampup = self::$rampups[$size];
69             $options[$size] = get_string('testplansize_' . $size, 'tool_generator', $a);
70         }
71         return $options;
72     }
74     /**
75      * Gets the list of courses that can be used used to generate a test.
76      *
77      * @return array The list of options as courseid => name
78      */
79     public static function get_course_options() {
80         $courses = get_courses('all', 'c.sortorder ASC', 'c.id, c.shortname, c.fullname');
81         if (!$courses) {
82             print_error('error_nocourses', 'tool_generator');
83         }
85         $options = array();
86         unset($courses[1]);
87         foreach ($courses as $course) {
88             $options[$course->id] = $course->fullname . '(' . $course->shortname . ')';
89         }
90         return $options;
91     }
93     /**
94      * Getter for moodle-performance-comparison project URL.
95      *
96      * @return string
97      */
98     public static function get_repourl() {
99         return self::$repourl;
100     }
102     /**
103      * Creates the test plan file.
104      *
105      * @param int $courseid The target course id
106      * @param int $size The test plan size
107      * @return stored_file
108      */
109     public static function create_testplan_file($courseid, $size) {
110         $jmxcontents = self::generate_test_plan($courseid, $size);
112         $fs = get_file_storage();
113         $filerecord = self::get_file_record('testplan', 'jmx');
114         return $fs->create_file_from_string($filerecord, $jmxcontents);
115     }
117     /**
118      * Creates the users data file.
119      *
120      * @param int $courseid The target course id
121      * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
122      * @return stored_file
123      */
124     public static function create_users_file($courseid, $updateuserspassword) {
125         $csvcontents = self::generate_users_file($courseid, $updateuserspassword);
127         $fs = get_file_storage();
128         $filerecord = self::get_file_record('users', 'csv');
129         return $fs->create_file_from_string($filerecord, $csvcontents);
130     }
132     /**
133      * Generates the test plan according to the target course contents.
134      *
135      * @param int $targetcourseid The target course id
136      * @param int $size The test plan size
137      * @return string The test plan as a string
138      */
139     protected static function generate_test_plan($targetcourseid, $size) {
140         global $CFG;
142         // Getting the template.
143         $template = file_get_contents(__DIR__ . '/../testplan.template.jmx');
145         // Getting the course modules data.
146         $coursedata = self::get_course_test_data($targetcourseid);
148         // Host and path to the site.
149         $urlcomponents = parse_url($CFG->wwwroot);
150         if (empty($urlcomponents['path'])) {
151             $urlcomponents['path'] = '';
152         }
154         $replacements = array(
155             $CFG->version,
156             self::$users[$size],
157             self::$loops[$size],
158             self::$rampups[$size],
159             $urlcomponents['host'],
160             $urlcomponents['path'],
161             get_string('shortsize_' . $size, 'tool_generator'),
162             $targetcourseid,
163             $coursedata->pageid,
164             $coursedata->forumid,
165             $coursedata->forumdiscussionid,
166             $coursedata->forumreplyid
167         );
169         $placeholders = array(
170             '{{MOODLEVERSION_PLACEHOLDER}}',
171             '{{USERS_PLACEHOLDER}}',
172             '{{LOOPS_PLACEHOLDER}}',
173             '{{RAMPUP_PLACEHOLDER}}',
174             '{{HOST_PLACEHOLDER}}',
175             '{{SITEPATH_PLACEHOLDER}}',
176             '{{SIZE_PLACEHOLDER}}',
177             '{{COURSEID_PLACEHOLDER}}',
178             '{{PAGEACTIVITYID_PLACEHOLDER}}',
179             '{{FORUMACTIVITYID_PLACEHOLDER}}',
180             '{{FORUMDISCUSSIONID_PLACEHOLDER}}',
181             '{{FORUMREPLYID_PLACEHOLDER}}'
182         );
184         // Fill the template with the target course values.
185         return str_replace($placeholders, $replacements, $template);
186     }
188     /**
189      * Generates the user's credentials file with all the course's users
190      *
191      * @param int $targetcourseid
192      * @param bool $updateuserspassword Updates the course users password to $CFG->tool_generator_users_password
193      * @return string The users csv file contents.
194      */
195     protected static function generate_users_file($targetcourseid, $updateuserspassword) {
196         global $CFG;
198         $coursecontext = context_course::instance($targetcourseid);
200         $users = get_enrolled_users($coursecontext, '', 0, 'u.id, u.username, u.auth', 'u.username ASC');
201         if (!$users) {
202             print_error('coursewithoutusers', 'tool_generator');
203         }
205         $lines = array();
206         foreach ($users as $user) {
208             // Updating password to the one set in config.php.
209             if ($updateuserspassword) {
210                 $userauth = get_auth_plugin($user->auth);
211                 if (!$userauth->user_update_password($user, $CFG->tool_generator_users_password)) {
212                     print_error('errorpasswordupdate', 'auth');
213                 }
214             }
216             // Here we already checked that $CFG->tool_generator_users_password is not null.
217             $lines[] = $user->username . ',' . $CFG->tool_generator_users_password;
218         }
220         return implode(PHP_EOL, $lines);
221     }
223     /**
224      * Returns a tool_generator file record
225      *
226      * @param string $filearea testplan or users
227      * @param string $filetype The file extension jmx or csv
228      * @return stdClass The file record to use when creating tool_generator files
229      */
230     protected static function get_file_record($filearea, $filetype) {
232         $systemcontext = context_system::instance();
234         $filerecord = new stdClass();
235         $filerecord->contextid = $systemcontext->id;
236         $filerecord->component = 'tool_generator';
237         $filerecord->filearea = $filearea;
238         $filerecord->itemid = 0;
239         $filerecord->filepath = '/';
241         // Random generated number to avoid concurrent execution problems.
242         $filerecord->filename = $filearea . '_' . date('YmdHi', time()) . '_' . rand(1000, 9999) . '.' . $filetype;
244         return $filerecord;
245     }
247     /**
248      * Gets the data required to fill the test plan template with the database contents.
249      *
250      * @param int $targetcourseid The target course id
251      * @return stdClass The ids required by the test plan
252      */
253     protected static function get_course_test_data($targetcourseid) {
254         global $DB, $USER;
256         $data = new stdClass();
258         // Getting course contents info as the current user (will be an admin).
259         $course = new stdClass();
260         $course->id = $targetcourseid;
261         $courseinfo = new course_modinfo($course, $USER->id);
263         // Getting the first page module instance.
264         if (!$pages = $courseinfo->get_instances_of('page')) {
265             print_error('error_nopageinstances', 'tool_generator');
266         }
267         $data->pageid = reset($pages)->id;
269         // Getting the first forum module instance and it's first discussion and reply as well.
270         if (!$forums = $courseinfo->get_instances_of('forum')) {
271             print_error('error_noforuminstances', 'tool_generator');
272         }
273         $forum = reset($forums);
275         // Getting the first discussion (and reply).
276         if (!$discussions = forum_get_discussions($forum, 'd.timemodified ASC', false, -1, 1)) {
277             print_error('error_noforumdiscussions', 'tool_generator');
278         }
279         $discussion = reset($discussions);
281         $data->forumid = $forum->id;
282         $data->forumdiscussionid = $discussion->discussion;
283         $data->forumreplyid = $discussion->id;
285         // According to the current test plan.
286         return $data;
287     }
289     /**
290      * Checks if the selected target course is ok.
291      *
292      * @param int|string $course
293      * @param int $size
294      * @return array Errors array or false if everything is ok
295      */
296     public static function has_selected_course_any_problem($course, $size) {
297         global $DB;
299         $errors = array();
301         if (!is_numeric($course)) {
302             if (!$course = $DB->get_field('course', 'id', array('shortname' => $course))) {
303                 $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
304                 return $errors;
305             }
306         }
308         $coursecontext = context_course::instance($course, IGNORE_MISSING);
309         if (!$coursecontext) {
310             $errors['courseid'] = get_string('error_nonexistingcourse', 'tool_generator');
311             return $errors;
312         }
314         if (!$users = get_enrolled_users($coursecontext, '', 0, 'u.id')) {
315             $errors['courseid'] = get_string('coursewithoutusers', 'tool_generator');
316         }
318         // Checks that the selected course has enough users.
319         $coursesizes = tool_generator_course_backend::get_users_per_size();
320         if (count($users) < $coursesizes[$size]) {
321             $errors['size'] = get_string('notenoughusers', 'tool_generator');
322         }
324         if (empty($errors)) {
325             return false;
326         }
328         return $errors;
329     }