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