MDL-37046 behat: Removing MinkContext
[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
d46340eb
DM
29require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/steps_definitions_form.php');
30
6d994c51
DM
31/**
32 * Behat commands manager
33 *
6d994c51
DM
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
d46340eb
DM
40 /**
41 * Path where each component's tests are stored
42 * @var string
43 */
93245e95
DM
44 private static $behat_tests_path = '/tests/behat';
45
7f541ea3 46 /**
d46340eb
DM
47 * Steps types
48 * @var array
7f541ea3 49 */
d46340eb 50 private static $steps_types = array('given', 'when', 'then');
7f541ea3 51
a09534f4 52 /** @var string Docu url */
d46340eb 53 public static $docsurl = 'http://docs.moodle.org/dev/Acceptance_testing';
7f541ea3 54
7f541ea3
DM
55 /**
56 * Lists the available steps definitions
d46340eb
DM
57 *
58 * @param string $type
59 * @param string $component
60 * @param string $filter
61 * @return string
7f541ea3 62 */
d46340eb 63 public static function stepsdefinitions($type, $component, $filter) {
7f541ea3
DM
64 global $CFG;
65
33005f68 66 self::check_behat_setup();
7f541ea3 67
d46340eb
DM
68 // The loaded steps depends on the component specified.
69 self::update_config_file($component);
df8dcf3e 70
a09534f4 71 // The Moodle\BehatExtension\HelpPrinter\MoodleDefinitionsPrinter will parse this search format.
d46340eb
DM
72 if ($type) {
73 $filter .= '&&' . $type;
6d994c51
DM
74 }
75
76 if ($filter) {
d46340eb 77 $filteroption = ' -d "' . $filter . '"';
7f541ea3
DM
78 } else {
79 $filteroption = ' -di';
80 }
81
82 $currentcwd = getcwd();
cb18fd71 83 chdir($CFG->dirroot . '/lib/behat');
d46340eb 84 exec('bin/behat --config="' . self::get_behat_config_filepath() . '" ' . $filteroption, $steps, $code);
7f541ea3
DM
85 chdir($currentcwd);
86
7f541ea3 87 if ($steps) {
d46340eb 88 $stepshtml = implode('', $steps);
7f541ea3
DM
89 }
90
d46340eb
DM
91 if (!isset($stepshtml) || $stepshtml == '') {
92 $stepshtml = get_string('nostepsdefinitions', 'tool_behat');
7f541ea3
DM
93 }
94
d46340eb 95 return $stepshtml;
7f541ea3
DM
96 }
97
cc544646 98 /**
cca2c806
DM
99 * Allows / disables the test environment to be accesses through the built-in server
100 *
101 * Built-in server must be started separately
102 *
6d994c51 103 * @param string $testenvironment enable|disable
cc544646 104 */
6d994c51 105 public static function switchenvironment($testenvironment = false) {
cc544646
DM
106 global $CFG;
107
6d994c51
DM
108 // Priority to the one specified as argument.
109 if (!$testenvironment) {
110 $testenvironment = optional_param('testenvironment', 'enable', PARAM_ALPHA);
111 }
112
cc544646 113 if ($testenvironment == 'enable') {
cca2c806 114 self::start_test_mode();
cc544646 115 } else if ($testenvironment == 'disable') {
cca2c806 116 self::stop_test_mode();
cc544646 117 }
cc544646 118 }
7f541ea3 119
7f541ea3
DM
120 /**
121 * Runs the acceptance tests
cca2c806 122 *
a09534f4
DM
123 * It starts test mode and runs the built-in PHP
124 * erver and stops it all then it's done
cca2c806 125 *
a09534f4 126 * @param boolean $withjavascript Include tests with javascript
6d994c51
DM
127 * @param string $tags Restricts the executed tests to the ones that matches the tags
128 * @param string $extra Extra CLI behat options
7f541ea3 129 */
a09534f4 130 public static function runtests($withjavascript = false, $tags = false, $extra = '') {
7f541ea3
DM
131 global $CFG;
132
a2a98438
DM
133 // Checks the behat set up and the PHP version.
134 self::check_behat_setup(true);
7f541ea3 135
cca2c806
DM
136 // Check that PHPUnit test environment is correctly set up.
137 self::test_environment_problem();
138
a09534f4 139 // Updates all the Moodle features and steps definitions.
8cdc0ce8
DM
140 self::update_config_file();
141
7f541ea3
DM
142 @set_time_limit(0);
143
d46340eb 144 // No javascript by default.
25833ef8 145 if (!$withjavascript && strstr($tags, 'javascript') == false) {
a09534f4 146 $jsstr = '~@javascript';
6d994c51 147 }
6d994c51 148
25833ef8 149 // Adding javascript option to --tags.
7f541ea3 150 $tagsoption = '';
6d994c51 151 if ($tags) {
25833ef8 152 if (!empty($jsstr)) {
a09534f4 153 $tags .= '&&' . $jsstr;
25833ef8 154 }
a09534f4 155 $tagsoption = " --tags '" . $tags . "'";
25833ef8
DM
156
157 // No javascript by default.
158 } else if (!empty($jsstr)) {
a09534f4 159 $tagsoption = " --tags '" . $jsstr . "'";
6d994c51
DM
160 }
161
d46340eb 162 // Starts built-in server and inits test mode.
cca2c806
DM
163 self::start_test_mode();
164 $server = self::start_test_server();
7f541ea3 165
cb18fd71 166 // Runs the tests switching the current working directory to behat path.
cca2c806 167 $currentcwd = getcwd();
cb18fd71 168 chdir($CFG->dirroot . '/lib/behat');
93245e95 169 ob_start();
df8dcf3e 170 passthru('bin/behat --ansi --config="' . self::get_behat_config_filepath() .'" ' . $tagsoption . ' ' .$extra, $code);
93245e95
DM
171 $output = ob_get_contents();
172 ob_end_clean();
7f541ea3 173 chdir($currentcwd);
cc544646 174
d46340eb 175 // Stops built-in server and stops test mode.
cca2c806
DM
176 self::stop_test_server($server[0], $server[1]);
177 self::stop_test_mode();
178
93245e95 179 // Output.
93245e95 180 echo $output;
7f541ea3
DM
181 }
182
8cdc0ce8
DM
183 /**
184 * Updates the config file
d46340eb 185 * @param string $component Restricts the obtained steps definitions to the specified component
8cdc0ce8
DM
186 * @throws file_exception
187 */
d46340eb 188 private static function update_config_file($component = '') {
df8dcf3e 189 global $CFG;
8cdc0ce8 190
cb18fd71 191 $behatpath = $CFG->dirroot . '/lib/behat';
df8dcf3e 192
a09534f4
DM
193 // Behat config file specifing the main context class,
194 // the required Behat extensions and Moodle test wwwroot.
df8dcf3e
DM
195 $contents = 'default:
196 paths:
197 features: ' . $behatpath . '/features
198 bootstrap: ' . $behatpath . '/features/bootstrap
d46340eb 199 context:
cb18fd71 200 class: behat_init_context
df8dcf3e
DM
201 extensions:
202 Behat\MinkExtension\Extension:
cca2c806 203 base_url: ' . $CFG->test_wwwroot . '
df8dcf3e
DM
204 goutte: ~
205 selenium2: ~
cb18fd71 206 ' . $CFG->dirroot . '/vendor/moodlehq/behat-extension/init.php:
df8dcf3e 207';
93245e95
DM
208
209 // Gets all the components with features.
8cdc0ce8
DM
210 $components = tests_finder::get_components_with_tests('features');
211 if ($components) {
df8dcf3e 212 $featurespaths = array('');
8cdc0ce8 213 foreach ($components as $componentname => $path) {
93245e95 214 $path = self::clean_path($path) . self::$behat_tests_path;
603c95dc
DM
215 if (empty($featurespaths[$path]) && file_exists($path)) {
216 $featurespaths[$path] = $path;
217 }
8cdc0ce8 218 }
df8dcf3e 219 $contents .= ' features:' . implode(PHP_EOL . ' - ', $featurespaths) . PHP_EOL;
8cdc0ce8
DM
220 }
221
d46340eb 222
93245e95 223 // Gets all the components with steps definitions.
d46340eb 224 $steps = self::get_components_steps_definitions();
6d9427bb 225 if ($steps) {
df8dcf3e 226 $stepsdefinitions = array('');
d46340eb
DM
227 foreach ($steps as $key => $filepath) {
228 if ($component == '' || $component === $key) {
229 $stepsdefinitions[$key] = $key . ': ' . $filepath;
df8dcf3e 230 }
93245e95 231 }
df8dcf3e 232 $contents .= ' steps_definitions:' . implode(PHP_EOL . ' ', $stepsdefinitions) . PHP_EOL;
93245e95 233 }
8cdc0ce8 234
93245e95 235 // Stores the file.
df8dcf3e
DM
236 if (!file_put_contents(self::get_behat_config_filepath(), $contents)) {
237 throw new file_exception('cannotcreatefile', self::get_behat_config_filepath());
8cdc0ce8 238 }
603c95dc 239
8cdc0ce8 240 }
7f541ea3 241
d46340eb
DM
242 /**
243 * Gets the list of Moodle steps definitions
244 *
245 * Class name as a key and the filepath as value
246 *
247 * Externalized from update_config_file() to use
248 * it from the steps definitions web interface
249 *
250 * @return array
251 */
252 public static function get_components_steps_definitions() {
253
254 $components = tests_finder::get_components_with_tests('stepsdefinitions');
255 if (!$components) {
256 return false;
257 }
258
259 $stepsdefinitions = array();
260 foreach ($components as $componentname => $componentpath) {
261 $componentpath = self::clean_path($componentpath);
262 $diriterator = new DirectoryIterator($componentpath . self::$behat_tests_path);
263 $regite = new RegexIterator($diriterator, '|behat_.*\.php$|');
264
265 // All behat_*.php inside self::$behat_tests_path are added as steps definitions files.
266 foreach ($regite as $file) {
267 $key = $file->getBasename('.php');
268 $stepsdefinitions[$key] = $file->getPathname();
269 }
270 }
271
272 return $stepsdefinitions;
273 }
274
275
93245e95
DM
276 /**
277 * Cleans the path returned by get_components_with_tests() to standarize it
278 *
279 * {@see tests_finder::get_all_directories_with_tests()} it returns the path including /tests/
280 * @param string $path
281 * @return string The string without the last /tests part
282 */
283 private static function clean_path($path) {
284
285 $path = rtrim($path, '/');
286
287 $parttoremove = '/tests';
288
289 $substr = substr($path, strlen($path) - strlen($parttoremove));
290 if ($substr == $parttoremove) {
291 $path = substr($path, 0, strlen($path) - strlen($parttoremove));
292 }
293
294 return rtrim($path, '/');
295 }
296
7f541ea3
DM
297 /**
298 * Checks whether the test database and dataroot is ready
299 * Stops execution if something went wrong
300 */
301 private static function test_environment_problem() {
302 global $CFG;
303
a09534f4
DM
304 // PHPUnit --diag returns nothing if the test environment is set up correctly.
305 exec('php ' . $CFG->dirroot . '/' . $CFG->admin . '/tool/phpunit/cli/util.php --diag', $output, $code);
7f541ea3
DM
306
307 // If something is not ready stop execution and display the CLI command output.
308 if ($code != 0) {
a09534f4 309 notice(get_string('phpunitenvproblem', 'tool_behat') . ': ' . implode(' ', $output));
7f541ea3
DM
310 }
311 }
312
313 /**
cca2c806
DM
314 * Checks if behat is set up and working
315 *
cb18fd71
DM
316 * It checks behat dependencies have been installed and runs
317 * the behat help command to ensure it works as expected
a2a98438 318 * @param boolean $checkphp Extra check for the PHP version
7f541ea3 319 */
a2a98438 320 private static function check_behat_setup($checkphp = false) {
7f541ea3
DM
321 global $CFG;
322
a2a98438
DM
323 if ($checkphp && version_compare(PHP_VERSION, '5.4.0', '<')) {
324 throw new Exception(get_string('wrongphpversion', 'tool_behat'));
325 }
326
7f541ea3 327 // Moodle setting.
fbb52434 328 if (!is_dir($vendor = __DIR__ . '/../../../vendor/behat')) {
cb18fd71
DM
329
330 $msg = get_string('wrongbehatsetup', 'tool_behat');
7f541ea3 331
d46340eb 332 // With HTML.
a09534f4 333 $docslink = tool_behat::$docsurl . '#Installation';
7f541ea3 334 if (!CLI_SCRIPT) {
d46340eb 335 $docslink = html_writer::tag('a', $docslink, array('href' => $docslink, 'target' => '_blank'));
7f541ea3 336 }
d46340eb 337 $msg .= '. ' . get_string('moreinfoin', 'tool_behat') . ' ' . $docslink;
7f541ea3
DM
338 notice($msg);
339 }
340
341 // Behat test command.
342 $currentcwd = getcwd();
cb18fd71 343 chdir($CFG->dirroot . '/lib/behat');
7f541ea3
DM
344 exec('bin/behat --help', $output, $code);
345 chdir($currentcwd);
346
347 if ($code != 0) {
348 notice(get_string('wrongbehatsetup', 'tool_behat'));
349 }
350 }
351
352 /**
cca2c806 353 * Enables test mode
7f541ea3 354 *
a09534f4
DM
355 * Stores a file in dataroot/behat to allow Moodle to switch
356 * to the test environment when using cli-server
7f541ea3
DM
357 *
358 * @throws file_exception
359 */
cca2c806 360 private static function start_test_mode() {
554820dd 361 global $CFG;
7f541ea3 362
cca2c806 363 if (self::is_test_mode_enabled()) {
7f541ea3
DM
364 debugging('Test environment was already enabled');
365 return;
366 }
367
8cdc0ce8 368 $behatdir = self::get_behat_dir();
7f541ea3 369
d46340eb
DM
370 $contents = '$CFG->test_wwwroot, $CFG->phpunit_prefix and $CFG->phpunit_dataroot' .
371 ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
7f541ea3 372 $filepath = $behatdir . '/test_environment_enabled.txt';
33005f68 373 if (!file_put_contents($filepath, $contents)) {
7f541ea3
DM
374 throw new file_exception('cannotcreatefile', $filepath);
375 }
554820dd 376 chmod($filepath, $CFG->directorypermissions);
7f541ea3
DM
377 }
378
cca2c806
DM
379 /**
380 * Runs the php built-in server
381 * @return array The process running the server and the pipes array
382 */
383 private static function start_test_server() {
384 global $CFG;
385
386 $descriptorspec = array(
387 array("pipe", "r"),
388 array("pipe", "w"),
389 array("pipe", "a")
390 );
391
392 $server = str_replace('http://', '', $CFG->test_wwwroot);
393 $process = proc_open('php -S ' . $server, $descriptorspec, $pipes, $CFG->dirroot);
394
d46340eb
DM
395 // TODO If it's already started close pipes.
396
cca2c806
DM
397 if (!is_resource($process)) {
398 print_error('testservercantrun');
399 }
400
401 return array($process, $pipes);
402 }
403
7f541ea3
DM
404 /**
405 * Disables test mode
a09534f4 406 * @throws file_exception
7f541ea3 407 */
cca2c806 408 private static function stop_test_mode() {
7f541ea3 409
33005f68 410 $testenvfile = self::get_test_filepath();
7f541ea3 411
cca2c806 412 if (!self::is_test_mode_enabled()) {
7f541ea3
DM
413 debugging('Test environment was already disabled');
414 } else {
cc544646
DM
415 if (!unlink($testenvfile)) {
416 throw new file_exception('cannotdeletetestenvironmentfile');
417 }
7f541ea3
DM
418 }
419 }
420
cca2c806
DM
421 /**
422 * Stops the built-in server
423 *
424 * @param resource $process
425 * @param array $pipes IN, OUT and error pipes
426 */
427 private static function stop_test_server($process, $pipes) {
428
429 if (is_resource($process)) {
430
431 // Closing pipes.
432 fclose($pipes[0]);
433 fclose($pipes[1]);
434 fclose($pipes[2]);
435
436 // Closing process.
437 proc_terminate($process);
438 proc_close($process);
439 }
440 }
441
7f541ea3
DM
442 /**
443 * Checks whether test environment is enabled or disabled
cc544646 444 *
a09534f4
DM
445 * To check is the current script is running in the test
446 * environment {@see tool_behat::is_test_environment_running()}
cc544646
DM
447 *
448 * @return bool
7f541ea3 449 */
a09534f4 450 public static function is_test_mode_enabled() {
7f541ea3 451
33005f68 452 $testenvfile = self::get_test_filepath();
7f541ea3
DM
453 if (file_exists($testenvfile)) {
454 return true;
455 }
456
457 return false;
458 }
459
cc544646
DM
460 /**
461 * Returns true if Moodle is currently running with the test database and dataroot
462 * @return bool
463 */
554820dd 464 private static function is_test_environment_running() {
cc544646
DM
465 global $CFG;
466
a09534f4 467 if (!empty($CFG->originaldataroot) || defined('BEHAT_RUNNING')) {
cc544646
DM
468 return true;
469 }
470
471 return false;
472 }
473
474 /**
475 * Returns the path to the file which specifies if test environment is enabled
476 * @return string
477 */
478 private static function get_test_filepath() {
479 global $CFG;
480
33005f68 481 if (self::is_test_environment_running()) {
cc544646
DM
482 $testenvfile = $CFG->originaldataroot . '/behat/test_environment_enabled.txt';
483 } else {
484 $testenvfile = $CFG->dataroot . '/behat/test_environment_enabled.txt';
485 }
486
487 return $testenvfile;
488 }
489
8cdc0ce8
DM
490
491 /**
492 * Ensures the behat dir exists in moodledata
493 * @throws file_exception
494 * @return string Full path
495 */
496 private static function get_behat_dir() {
497 global $CFG;
498
499 $behatdir = $CFG->dataroot . '/behat';
500
501 if (!is_dir($behatdir)) {
502 if (!mkdir($behatdir, $CFG->directorypermissions, true)) {
503 throw new file_exception('storedfilecannotcreatefiledirs');
504 }
505 }
506
507 if (!is_writable($behatdir)) {
508 throw new file_exception('storedfilecannotcreatefiledirs');
509 }
510
511 return $behatdir;
512 }
513
df8dcf3e
DM
514 /**
515 * Returns the behat config file path
516 * @return string
517 */
518 private static function get_behat_config_filepath() {
519 return self::get_behat_dir() . '/behat.yml';
520 }
521
7f541ea3 522}