MDL-37046 behat: Steps definitions loading
[moodle.git] / admin / tool / behat / locallib.php
CommitLineData
7f541ea3
DM
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 * Behat commands
19 *
20 * @package tool_behat
21 * @copyright 2012 David MonllaĆ³
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
24
25require_once($CFG->libdir . '/filestorage/file_exceptions.php');
26require_once($CFG->libdir . '/phpunit/bootstraplib.php');
33005f68 27require_once($CFG->libdir . '/phpunit/classes/tests_finder.php');
7f541ea3 28
6d994c51
DM
29/**
30 * Behat commands manager
31 *
32 * CLI + web execution
33 *
34 * @package tool_behat
35 * @copyright 2012 David MonllaĆ³
36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37 */
7f541ea3
DM
38class tool_behat {
39
93245e95
DM
40 private static $behat_tests_path = '/tests/behat';
41
7f541ea3
DM
42 /**
43 * Displays basic info about acceptance tests
44 */
45 public static function info() {
46
33005f68
DM
47 $html = self::get_header();
48 $html .= self::get_info();
33005f68 49 $html .= self::get_switch_environment_form();
554820dd
DM
50
51 if (!self::is_test_environment_running()) {
52 $html .= self::get_steps_definitions_form();
53 $html .= self::get_run_tests_form();
54 }
55
33005f68 56 $html .= self::get_footer();
7f541ea3
DM
57
58 echo $html;
59 }
60
7f541ea3
DM
61 /**
62 * Lists the available steps definitions
6d994c51 63 * @param string $filter Keyword to filter the list of steps definitions availables
7f541ea3 64 */
6d994c51 65 public static function stepsdefinitions($filter = false) {
7f541ea3
DM
66 global $CFG;
67
33005f68
DM
68 if (!CLI_SCRIPT) {
69 confirm_sesskey();
70 }
71 self::check_behat_setup();
7f541ea3 72
6d994c51
DM
73 // Priority to the one specified as argument.
74 if (!$filter) {
75 $filter = optional_param('filter', false, PARAM_ALPHANUMEXT);
76 }
77
78 if ($filter) {
7f541ea3
DM
79 $filteroption = ' -d ' . $filter;
80 } else {
81 $filteroption = ' -di';
82 }
83
84 $currentcwd = getcwd();
85 chdir($CFG->behatpath);
86 exec('bin/behat' . $filteroption, $steps, $code);
87 chdir($currentcwd);
88
89 // Outputing steps.
33005f68
DM
90 $html = self::get_header();
91 $html .= self::get_steps_definitions_form($filter);
7f541ea3
DM
92
93 $content = '';
94 if ($steps) {
95 foreach ($steps as $line) {
96
97 // Skipping the step definition context.
98 if (strpos($line, '#') == 0) {
99 $content .= htmlentities($line) . '<br/>';
100 }
101 }
102 }
103
104 if ($content === '') {
105 $content = get_string('nostepsdefinitions', 'tool_behat');
106 }
107
108 $html .= html_writer::tag('div', $content, array('id' => 'steps-definitions'));
33005f68 109 $html .= self::get_footer();
cc544646 110
7f541ea3
DM
111 echo $html;
112 }
113
cc544646
DM
114 /**
115 * Switches from and to the regular environment to the testing environment
6d994c51 116 * @param string $testenvironment enable|disable
cc544646 117 */
6d994c51 118 public static function switchenvironment($testenvironment = false) {
cc544646
DM
119 global $CFG;
120
33005f68
DM
121 if (!CLI_SCRIPT) {
122 confirm_sesskey();
123 }
6d994c51
DM
124
125 // Priority to the one specified as argument.
126 if (!$testenvironment) {
127 $testenvironment = optional_param('testenvironment', 'enable', PARAM_ALPHA);
128 }
129
cc544646 130 if ($testenvironment == 'enable') {
33005f68 131 self::enable_test_environment();
cc544646 132 } else if ($testenvironment == 'disable') {
33005f68 133 self::disable_test_environment();
cc544646
DM
134 }
135
554820dd
DM
136 if (!CLI_SCRIPT) {
137 redirect(get_login_url());
138 }
cc544646 139 }
7f541ea3 140
7f541ea3
DM
141 /**
142 * Runs the acceptance tests
6d994c51
DM
143 * @param string $tags Restricts the executed tests to the ones that matches the tags
144 * @param string $extra Extra CLI behat options
7f541ea3 145 */
6d994c51 146 public static function runtests($tags = false, $extra = false) {
7f541ea3
DM
147 global $CFG;
148
33005f68
DM
149 if (!CLI_SCRIPT) {
150 confirm_sesskey();
151 }
152 self::check_behat_setup();
7f541ea3 153
8cdc0ce8
DM
154 self::update_config_file();
155
7f541ea3
DM
156 @set_time_limit(0);
157
6d994c51
DM
158 // Priority to the one specified as argument.
159 if (!$tags) {
160 $tags = optional_param('tags', false, PARAM_ALPHANUMEXT);
161 }
162 // $extra only passed as CLI option (to simplify web runner usage).
163
7f541ea3 164 $tagsoption = '';
6d994c51 165 if ($tags) {
7f541ea3
DM
166 $tagsoption = ' --tags ' . $tags;
167 }
168
6d994c51
DM
169 if (!$extra && !CLI_SCRIPT) {
170 $extra = ' --format html';
171 } else if(!$extra && CLI_SCRIPT) {
172 $extra = '';
173 }
174
7f541ea3 175 // Switching database and dataroot to test environment.
33005f68 176 self::enable_test_environment();
7f541ea3
DM
177 $currentcwd = getcwd();
178
7f541ea3 179 chdir($CFG->behatpath);
93245e95
DM
180 ob_start();
181 passthru('bin/behat --ansi ' . $tagsoption . ' ' .$extra, $code);
182 $output = ob_get_contents();
183 ob_end_clean();
7f541ea3 184
93245e95 185 // Switching back to regular environment.
33005f68 186 self::disable_test_environment();
7f541ea3 187 chdir($currentcwd);
cc544646 188
93245e95
DM
189 // Output.
190 echo self::get_header();
191 if (!CLI_SCRIPT) {
192 echo self::get_run_tests_form($tags);
193 }
194 echo $output;
195
196 // Dirty hack to avoid behat bad HTML structure when test execution throws an exception and there are skipped steps.
197 if (strstr($output, 'class="skipped"') != false) {
198 echo '</ol></div></div></div></body></html>';
199 }
200
33005f68 201 echo self::get_footer();
7f541ea3
DM
202 }
203
8cdc0ce8
DM
204 /**
205 * Updates the config file
206 * @throws file_exception
207 */
208 private static function update_config_file() {
209
93245e95
DM
210 $contents = '';
211
212 // Gets all the components with features.
8cdc0ce8
DM
213 $components = tests_finder::get_components_with_tests('features');
214 if ($components) {
93245e95 215 $contents .= 'features:' . PHP_EOL;
8cdc0ce8 216 foreach ($components as $componentname => $path) {
93245e95 217 $path = self::clean_path($path) . self::$behat_tests_path;
8cdc0ce8
DM
218 $contents .= ' - ' . $path . PHP_EOL;
219 }
220 }
221
93245e95
DM
222 // Gets all the components with steps definitions.
223 $components = tests_finder::get_components_with_tests('stepsdefinitions');
224 if ($components) {
225 $contents .= 'steps_definitions:' . PHP_EOL;
226 foreach ($components as $componentname => $filepath) {
227 $filepath = self::clean_path($path) . self::$behat_tests_path . '/behat_' . $componentname . '.php';
228 $contents .= ' ' . $componentname . ': ' . $filepath . PHP_EOL;
229 }
230 }
8cdc0ce8 231
93245e95 232 // Stores the file.
8cdc0ce8
DM
233 $fullfilepath = self::get_behat_dir() . '/config.yml';
234 if (!file_put_contents($fullfilepath, $contents)) {
235 throw new file_exception('cannotcreatefile', $fullfilepath);
236 }
237 }
7f541ea3 238
93245e95
DM
239
240 /**
241 * Cleans the path returned by get_components_with_tests() to standarize it
242 *
243 * {@see tests_finder::get_all_directories_with_tests()} it returns the path including /tests/
244 * @param string $path
245 * @return string The string without the last /tests part
246 */
247 private static function clean_path($path) {
248
249 $path = rtrim($path, '/');
250
251 $parttoremove = '/tests';
252
253 $substr = substr($path, strlen($path) - strlen($parttoremove));
254 if ($substr == $parttoremove) {
255 $path = substr($path, 0, strlen($path) - strlen($parttoremove));
256 }
257
258 return rtrim($path, '/');
259 }
260
7f541ea3
DM
261 /**
262 * Checks whether the test database and dataroot is ready
263 * Stops execution if something went wrong
264 */
265 private static function test_environment_problem() {
266 global $CFG;
267
268 // phpunit --diag returns nothing if the test environment is set up correctly.
269 $currentcwd = getcwd();
270 chdir($CFG->dirroot . '/' . $CFG->admin . '/tool/phpunit/cli');
271 exec("php util.php --diag", $output, $code);
272 chdir($currentcwd);
273
274 // If something is not ready stop execution and display the CLI command output.
275 if ($code != 0) {
276 notice(implode(' ', $output));
277 }
278 }
279
280 /**
281 * Checks the behat setup
282 */
283 private static function check_behat_setup() {
284 global $CFG;
285
286 // Moodle setting.
287 if (empty($CFG->behatpath)) {
288 $msg = get_string('nobehatpath', 'tool_behat');
289 $url = $CFG->wwwroot . '/' . $CFG->admin . '/settings.php?section=systempaths';
290
291 if (!CLI_SCRIPT) {
292 $msg .= ' ' . html_writer::tag('a', get_string('systempaths', 'admin'), array('href' => $url));
293 }
294 notice($msg);
295 }
296
297 // Behat test command.
298 $currentcwd = getcwd();
299 chdir($CFG->behatpath);
300 exec('bin/behat --help', $output, $code);
301 chdir($currentcwd);
302
303 if ($code != 0) {
304 notice(get_string('wrongbehatsetup', 'tool_behat'));
305 }
306 }
307
308 /**
309 * Enables test mode checking the test environment setup
310 *
cc544646 311 * Stores a file in dataroot/behat to allow Moodle switch to
7f541ea3
DM
312 * test database and dataroot before the initial set up
313 *
314 * @throws file_exception
315 */
cc544646 316 private static function enable_test_environment() {
554820dd 317 global $CFG;
7f541ea3 318
33005f68 319 if (self::is_test_environment_enabled()) {
7f541ea3
DM
320 debugging('Test environment was already enabled');
321 return;
322 }
323
324 // Check that PHPUnit test environment is correctly set up.
33005f68 325 self::test_environment_problem();
7f541ea3 326
8cdc0ce8 327 $behatdir = self::get_behat_dir();
7f541ea3 328
33005f68 329 $contents = '$CFG->phpunit_prefix and $CFG->phpunit_dataroot are currently used as $CFG->prefix and $CFG->dataroot';
7f541ea3 330 $filepath = $behatdir . '/test_environment_enabled.txt';
33005f68 331 if (!file_put_contents($filepath, $contents)) {
7f541ea3
DM
332 throw new file_exception('cannotcreatefile', $filepath);
333 }
554820dd 334 chmod($filepath, $CFG->directorypermissions);
7f541ea3
DM
335 }
336
337 /**
338 * Disables test mode
339 */
cc544646 340 private static function disable_test_environment() {
7f541ea3 341
33005f68 342 $testenvfile = self::get_test_filepath();
7f541ea3 343
33005f68 344 if (!self::is_test_environment_enabled()) {
7f541ea3
DM
345 debugging('Test environment was already disabled');
346 } else {
cc544646
DM
347 if (!unlink($testenvfile)) {
348 throw new file_exception('cannotdeletetestenvironmentfile');
349 }
7f541ea3
DM
350 }
351 }
352
353 /**
354 * Checks whether test environment is enabled or disabled
cc544646
DM
355 *
356 * It does not return if the current script is running
357 * in test environment {@see tool_behat::is_test_environment_running()}
358 *
359 * @return bool
7f541ea3 360 */
554820dd 361 private static function is_test_environment_enabled() {
7f541ea3 362
33005f68 363 $testenvfile = self::get_test_filepath();
7f541ea3
DM
364 if (file_exists($testenvfile)) {
365 return true;
366 }
367
368 return false;
369 }
370
cc544646
DM
371 /**
372 * Returns true if Moodle is currently running with the test database and dataroot
373 * @return bool
374 */
554820dd 375 private static function is_test_environment_running() {
cc544646
DM
376 global $CFG;
377
378 if (!empty($CFG->originaldataroot)) {
379 return true;
380 }
381
382 return false;
383 }
384
385 /**
386 * Returns the path to the file which specifies if test environment is enabled
387 * @return string
388 */
389 private static function get_test_filepath() {
390 global $CFG;
391
33005f68 392 if (self::is_test_environment_running()) {
cc544646
DM
393 $testenvfile = $CFG->originaldataroot . '/behat/test_environment_enabled.txt';
394 } else {
395 $testenvfile = $CFG->dataroot . '/behat/test_environment_enabled.txt';
396 }
397
398 return $testenvfile;
399 }
400
8cdc0ce8
DM
401
402 /**
403 * Ensures the behat dir exists in moodledata
404 * @throws file_exception
405 * @return string Full path
406 */
407 private static function get_behat_dir() {
408 global $CFG;
409
410 $behatdir = $CFG->dataroot . '/behat';
411
412 if (!is_dir($behatdir)) {
413 if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
414 throw new file_exception('storedfilecannotcreatefiledirs');
415 }
416 }
417
418 if (!is_writable($behatdir)) {
419 throw new file_exception('storedfilecannotcreatefiledirs');
420 }
421
422 return $behatdir;
423 }
424
cc544646
DM
425 /**
426 * Returns header output
427 * @return string
428 */
429 private static function get_header() {
430 global $OUTPUT;
431
33005f68
DM
432 $action = optional_param('action', 'info', PARAM_ALPHAEXT);
433
cc544646 434 if (CLI_SCRIPT) {
33005f68 435 return '';
cc544646
DM
436 }
437
cc544646
DM
438 $title = get_string('pluginname', 'tool_behat') . ' - ' . get_string('command' . $action, 'tool_behat');
439 $html = $OUTPUT->header();
440 $html .= $OUTPUT->heading($title);
441
442 return $html;
443 }
444
445 /**
446 * Returns footer output
447 * @return string
448 */
449 private static function get_footer() {
450 global $OUTPUT;
451
452 if (CLI_SCRIPT) {
453 return '';
454 }
455
456 return $OUTPUT->footer();
457 }
458
33005f68
DM
459 /**
460 * Returns a message and a button to continue if web execution
461 * @param string $html
462 * @param string $url
463 * @return string
464 */
465 private static function output_success($html, $url = false) {
466 global $CFG, $OUTPUT;
467
468 if (!$url) {
469 $url = $CFG->wwwroot . '/' . $CFG->admin . '/tool/behat/index.php';
470 }
471
472 if (!CLI_SCRIPT) {
473 $html = $OUTPUT->box($html, 'generalbox', 'notice');
474 $html .= $OUTPUT->continue_button($url);
475 }
476
477 return $html;
478 }
479
7f541ea3
DM
480 /**
481 * Returns the installation instructions
482 *
483 * (hardcoded in English)
484 *
485 * @return string
486 */
487 private static function get_info() {
488 global $OUTPUT;
489
554820dd
DM
490 $url = 'http://docs.moodle.org/dev/Behat_integration#Installation';
491
7f541ea3 492 $html = $OUTPUT->box_start();
554820dd
DM
493 $html .= html_writer::tag('h1', 'Installation info');
494 $html .= html_writer::tag('div', 'Follow <a href="' . $url . '" target="_blank">' . $url . '</a> instructions');
7f541ea3
DM
495 $html .= $OUTPUT->box_end();
496
497 return $html;
498 }
499
cc544646
DM
500 /**
501 * Returns a button to switch between environments
502 * @return string
503 */
504 private static function get_switch_environment_form() {
505 global $CFG, $OUTPUT;
506
33005f68 507 if (self::is_test_environment_running()) {
cc544646
DM
508 $perform = 'disable';
509 } else {
510 $perform = 'enable';
511 }
512 $params = array('action' => 'switchenvironment', 'testenvironment' => $perform, 'sesskey' => sesskey());
513 $url = new moodle_url('/' . $CFG->admin . '/tool/behat/index.php', $params);
514
515 $html = $OUTPUT->box_start();
516 $html .= '<p>' . get_string('switchenvironmentinfo', 'tool_behat') . '</p>';
517 $html .= $OUTPUT->single_button($url, get_string('switchenvironment' . $perform, 'tool_behat'));
518 $html .= $OUTPUT->box_end();
519
520 return $html;
521 }
522
7f541ea3
DM
523 /**
524 * Returns the steps definitions form
525 * @param string $filter To filter the steps definitions list by keyword
526 * @return string
527 */
528 private static function get_steps_definitions_form($filter = false) {
529 global $OUTPUT;
530
531 if ($filter === false) {
532 $filter = '';
533 } else {
534 $filter = s($filter);
535 }
536
537 $html = $OUTPUT->box_start();
538 $html .= '<form method="get" action="index.php">';
7f541ea3
DM
539 $html .= '<fieldset class="invisiblefieldset">';
540 $html .= '<label for="id_filter">Steps definitions which contains</label> ';
541 $html .= '<input type="text" id="id_filter" value="' . $filter . '" name="filter"/> (all steps definitions if empty)';
33005f68 542 $html .= '<p></p>';
7f541ea3
DM
543 $html .= '<input type="submit" value="View available steps definitions" />';
544 $html .= '<input type="hidden" name="action" value="stepsdefinitions" />';
545 $html .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
546 $html .= '</fieldset>';
547 $html .= '</form>';
548 $html .= $OUTPUT->box_end();
549
550 return $html;
551 }
552
553 /**
554 * Returns the run tests form
555 * @param string $tags To execute only the tests that matches the specified tag
556 * @return string
557 */
558 private static function get_run_tests_form($tags = false) {
559 global $OUTPUT;
560
561 if ($tags === false) {
562 $tags = '';
563 } else {
564 $tags = s($tags);
565 }
566
567 $html = $OUTPUT->box_start();
568 $html .= '<form method="get" action="index.php">';
7f541ea3
DM
569 $html .= '<fieldset class="invisiblefieldset">';
570 $html .= '<label for="id_tags">Tests tagged as</label> ';
571 $html .= '<input type="text" id="id_tags" value="' . $tags . '" name="tags"/> (all available tests if empty)';
33005f68 572 $html .= '<p></p>';
7f541ea3
DM
573 $html .= '<input type="submit" value="Run tests" />';
574 $html .= '<input type="hidden" name="action" value="runtests" />';
575 $html .= '<input type="hidden" name="sesskey" value="'.sesskey().'" />';
576 $html .= '</fieldset>';
577 $html .= '</form>';
578 $html .= $OUTPUT->box_end();
579
580 return $html;
581 }
7f541ea3 582}