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