MDL-51881 behat: Set navbar to be absolutely positioned
[moodle.git] / lib / behat / lib.php
CommitLineData
096858ed
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 basic functions
19 *
c0d8831a
EL
20 * It does not include MOODLE_INTERNAL because is part of the bootstrap.
21 *
22 * This script should not be usually included, neither any of its functions
23 * used, within mooodle code at all. It's for exclusive use of behat and
24 * moodle setup.php. For places requiring a different/special behavior
25 * needing to check if are being run as part of behat tests, use:
26 * if (defined('BEHAT_SITE_RUNNING')) { ...
096858ed
DM
27 *
28 * @package core
29 * @category test
30 * @copyright 2012 David Monllaó
31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
32 */
33
34require_once(__DIR__ . '/../testing/lib.php');
35
36define('BEHAT_EXITCODE_CONFIG', 250);
37define('BEHAT_EXITCODE_REQUIREMENT', 251);
38define('BEHAT_EXITCODE_PERMISSIONS', 252);
39define('BEHAT_EXITCODE_REINSTALL', 253);
40define('BEHAT_EXITCODE_INSTALL', 254);
0e825e5d 41define('BEHAT_EXITCODE_INSTALLED', 256);
096858ed 42
3c71c15c
RT
43/**
44 * The behat test site fullname and shortname.
45 */
027212b0 46define('BEHAT_PARALLEL_SITE_NAME', "behatrun");
3c71c15c 47
096858ed
DM
48/**
49 * Exits with an error code
50 *
51 * @param mixed $errorcode
52 * @param string $text
53 * @return void Stops execution with error code
54 */
55function behat_error($errorcode, $text = '') {
56
57 // Adding error prefixes.
58 switch ($errorcode) {
59 case BEHAT_EXITCODE_CONFIG:
60 $text = 'Behat config error: ' . $text;
61 break;
62 case BEHAT_EXITCODE_REQUIREMENT:
63 $text = 'Behat requirement not satisfied: ' . $text;
64 break;
65 case BEHAT_EXITCODE_PERMISSIONS:
66 $text = 'Behat permissions problem: ' . $text . ', check the permissions';
67 break;
68 case BEHAT_EXITCODE_REINSTALL:
b32ca4ca
DM
69 $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php');
70 $text = "Reinstall Behat: ".$text.", use:\n php ".$path;
096858ed
DM
71 break;
72 case BEHAT_EXITCODE_INSTALL:
b32ca4ca
DM
73 $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php');
74 $text = "Install Behat before enabling it, use:\n php ".$path;
096858ed 75 break;
0e825e5d
DM
76 case BEHAT_EXITCODE_INSTALLED:
77 $text = "The Behat site is already installed";
78 break;
096858ed
DM
79 default:
80 $text = 'Unknown error ' . $errorcode . ' ' . $text;
81 break;
82 }
83
84 testing_error($errorcode, $text);
85}
86
90ed22ab
DM
87/**
88 * PHP errors handler to use when running behat tests.
89 *
90 * Adds specific CSS classes to identify
91 * the messages.
92 *
93 * @param int $errno
94 * @param string $errstr
95 * @param string $errfile
96 * @param int $errline
97 * @param array $errcontext
98 * @return bool
99 */
100function behat_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
90ed22ab
DM
101
102 // If is preceded by an @ we don't show it.
103 if (!error_reporting()) {
104 return true;
105 }
106
1d9ec4cb
DM
107 // This error handler receives E_ALL | E_STRICT, running the behat test site the debug level is
108 // set to DEVELOPER and will always include E_NOTICE,E_USER_NOTICE... as part of E_ALL, if the current
109 // error_reporting() value does not include one of those levels is because it has been forced through
110 // the moodle code (see fix_utf8() for example) in that cases we respect the forced error level value.
111 $respect = array(E_NOTICE, E_USER_NOTICE, E_STRICT, E_WARNING, E_USER_WARNING);
112 foreach ($respect as $respectable) {
113
114 // If the current value does not include this kind of errors and the reported error is
115 // at that level don't print anything.
116 if ($errno == $respectable && !(error_reporting() & $respectable)) {
117 return true;
118 }
119 }
120
90ed22ab
DM
121 // Using the default one in case there is a fatal catchable error.
122 default_error_handler($errno, $errstr, $errfile, $errline, $errcontext);
123
124 switch ($errno) {
125 case E_USER_ERROR:
126 $errnostr = 'Fatal error';
127 break;
128 case E_WARNING:
129 case E_USER_WARNING:
130 $errnostr = 'Warning';
131 break;
132 case E_NOTICE:
133 case E_USER_NOTICE:
134 case E_STRICT:
135 $errnostr = 'Notice';
136 break;
137 case E_RECOVERABLE_ERROR:
138 $errnostr = 'Catchable';
139 break;
140 default:
141 $errnostr = 'Unknown error type';
142 }
143
144 // Wrapping the output.
3e76c7fa 145 echo '<div class="phpdebugmessage" data-rel="phpdebugmessage">' . PHP_EOL;
90ed22ab
DM
146 echo "$errnostr: $errstr in $errfile on line $errline" . PHP_EOL;
147 echo '</div>';
148
149 // Also use the internal error handler so we keep the usual behaviour.
150 return false;
151}
f38e22ed
DM
152
153/**
154 * Restrict the config.php settings allowed.
155 *
156 * When running the behat features the config.php
157 * settings should not affect the results.
158 *
159 * @return void
160 */
161function behat_clean_init_config() {
162 global $CFG;
163
164 $allowed = array_flip(array(
165 'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
f46e869d
DM
166 'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix',
167 'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword',
a52c5e63
SH
168 'proxybypass', 'theme', 'pathtogs', 'pathtoclam', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade',
169 'altcacheconfigpath'
f38e22ed
DM
170 ));
171
401f60d5
DM
172 // Add extra allowed settings.
173 if (!empty($CFG->behat_extraallowedsettings)) {
174 $allowed = array_merge($allowed, array_flip($CFG->behat_extraallowedsettings));
175 }
176
f38e22ed
DM
177 // Also allowing behat_ prefixed attributes.
178 foreach ($CFG as $key => $value) {
179 if (!isset($allowed[$key]) && strpos($key, 'behat_') !== 0) {
180 unset($CFG->{$key});
181 }
182 }
f38e22ed 183}
7f7a91e4
DM
184
185/**
186 * Checks that the behat config vars are properly set.
187 *
188 * @return void Stops execution with error code if something goes wrong.
189 */
190function behat_check_config_vars() {
191 global $CFG;
192
60129d5d
PS
193 // Verify prefix value.
194 if (empty($CFG->behat_prefix)) {
7f7a91e4 195 behat_error(BEHAT_EXITCODE_CONFIG,
60129d5d 196 'Define $CFG->behat_prefix in config.php');
7f7a91e4 197 }
60129d5d
PS
198 if (!empty($CFG->prefix) and $CFG->behat_prefix == $CFG->prefix) {
199 behat_error(BEHAT_EXITCODE_CONFIG,
200 '$CFG->behat_prefix in config.php must be different from $CFG->prefix');
201 }
202 if (!empty($CFG->phpunit_prefix) and $CFG->behat_prefix == $CFG->phpunit_prefix) {
7f7a91e4 203 behat_error(BEHAT_EXITCODE_CONFIG,
60129d5d 204 '$CFG->behat_prefix in config.php must be different from $CFG->phpunit_prefix');
7f7a91e4
DM
205 }
206
60129d5d
PS
207 // Verify behat wwwroot value.
208 if (empty($CFG->behat_wwwroot)) {
209 behat_error(BEHAT_EXITCODE_CONFIG,
210 'Define $CFG->behat_wwwroot in config.php');
7f7a91e4 211 }
60129d5d 212 if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) {
7f7a91e4 213 behat_error(BEHAT_EXITCODE_CONFIG,
60129d5d 214 '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot');
7f7a91e4
DM
215 }
216
60129d5d
PS
217 // Verify behat dataroot value.
218 if (empty($CFG->behat_dataroot)) {
219 behat_error(BEHAT_EXITCODE_CONFIG,
220 'Define $CFG->behat_dataroot in config.php');
221 }
3c71c15c 222 clearstatcache();
60129d5d
PS
223 if (!file_exists($CFG->behat_dataroot)) {
224 $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777;
9bb80d20 225 umask(0);
60129d5d
PS
226 if (!mkdir($CFG->behat_dataroot, $permissions, true)) {
227 behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created');
228 }
229 }
230 $CFG->behat_dataroot = realpath($CFG->behat_dataroot);
231 if (empty($CFG->behat_dataroot) or !is_dir($CFG->behat_dataroot) or !is_writable($CFG->behat_dataroot)) {
232 behat_error(BEHAT_EXITCODE_CONFIG,
233 '$CFG->behat_dataroot in config.php must point to an existing writable directory');
234 }
235 if (!empty($CFG->dataroot) and $CFG->behat_dataroot == realpath($CFG->dataroot)) {
236 behat_error(BEHAT_EXITCODE_CONFIG,
237 '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot');
238 }
239 if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot == realpath($CFG->phpunit_dataroot)) {
240 behat_error(BEHAT_EXITCODE_CONFIG,
241 '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot');
242 }
7f7a91e4
DM
243}
244
245/**
60129d5d
PS
246 * Should we switch to the test site data?
247 * @return bool
7f7a91e4 248 */
60129d5d 249function behat_is_test_site() {
7f7a91e4
DM
250 global $CFG;
251
60129d5d
PS
252 if (defined('BEHAT_UTIL')) {
253 // This is the admin tool that installs/drops the test site install.
254 return true;
255 }
256 if (defined('BEHAT_TEST')) {
257 // This is the main vendor/bin/behat script.
258 return true;
259 }
260 if (empty($CFG->behat_wwwroot)) {
261 return false;
262 }
263 if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) {
264 // Something is accessing the web server like a real browser.
265 return true;
7f7a91e4
DM
266 }
267
60129d5d 268 return false;
7f7a91e4
DM
269}
270
08e7f97e 271/**
3c71c15c
RT
272 * Fix variables for parallel behat testing.
273 * - behat_wwwroot = behat_wwwroot{behatrunprocess}
274 * - behat_dataroot = behat_dataroot{behatrunprocess}
275 * - behat_prefix = behat_prefix.{behatrunprocess}_ (For oracle it will be firstletter of prefix and behatrunprocess)
08e7f97e 276 **/
a2754d79 277function behat_update_vars_for_process() {
08e7f97e
TL
278 global $CFG;
279
5b9e6b55
RT
280 $allowedconfigoverride = array('dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'behat_prefix',
281 'behat_wwwroot', 'behat_dataroot');
a2754d79
RT
282 $behatrunprocess = behat_get_run_process();
283 $CFG->behatrunprocess = $behatrunprocess;
08e7f97e 284
3c71c15c 285 if ($behatrunprocess) {
5b9e6b55
RT
286 if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_wwwroot'])) {
287 // Set www root for run process.
288 if (isset($CFG->behat_wwwroot) &&
289 !preg_match("#/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess . "\$#", $CFG->behat_wwwroot)) {
290 $CFG->behat_wwwroot .= "/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess;
291 }
08e7f97e 292 }
08e7f97e 293
5b9e6b55
RT
294 if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_dataroot'])) {
295 // Set behat_dataroot.
296 if (!preg_match("#" . $behatrunprocess . "\$#", $CFG->behat_dataroot)) {
297 $CFG->behat_dataroot .= $behatrunprocess;
298 }
3c71c15c 299 }
08e7f97e 300
3c71c15c
RT
301 // Set behat_prefix for db, just suffix run process number, to avoid max length exceed.
302 // For oracle only 2 letter prefix is possible.
303 // NOTE: This will not work for parallel process > 9.
304 if ($CFG->dbtype === 'oci') {
305 $CFG->behat_prefix = substr($CFG->behat_prefix, 0, 1);
306 $CFG->behat_prefix .= "{$behatrunprocess}";
307 } else {
308 $CFG->behat_prefix .= "{$behatrunprocess}_";
08e7f97e 309 }
3c71c15c
RT
310
311 if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) {
312 // Override allowed config vars.
313 foreach ($allowedconfigoverride as $config) {
314 if (isset($CFG->behat_parallel_run[$behatrunprocess - 1][$config])) {
315 $CFG->$config = $CFG->behat_parallel_run[$behatrunprocess - 1][$config];
316 }
317 }
08e7f97e 318 }
08e7f97e
TL
319 }
320}
321
7f7a91e4
DM
322/**
323 * Checks if the URL requested by the user matches the provided argument
324 *
325 * @param string $url
326 * @return bool Returns true if it matches.
327 */
328function behat_is_requested_url($url) {
329
330 $parsedurl = parse_url($url . '/');
331 $parsedurl['port'] = isset($parsedurl['port']) ? $parsedurl['port'] : 80;
332 $parsedurl['path'] = rtrim($parsedurl['path'], '/');
333
334 // Removing the port.
335 $pos = strpos($_SERVER['HTTP_HOST'], ':');
336 if ($pos !== false) {
337 $requestedhost = substr($_SERVER['HTTP_HOST'], 0, $pos);
338 } else {
339 $requestedhost = $_SERVER['HTTP_HOST'];
340 }
341
342 // The path should also match.
343 if (empty($parsedurl['path'])) {
344 $matchespath = true;
345 } else if (strpos($_SERVER['SCRIPT_NAME'], $parsedurl['path']) === 0) {
346 $matchespath = true;
347 }
348
349 // The host and the port should match
350 if ($parsedurl['host'] == $requestedhost && $parsedurl['port'] == $_SERVER['SERVER_PORT'] && !empty($matchespath)) {
351 return true;
352 }
353
354 return false;
355}
027212b0 356
a2754d79
RT
357/**
358 * Get behat run process from either $_SERVER or command config.
359 *
360 * @return bool|int false if single run, else run process number.
361 */
362function behat_get_run_process() {
363 global $argv, $CFG;
364 $behatrunprocess = false;
365
366 // Get behat run process, if set.
367 if (defined('BEHAT_CURRENT_RUN') && BEHAT_CURRENT_RUN) {
368 $behatrunprocess = BEHAT_CURRENT_RUN;
369 } else if (!empty($_SERVER['REMOTE_ADDR'])) {
5b9e6b55
RT
370 // Try get it from config if present.
371 if (!empty($CFG->behat_parallel_run)) {
372 foreach ($CFG->behat_parallel_run as $run => $behatconfig) {
373 if (isset($behatconfig['behat_wwwroot']) && behat_is_requested_url($behatconfig['behat_wwwroot'])) {
374 $behatrunprocess = $run + 1; // We start process from 1.
375 break;
376 }
377 }
378 }
379 // Check if parallel site prefix is used.
380 if (empty($behatrunprocess) && preg_match('#/' . BEHAT_PARALLEL_SITE_NAME . '(.+?)/#', $_SERVER['REQUEST_URI'])) {
a2754d79
RT
381 $dirrootrealpath = str_replace("\\", "/", realpath($CFG->dirroot));
382 $serverrealpath = str_replace("\\", "/", realpath($_SERVER['SCRIPT_FILENAME']));
383 $afterpath = str_replace($dirrootrealpath.'/', '', $serverrealpath);
384 if (!$behatrunprocess = preg_filter("#.*/" . BEHAT_PARALLEL_SITE_NAME . "(.+?)/$afterpath#", '$1',
385 $_SERVER['SCRIPT_FILENAME'])) {
386 throw new Exception("Unable to determine behat process [afterpath=" . $afterpath .
387 ", scriptfilename=" . $_SERVER['SCRIPT_FILENAME'] . "]!");
388 }
389 }
390 } else if (defined('BEHAT_TEST') || defined('BEHAT_UTIL')) {
391 if ($match = preg_filter('#--run=(.+)#', '$1', $argv)) {
392 $behatrunprocess = reset($match);
5b9e6b55 393 } else if ($k = array_search('--config', $argv)) {
a2754d79 394 $behatconfig = str_replace("\\", "/", $argv[$k + 1]);
5b9e6b55
RT
395 // Try get it from config if present.
396 if (!empty($CFG->behat_parallel_run)) {
397 foreach ($CFG->behat_parallel_run as $run => $parallelconfig) {
398 if (!empty($parallelconfig['behat_dataroot']) &&
399 $parallelconfig['behat_dataroot'] . '/behat/behat.yml' == $behatconfig) {
400
401 $behatrunprocess = $run + 1; // We start process from 1.
402 break;
403 }
404 }
405 }
406 // Check if default behat datroot increment was done.
407 if (empty($behatrunprocess)) {
408 $behatdataroot = str_replace("\\", "/", $CFG->behat_dataroot);
409 $behatrunprocess = preg_filter("#^{$behatdataroot}" . "(.+?)[/|\\\]behat[/|\\\]behat\.yml#", '$1',
410 $behatconfig);
411 }
a2754d79
RT
412 }
413 }
414
415 return $behatrunprocess;
416}
417
027212b0
RT
418/**
419 * Execute commands in parallel.
420 *
421 * @param array $cmds list of commands to be executed.
422 * @param string $cwd absolute path of working directory.
423 * @return array list of processes.
424 */
425function cli_execute_parallel($cmds, $cwd = null) {
426 require_once(__DIR__ . "/../../vendor/autoload.php");
427
428 $processes = array();
429
430 // Create child process.
431 foreach ($cmds as $name => $cmd) {
432 $process = new Symfony\Component\Process\Process($cmd);
433
434 $process->setWorkingDirectory($cwd);
435 $process->setTimeout(null);
436 $processes[$name] = $process;
437 $processes[$name]->start();
438
439 // If error creating process then exit.
440 if ($processes[$name]->getStatus() !== 'started') {
441 echo "Error starting process: $name";
442 foreach ($processes[$name] as $process) {
443 if ($process) {
444 $process->signal(SIGKILL);
445 }
446 }
447 exit(1);
448 }
449 }
450 return $processes;
0897e190 451}