Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 * 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')) { ... 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 34 require_once (__DIR__ . '/../testing/lib.php'); 35 36 define('BEHAT_EXITCODE_CONFIG', 250); 37 define('BEHAT_EXITCODE_REQUIREMENT', 251); 38 define('BEHAT_EXITCODE_PERMISSIONS', 252); 39 define('BEHAT_EXITCODE_REINSTALL', 253); 40 define('BEHAT_EXITCODE_INSTALL', 254); 41 define('BEHAT_EXITCODE_INSTALLED', 256); 42 43 /** 44 * The behat test site fullname and shortname. 45 */ 46 define('BEHAT_PARALLEL_SITE_NAME', "behatrun"); 47 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 */ 55 function 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: 69 $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php'); 70 $text = "Reinstall Behat: ".$text.", use:\n php ".$path; 71 break; 72 case BEHAT_EXITCODE_INSTALL: 73 $path = testing_cli_argument_path('/admin/tool/behat/cli/init.php'); 74 $text = "Install Behat before enabling it, use:\n php ".$path; 75 break; 76 case BEHAT_EXITCODE_INSTALLED: 77 $text = "The Behat site is already installed"; 78 break; 79 default: 80 $text = 'Unknown error ' . $errorcode . ' ' . $text; 81 break; 82 } 83 84 testing_error($errorcode, $text); 85 } 86 87 /** 88 * Return logical error string. 89 * 90 * @param int $errtype php error type. 91 * @return string string which will be returned. 92 */ 93 function behat_get_error_string($errtype) { 94 switch ($errtype) { 95 case E_USER_ERROR: 96 $errnostr = 'Fatal error'; 97 break; 98 case E_WARNING: 99 case E_USER_WARNING: 100 $errnostr = 'Warning'; 101 break; 102 case E_NOTICE: 103 case E_USER_NOTICE: 104 case E_STRICT: 105 $errnostr = 'Notice'; 106 break; 107 case E_RECOVERABLE_ERROR: 108 $errnostr = 'Catchable'; 109 break; 110 default: 111 $errnostr = 'Unknown error type'; 112 } 113 114 return $errnostr; 115 } 116 117 /** 118 * PHP errors handler to use when running behat tests. 119 * 120 * Adds specific CSS classes to identify 121 * the messages. 122 * 123 * @param int $errno 124 * @param string $errstr 125 * @param string $errfile 126 * @param int $errline 127 * @return bool 128 */ 129 function behat_error_handler($errno, $errstr, $errfile, $errline) { 130 131 // If is preceded by an @ we don't show it. 132 if (!error_reporting()) { 133 return true; 134 } 135 136 // This error handler receives E_ALL | E_STRICT, running the behat test site the debug level is 137 // set to DEVELOPER and will always include E_NOTICE,E_USER_NOTICE... as part of E_ALL, if the current 138 // error_reporting() value does not include one of those levels is because it has been forced through 139 // the moodle code (see fix_utf8() for example) in that cases we respect the forced error level value. 140 $respect = array(E_NOTICE, E_USER_NOTICE, E_STRICT, E_WARNING, E_USER_WARNING, E_DEPRECATED, E_USER_DEPRECATED); 141 foreach ($respect as $respectable) { 142 143 // If the current value does not include this kind of errors and the reported error is 144 // at that level don't print anything. 145 if ($errno == $respectable && !(error_reporting() & $respectable)) { 146 return true; 147 } 148 } 149 150 // Using the default one in case there is a fatal catchable error. 151 default_error_handler($errno, $errstr, $errfile, $errline); 152 153 $errnostr = behat_get_error_string($errno); 154 155 // If ajax script then throw exception, so the calling api catch it and show it on web page. 156 if (defined('AJAX_SCRIPT')) { 157 throw new Exception("$errnostr: $errstr in $errfile on line $errline"); 158 } else { 159 // Wrapping the output. 160 echo '<div class="phpdebugmessage" data-rel="phpdebugmessage">' . PHP_EOL; 161 echo "$errnostr: $errstr in $errfile on line $errline" . PHP_EOL; 162 echo '</div>'; 163 } 164 165 // Also use the internal error handler so we keep the usual behaviour. 166 return false; 167 } 168 169 /** 170 * Before shutdown save last error entries, so we can fail the test. 171 */ 172 function behat_shutdown_function() { 173 // If any error found, then save it. 174 if ($error = error_get_last()) { 175 // Ignore E_WARNING, as they might come via ( @ )suppression and might lead to false failure. 176 if (isset($error['type']) && !($error['type'] & E_WARNING)) { 177 178 $errors = behat_get_shutdown_process_errors(); 179 180 $errors[] = $error; 181 $errorstosave = json_encode($errors); 182 183 set_config('process_errors', $errorstosave, 'tool_behat'); 184 } 185 } 186 } 187 188 /** 189 * Return php errors save which were save during shutdown. 190 * 191 * @return array 192 */ 193 function behat_get_shutdown_process_errors() { 194 global $DB; 195 196 // Don't use get_config, as it use cache and return invalid value, between selenium and cli process. 197 $phperrors = $DB->get_field('config_plugins', 'value', array('name' => 'process_errors', 'plugin' => 'tool_behat')); 198 199 if (!empty($phperrors)) { 200 return json_decode($phperrors, true); 201 } else { 202 return array(); 203 } 204 } 205 206 /** 207 * Restrict the config.php settings allowed. 208 * 209 * When running the behat features the config.php 210 * settings should not affect the results. 211 * 212 * @return void 213 */ 214 function behat_clean_init_config() { 215 global $CFG; 216 217 $allowed = array_flip(array( 218 'wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions', 219 'umaskpermissions', 'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 220 'dboptions', 'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 221 'proxybypass', 'pathtogs', 'pathtophp', 'pathtodu', 'aspellpath', 'pathtodot', 'skiplangupgrade', 222 'altcacheconfigpath', 'pathtounoconv', 'alternative_file_system_class', 'pathtopython' 223 )); 224 225 // Add extra allowed settings. 226 if (!empty($CFG->behat_extraallowedsettings)) { 227 $allowed = array_merge($allowed, array_flip($CFG->behat_extraallowedsettings)); 228 } 229 230 // Also allowing behat_ prefixed attributes. 231 foreach ($CFG as $key => $value) { 232 if (!isset($allowed[$key]) && strpos($key, 'behat_') !== 0) { 233 unset($CFG->{$key}); 234 } 235 } 236 } 237 238 /** 239 * Checks that the behat config vars are properly set. 240 * 241 * @return void Stops execution with error code if something goes wrong. 242 */ 243 function behat_check_config_vars() { 244 global $CFG; 245 246 // Verify prefix value. 247 if (empty($CFG->behat_prefix)) { 248 behat_error(BEHAT_EXITCODE_CONFIG, 249 'Define $CFG->behat_prefix in config.php'); 250 } 251 if (!empty($CFG->prefix) and $CFG->behat_prefix == $CFG->prefix) { 252 behat_error(BEHAT_EXITCODE_CONFIG, 253 '$CFG->behat_prefix in config.php must be different from $CFG->prefix'); 254 } 255 if (!empty($CFG->phpunit_prefix) and $CFG->behat_prefix == $CFG->phpunit_prefix) { 256 behat_error(BEHAT_EXITCODE_CONFIG, 257 '$CFG->behat_prefix in config.php must be different from $CFG->phpunit_prefix'); 258 } 259 260 // Verify behat wwwroot value. 261 if (empty($CFG->behat_wwwroot)) { 262 behat_error(BEHAT_EXITCODE_CONFIG, 263 'Define $CFG->behat_wwwroot in config.php'); 264 } 265 if (!empty($CFG->wwwroot) and $CFG->behat_wwwroot == $CFG->wwwroot) { 266 behat_error(BEHAT_EXITCODE_CONFIG, 267 '$CFG->behat_wwwroot in config.php must be different from $CFG->wwwroot'); 268 } 269 270 // Verify behat dataroot value. 271 if (empty($CFG->behat_dataroot)) { 272 behat_error(BEHAT_EXITCODE_CONFIG, 273 'Define $CFG->behat_dataroot in config.php'); 274 } 275 clearstatcache(); 276 if (!file_exists($CFG->behat_dataroot_parent)) { 277 $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777; 278 umask(0); 279 if (!mkdir($CFG->behat_dataroot_parent, $permissions, true)) { 280 behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created'); 281 } 282 } 283 $CFG->behat_dataroot_parent = realpath($CFG->behat_dataroot_parent); 284 if (empty($CFG->behat_dataroot_parent) or !is_dir($CFG->behat_dataroot_parent) or !is_writable($CFG->behat_dataroot_parent)) { 285 behat_error(BEHAT_EXITCODE_CONFIG, 286 '$CFG->behat_dataroot in config.php must point to an existing writable directory'); 287 } 288 if (!empty($CFG->dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->dataroot)) { 289 behat_error(BEHAT_EXITCODE_CONFIG, 290 '$CFG->behat_dataroot in config.php must be different from $CFG->dataroot'); 291 } 292 if (!empty($CFG->phpunit_dataroot) and $CFG->behat_dataroot_parent == realpath($CFG->phpunit_dataroot)) { 293 behat_error(BEHAT_EXITCODE_CONFIG, 294 '$CFG->behat_dataroot in config.php must be different from $CFG->phpunit_dataroot'); 295 } 296 297 // This request is coming from admin/tool/behat/cli/util.php which will call util_single.php. So just return from 298 // here as we don't need to create a dataroot for single run. 299 if (defined('BEHAT_PARALLEL_UTIL') && BEHAT_PARALLEL_UTIL && empty($CFG->behatrunprocess)) { 300 return; 301 } 302 303 if (!file_exists($CFG->behat_dataroot)) { 304 $permissions = isset($CFG->directorypermissions) ? $CFG->directorypermissions : 02777; 305 umask(0); 306 if (!mkdir($CFG->behat_dataroot, $permissions, true)) { 307 behat_error(BEHAT_EXITCODE_PERMISSIONS, '$CFG->behat_dataroot directory can not be created'); 308 } 309 } 310 $CFG->behat_dataroot = realpath($CFG->behat_dataroot); 311 } 312 313 /** 314 * Should we switch to the test site data? 315 * @return bool 316 */ 317 function behat_is_test_site() { 318 global $CFG; 319 320 if (defined('BEHAT_UTIL')) { 321 // This is the admin tool that installs/drops the test site install. 322 return true; 323 } 324 if (defined('BEHAT_TEST')) { 325 // This is the main vendor/bin/behat script. 326 return true; 327 } 328 if (empty($CFG->behat_wwwroot)) { 329 return false; 330 } 331 if (isset($_SERVER['REMOTE_ADDR']) and behat_is_requested_url($CFG->behat_wwwroot)) { 332 // Something is accessing the web server like a real browser. 333 return true; 334 } 335 336 return false; 337 } 338 339 /** 340 * Fix variables for parallel behat testing. 341 * - behat_wwwroot = behat_wwwroot{behatrunprocess} 342 * - behat_dataroot = behat_dataroot{behatrunprocess} 343 * - behat_prefix = behat_prefix.{behatrunprocess}_ (For oracle it will be firstletter of prefix and behatrunprocess) 344 **/ 345 function behat_update_vars_for_process() { 346 global $CFG; 347 348 $allowedconfigoverride = array('dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'behat_prefix', 349 'behat_wwwroot', 'behat_dataroot'); 350 $behatrunprocess = behat_get_run_process(); 351 $CFG->behatrunprocess = $behatrunprocess; 352 353 // Data directory will be a directory under parent directory. 354 $CFG->behat_dataroot_parent = $CFG->behat_dataroot; 355 $CFG->behat_dataroot .= '/'. BEHAT_PARALLEL_SITE_NAME; 356 357 if ($behatrunprocess) { 358 if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_wwwroot'])) { 359 // Set www root for run process. 360 if (isset($CFG->behat_wwwroot) && 361 !preg_match("#/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess . "\$#", $CFG->behat_wwwroot)) { 362 $CFG->behat_wwwroot .= "/" . BEHAT_PARALLEL_SITE_NAME . $behatrunprocess; 363 } 364 } 365 366 if (empty($CFG->behat_parallel_run[$behatrunprocess - 1]['behat_dataroot'])) { 367 // Set behat_dataroot. 368 if (!preg_match("#" . $behatrunprocess . "\$#", $CFG->behat_dataroot)) { 369 $CFG->behat_dataroot .= $behatrunprocess; 370 } 371 } 372 373 // Set behat_prefix for db, just suffix run process number, to avoid max length exceed. 374 // For oracle only 2 letter prefix is possible. 375 // NOTE: This will not work for parallel process > 9. 376 if ($CFG->dbtype === 'oci') { 377 $CFG->behat_prefix = substr($CFG->behat_prefix, 0, 1); 378 $CFG->behat_prefix .= "{$behatrunprocess}"; 379 } else { 380 $CFG->behat_prefix .= "{$behatrunprocess}_"; 381 } 382 383 if (!empty($CFG->behat_parallel_run[$behatrunprocess - 1])) { 384 // Override allowed config vars. 385 foreach ($allowedconfigoverride as $config) { 386 if (isset($CFG->behat_parallel_run[$behatrunprocess - 1][$config])) { 387 $CFG->$config = $CFG->behat_parallel_run[$behatrunprocess - 1][$config]; 388 } 389 } 390 } 391 } 392 } 393 394 /** 395 * Checks if the URL requested by the user matches the provided argument 396 * 397 * @param string $url 398 * @return bool Returns true if it matches. 399 */ 400 function behat_is_requested_url($url) { 401 402 $parsedurl = parse_url($url . '/'); 403 $parsedurl['port'] = isset($parsedurl['port']) ? $parsedurl['port'] : 80; 404 $parsedurl['path'] = rtrim($parsedurl['path'], '/'); 405 406 // Removing the port. 407 $pos = strpos($_SERVER['HTTP_HOST'], ':'); 408 if ($pos !== false) { 409 $requestedhost = substr($_SERVER['HTTP_HOST'], 0, $pos); 410 } else { 411 $requestedhost = $_SERVER['HTTP_HOST']; 412 } 413 414 // The path should also match. 415 if (empty($parsedurl['path'])) { 416 $matchespath = true; 417 } else if (strpos($_SERVER['SCRIPT_NAME'], $parsedurl['path']) === 0) { 418 $matchespath = true; 419 } 420 421 // The host and the port should match 422 if ($parsedurl['host'] == $requestedhost && $parsedurl['port'] == $_SERVER['SERVER_PORT'] && !empty($matchespath)) { 423 return true; 424 } 425 426 return false; 427 } 428 429 /** 430 * Get behat run process from either $_SERVER or command config. 431 * 432 * @return bool|int false if single run, else run process number. 433 */ 434 function behat_get_run_process() { 435 global $argv, $CFG; 436 $behatrunprocess = false; 437 438 // Get behat run process, if set. 439 if (defined('BEHAT_CURRENT_RUN') && BEHAT_CURRENT_RUN) { 440 $behatrunprocess = BEHAT_CURRENT_RUN; 441 } else if (!empty($_SERVER['REMOTE_ADDR'])) { 442 // Try get it from config if present. 443 if (!empty($CFG->behat_parallel_run)) { 444 foreach ($CFG->behat_parallel_run as $run => $behatconfig) { 445 if (isset($behatconfig['behat_wwwroot']) && behat_is_requested_url($behatconfig['behat_wwwroot'])) { 446 $behatrunprocess = $run + 1; // We start process from 1. 447 break; 448 } 449 } 450 } 451 // Check if parallel site prefix is used. 452 if (empty($behatrunprocess) && preg_match('#/' . BEHAT_PARALLEL_SITE_NAME . '(.+?)/#', $_SERVER['REQUEST_URI'])) { 453 $dirrootrealpath = str_replace("\\", "/", realpath($CFG->dirroot)); 454 $serverrealpath = str_replace("\\", "/", realpath($_SERVER['SCRIPT_FILENAME'])); 455 $afterpath = str_replace($dirrootrealpath.'/', '', $serverrealpath); 456 if (!$behatrunprocess = preg_filter("#.*/" . BEHAT_PARALLEL_SITE_NAME . "(.+?)/$afterpath#", '$1', 457 $_SERVER['SCRIPT_FILENAME'])) { 458 throw new Exception("Unable to determine behat process [afterpath=" . $afterpath . 459 ", scriptfilename=" . $_SERVER['SCRIPT_FILENAME'] . "]!"); 460 } 461 } 462 } else if (defined('BEHAT_TEST') || defined('BEHAT_UTIL')) { 463 $behatconfig = ''; 464 465 if ($match = preg_filter('#--run=(.+)#', '$1', $argv)) { 466 // Try to guess the run from the existence of the --run arg. 467 $behatrunprocess = reset($match); 468 469 } else { 470 // Try to guess the run from the existence of the --config arg. Note there are 2 alternatives below. 471 if ($k = array_search('--config', $argv)) { 472 // Alternative 1: --config /path/to/config.yml => (next arg, pick it). 473 $behatconfig = str_replace("\\", "/", $argv[$k + 1]); 474 475 } else if ($config = preg_filter('#^(?:--config[ =]*)(.+)$#', '$1', $argv)) { 476 // Alternative 2: --config=/path/to/config.yml => (same arg, just get the path part). 477 $behatconfig = str_replace("\\", "/", reset($config)); 478 } 479 480 // Try get it from config if present. 481 if ($behatconfig) { 482 if (!empty($CFG->behat_parallel_run)) { 483 foreach ($CFG->behat_parallel_run as $run => $parallelconfig) { 484 if (!empty($parallelconfig['behat_dataroot']) && 485 $parallelconfig['behat_dataroot'] . '/behat/behat.yml' == $behatconfig) { 486 $behatrunprocess = $run + 1; // We start process from 1. 487 break; 488 } 489 } 490 } 491 // Check if default behat dataroot increment was done. 492 if (empty($behatrunprocess)) { 493 $behatdataroot = str_replace("\\", "/", $CFG->behat_dataroot . '/' . BEHAT_PARALLEL_SITE_NAME); 494 $behatrunprocess = preg_filter("#^{$behatdataroot}" . "(.+?)[/|\\\]behat[/|\\\]behat\.yml#", '$1', 495 $behatconfig); 496 } 497 } 498 } 499 } 500 501 return $behatrunprocess; 502 } 503 504 /** 505 * Execute commands in parallel. 506 * 507 * @param array $cmds list of commands to be executed. 508 * @param string $cwd absolute path of working directory. 509 * @param int $delay time in seconds to add delay between each parallel process. 510 * @return array list of processes. 511 */ 512 function cli_execute_parallel($cmds, $cwd = null, $delay = 0) { 513 require_once(__DIR__ . "/../../vendor/autoload.php"); 514 515 $processes = array(); 516 517 // Create child process. 518 foreach ($cmds as $name => $cmd) { 519 if (method_exists('\\Symfony\\Component\\Process\\Process', 'fromShellCommandline')) { 520 // Process 4.2 and up. 521 $process = Symfony\Component\Process\Process::fromShellCommandline($cmd); 522 } else { 523 // Process 4.1 and older. 524 $process = new Symfony\Component\Process\Process(null); 525 $process->setCommandLine($cmd); 526 } 527 528 $process->setWorkingDirectory($cwd); 529 $process->setTimeout(null); 530 $processes[$name] = $process; 531 $processes[$name]->start(); 532 533 // If error creating process then exit. 534 if ($processes[$name]->getStatus() !== 'started') { 535 echo "Error starting process: $name"; 536 foreach ($processes[$name] as $process) { 537 if ($process) { 538 $process->signal(SIGKILL); 539 } 540 } 541 exit(1); 542 } 543 544 // Sleep for specified delay. 545 if ($delay) { 546 sleep($delay); 547 } 548 } 549 return $processes; 550 } 551 552 /** 553 * Get command flags for an option/value combination 554 * 555 * @param string $option 556 * @param string|bool|null $value 557 * @return string 558 */ 559 function behat_get_command_flags(string $option, $value): string { 560 $commandoptions = ''; 561 if (is_bool($value)) { 562 if ($value) { 563 return " --{$option}"; 564 } else { 565 return " --no-{$option}"; 566 } 567 } else if ($value !== null) { 568 return " --$option=\"$value\""; 569 } 570 return ''; 571 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body