See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401]
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 * Utils for behat-related stuff 19 * 20 * @package core 21 * @category test 22 * @copyright 2012 David MonllaĆ³ 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 defined('MOODLE_INTERNAL') || die(); 27 28 require_once (__DIR__ . '/../lib.php'); 29 require_once (__DIR__ . '/../../testing/classes/util.php'); 30 require_once (__DIR__ . '/behat_command.php'); 31 require_once (__DIR__ . '/behat_config_manager.php'); 32 33 require_once (__DIR__ . '/../../filelib.php'); 34 require_once (__DIR__ . '/../../clilib.php'); 35 require_once (__DIR__ . '/../../csslib.php'); 36 37 use Behat\Mink\Session; 38 use Behat\Mink\Exception\ExpectationException; 39 40 /** 41 * Init/reset utilities for Behat database and dataroot 42 * 43 * @package core 44 * @category test 45 * @copyright 2013 David MonllaĆ³ 46 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 47 */ 48 class behat_util extends testing_util { 49 50 /** 51 * The behat test site fullname and shortname. 52 */ 53 const BEHATSITENAME = "Acceptance test site"; 54 55 /** 56 * @var array Files to skip when resetting dataroot folder 57 */ 58 protected static $datarootskiponreset = array('.', '..', 'behat', 'behattestdir.txt'); 59 60 /** 61 * @var array Files to skip when dropping dataroot folder 62 */ 63 protected static $datarootskipondrop = array('.', '..', 'lock'); 64 65 /** 66 * Installs a site using $CFG->dataroot and $CFG->prefix 67 * @throws coding_exception 68 * @return void 69 */ 70 public static function install_site() { 71 global $DB, $CFG; 72 require_once($CFG->dirroot.'/user/lib.php'); 73 if (!defined('BEHAT_UTIL')) { 74 throw new coding_exception('This method can be only used by Behat CLI tool'); 75 } 76 77 $tables = $DB->get_tables(false); 78 if (!empty($tables)) { 79 behat_error(BEHAT_EXITCODE_INSTALLED); 80 } 81 82 // New dataroot. 83 self::reset_dataroot(); 84 85 $options = array(); 86 $options['adminuser'] = 'admin'; 87 $options['adminpass'] = 'admin'; 88 $options['fullname'] = self::BEHATSITENAME; 89 $options['shortname'] = self::BEHATSITENAME; 90 91 install_cli_database($options, false); 92 93 // We need to keep the installed dataroot filedir files. 94 // So each time we reset the dataroot before running a test, the default files are still installed. 95 self::save_original_data_files(); 96 97 $frontpagesummary = new admin_setting_special_frontpagedesc(); 98 $frontpagesummary->write_setting(self::BEHATSITENAME); 99 100 // Update admin user info. 101 $user = $DB->get_record('user', array('username' => 'admin')); 102 $user->email = 'moodle@example.com'; 103 $user->firstname = 'Admin'; 104 $user->lastname = 'User'; 105 $user->city = 'Perth'; 106 $user->country = 'AU'; 107 user_update_user($user, false); 108 109 // Disable email message processor. 110 $DB->set_field('message_processors', 'enabled', '0', array('name' => 'email')); 111 112 // Sets maximum debug level. 113 set_config('debug', DEBUG_DEVELOPER); 114 set_config('debugdisplay', 1); 115 116 // Disable some settings that are not wanted on test sites. 117 set_config('noemailever', 1); 118 119 // Enable web cron. 120 set_config('cronclionly', 0); 121 122 // Set editor autosave to high value, so as to avoid unwanted ajax. 123 set_config('autosavefrequency', '604800', 'editor_atto'); 124 125 // Set noreplyaddress to an example domain, as it should be valid email address and test site can be a localhost. 126 set_config('noreplyaddress', 'noreply@example.com'); 127 128 // Set the support email address. 129 set_config('supportemail', 'email@example.com'); 130 131 // Remove any default blocked hosts and port restrictions, to avoid blocking tests (eg those using local files). 132 set_config('curlsecurityblockedhosts', ''); 133 set_config('curlsecurityallowedport', ''); 134 135 // Execute all the adhoc tasks. 136 while ($task = \core\task\manager::get_next_adhoc_task(time())) { 137 $task->execute(); 138 \core\task\manager::adhoc_task_complete($task); 139 } 140 141 // Keeps the current version of database and dataroot. 142 self::store_versions_hash(); 143 144 // Stores the database contents for fast reset. 145 self::store_database_state(); 146 } 147 148 /** 149 * Build theme CSS. 150 */ 151 public static function build_themes($mtraceprogress = false) { 152 global $CFG; 153 require_once("{$CFG->libdir}/outputlib.php"); 154 155 $themenames = array_keys(\core_component::get_plugin_list('theme')); 156 157 // Load the theme configs. 158 $themeconfigs = array_map(function($themename) { 159 return \theme_config::load($themename); 160 }, $themenames); 161 162 // Build the list of themes and cache them in local cache. 163 $themes = theme_build_css_for_themes($themeconfigs, ['ltr'], true, $mtraceprogress); 164 165 $framework = self::get_framework(); 166 $storageroot = self::get_dataroot() . "/{$framework}/themedata"; 167 168 foreach ($themes as $themename => $themedata) { 169 $dirname = "{$storageroot}/{$themename}"; 170 check_dir_exists($dirname); 171 foreach ($themedata as $direction => $css) { 172 file_put_contents("{$dirname}/{$direction}.css", $css); 173 } 174 } 175 } 176 177 /** 178 * Drops dataroot and remove test database tables 179 * @throws coding_exception 180 * @return void 181 */ 182 public static function drop_site() { 183 184 if (!defined('BEHAT_UTIL')) { 185 throw new coding_exception('This method can be only used by Behat CLI tool'); 186 } 187 188 self::reset_dataroot(); 189 self::drop_database(true); 190 self::drop_dataroot(); 191 } 192 193 /** 194 * Delete files and directories under dataroot. 195 */ 196 public static function drop_dataroot() { 197 global $CFG; 198 199 // As behat directory is now created under default $CFG->behat_dataroot_parent, so remove the whole dir. 200 if ($CFG->behat_dataroot !== $CFG->behat_dataroot_parent) { 201 remove_dir($CFG->behat_dataroot, false); 202 } else { 203 // It should never come here. 204 throw new moodle_exception("Behat dataroot should not be same as parent behat data root."); 205 } 206 } 207 208 /** 209 * Checks if $CFG->behat_wwwroot is available and using same versions for cli and web. 210 * 211 * @return void 212 */ 213 public static function check_server_status() { 214 global $CFG; 215 216 $url = $CFG->behat_wwwroot . '/admin/tool/behat/tests/behat/fixtures/environment.php'; 217 218 // Get web versions used by behat site. 219 $ch = curl_init($url); 220 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 221 $result = curl_exec($ch); 222 $statuscode = curl_getinfo($ch, CURLINFO_HTTP_CODE); 223 curl_close($ch); 224 225 if ($statuscode !== 200 || empty($result) || (!$result = json_decode($result, true))) { 226 227 behat_error (BEHAT_EXITCODE_REQUIREMENT, $CFG->behat_wwwroot . ' is not available, ensure you specified ' . 228 'correct url and that the server is set up and started.' . PHP_EOL . ' More info in ' . 229 behat_command::DOCS_URL . PHP_EOL); 230 } 231 232 // Check if cli version is same as web version. 233 $clienv = self::get_environment(); 234 if ($result != $clienv) { 235 $output = 'Differences detected between cli and webserver...'.PHP_EOL; 236 foreach ($result as $key => $version) { 237 if ($clienv[$key] != $version) { 238 $output .= ' ' . $key . ': ' . PHP_EOL; 239 $output .= ' - web server: ' . $version . PHP_EOL; 240 $output .= ' - cli: ' . $clienv[$key] . PHP_EOL; 241 } 242 } 243 echo $output; 244 ob_flush(); 245 } 246 } 247 248 /** 249 * Checks whether the test database and dataroot is ready 250 * Stops execution if something went wrong 251 * @throws coding_exception 252 * @return void 253 */ 254 protected static function test_environment_problem() { 255 global $CFG, $DB; 256 257 if (!defined('BEHAT_UTIL')) { 258 throw new coding_exception('This method can be only used by Behat CLI tool'); 259 } 260 261 if (!self::is_test_site()) { 262 behat_error(1, 'This is not a behat test site!'); 263 } 264 265 $tables = $DB->get_tables(false); 266 if (empty($tables)) { 267 behat_error(BEHAT_EXITCODE_INSTALL, ''); 268 } 269 270 if (!self::is_test_data_updated()) { 271 behat_error(BEHAT_EXITCODE_REINSTALL, 'The test environment was initialised for a different version'); 272 } 273 } 274 275 /** 276 * Enables test mode 277 * 278 * It uses CFG->behat_dataroot 279 * 280 * Starts the test mode checking the composer installation and 281 * the test environment and updating the available 282 * features and steps definitions. 283 * 284 * Stores a file in dataroot/behat to allow Moodle to switch 285 * to the test environment when using cli-server. 286 * @param bool $themesuitewithallfeatures List themes to include core features. 287 * @param string $tags comma separated tag, which will be given preference while distributing features in parallel run. 288 * @param int $parallelruns number of parallel runs. 289 * @param int $run current run. 290 * @throws coding_exception 291 * @return void 292 */ 293 public static function start_test_mode($themesuitewithallfeatures = false, $tags = '', $parallelruns = 0, $run = 0) { 294 295 if (!defined('BEHAT_UTIL')) { 296 throw new coding_exception('This method can be only used by Behat CLI tool'); 297 } 298 299 // Checks the behat set up and the PHP version. 300 if ($errorcode = behat_command::behat_setup_problem()) { 301 exit($errorcode); 302 } 303 304 // Check that test environment is correctly set up. 305 self::test_environment_problem(); 306 307 // Updates all the Moodle features and steps definitions. 308 behat_config_manager::update_config_file('', true, $tags, $themesuitewithallfeatures, $parallelruns, $run); 309 310 if (self::is_test_mode_enabled()) { 311 return; 312 } 313 314 $contents = '$CFG->behat_wwwroot, $CFG->behat_prefix and $CFG->behat_dataroot' . 315 ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot'; 316 $filepath = self::get_test_file_path(); 317 if (!file_put_contents($filepath, $contents)) { 318 behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $filepath . ' can not be created'); 319 } 320 } 321 322 /** 323 * Returns the status of the behat test environment 324 * 325 * @return int Error code 326 */ 327 public static function get_behat_status() { 328 329 if (!defined('BEHAT_UTIL')) { 330 throw new coding_exception('This method can be only used by Behat CLI tool'); 331 } 332 333 // Checks the behat set up and the PHP version, returning an error code if something went wrong. 334 if ($errorcode = behat_command::behat_setup_problem()) { 335 return $errorcode; 336 } 337 338 // Check that test environment is correctly set up, stops execution. 339 self::test_environment_problem(); 340 } 341 342 /** 343 * Disables test mode 344 * @throws coding_exception 345 * @return void 346 */ 347 public static function stop_test_mode() { 348 349 if (!defined('BEHAT_UTIL')) { 350 throw new coding_exception('This method can be only used by Behat CLI tool'); 351 } 352 353 $testenvfile = self::get_test_file_path(); 354 behat_config_manager::set_behat_run_config_value('behatsiteenabled', 0); 355 356 if (!self::is_test_mode_enabled()) { 357 echo "Test environment was already disabled\n"; 358 } else { 359 if (!unlink($testenvfile)) { 360 behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete test environment file'); 361 } 362 } 363 } 364 365 /** 366 * Checks whether test environment is enabled or disabled 367 * 368 * To check is the current script is running in the test 369 * environment 370 * 371 * @return bool 372 */ 373 public static function is_test_mode_enabled() { 374 375 $testenvfile = self::get_test_file_path(); 376 if (file_exists($testenvfile)) { 377 return true; 378 } 379 380 return false; 381 } 382 383 /** 384 * Returns the path to the file which specifies if test environment is enabled 385 * @return string 386 */ 387 public final static function get_test_file_path() { 388 return behat_command::get_parent_behat_dir() . '/test_environment_enabled.txt'; 389 } 390 391 /** 392 * Removes config settings that were added to the main $CFG config within the Behat CLI 393 * run. 394 * 395 * Database storage is already handled by reset_database and existing config values will 396 * be reset automatically by initialise_cfg(), so we only need to remove added ones. 397 */ 398 public static function remove_added_config() { 399 global $CFG; 400 if (!empty($CFG->behat_cli_added_config)) { 401 foreach ($CFG->behat_cli_added_config as $key => $value) { 402 unset($CFG->{$key}); 403 } 404 unset($CFG->behat_cli_added_config); 405 } 406 } 407 408 /** 409 * Reset contents of all database tables to initial values, reset caches, etc. 410 */ 411 public static function reset_all_data() { 412 // Reset database. 413 self::reset_database(); 414 415 // Purge dataroot directory. 416 self::reset_dataroot(); 417 418 // Reset all static caches. 419 accesslib_clear_all_caches(true); 420 accesslib_reset_role_cache(); 421 // Reset the nasty strings list used during the last test. 422 nasty_strings::reset_used_strings(); 423 424 filter_manager::reset_caches(); 425 426 \core_reportbuilder\manager::reset_caches(); 427 428 // Reset course and module caches. 429 core_courseformat\base::reset_course_cache(0); 430 get_fast_modinfo(0, 0, true); 431 432 // Inform data generator. 433 self::get_data_generator()->reset(); 434 435 // Initialise $CFG with default values. This is needed for behat cli process, so we don't have modified 436 // $CFG values from the old run. @see set_config. 437 self::remove_added_config(); 438 initialise_cfg(); 439 } 440 441 /** 442 * Restore theme CSS stored during behat setup. 443 */ 444 public static function restore_saved_themes(): void { 445 global $CFG; 446 447 $themerev = theme_get_revision(); 448 449 $framework = self::get_framework(); 450 $storageroot = self::get_dataroot() . "/{$framework}/themedata"; 451 $themenames = array_keys(\core_component::get_plugin_list('theme')); 452 $directions = ['ltr', 'rtl']; 453 454 $themeconfigs = array_map(function($themename) { 455 return \theme_config::load($themename); 456 }, $themenames); 457 458 foreach ($themeconfigs as $themeconfig) { 459 $themename = $themeconfig->name; 460 $themesubrev = theme_get_sub_revision_for_theme($themename); 461 462 $dirname = "{$storageroot}/{$themename}"; 463 foreach ($directions as $direction) { 464 $cssfile = "{$dirname}/{$direction}.css"; 465 if (file_exists($cssfile)) { 466 $themeconfig->set_css_content_cache(file_get_contents($cssfile)); 467 } 468 } 469 } 470 } 471 472 /** 473 * Pause execution immediately. 474 * 475 * @param Session $session 476 * @param string $message The message to show when pausing. 477 * This will be passed through cli_ansi_format so appropriate ANSI formatting and features are available. 478 */ 479 public static function pause(Session $session, string $message): void { 480 $posixexists = function_exists('posix_isatty'); 481 482 // Make sure this step is only used with interactive terminal (if detected). 483 if ($posixexists && !@posix_isatty(STDOUT)) { 484 throw new ExpectationException('Break point should only be used with interactive terminal.', $session); 485 } 486 487 // Save the cursor position, ring the bell, and add a new line. 488 fwrite(STDOUT, cli_ansi_format("<cursor:save><bell><newline>")); 489 490 // Output the formatted message and reset colour back to normal. 491 $formattedmessage = cli_ansi_format("{$message}<colour:normal>"); 492 fwrite(STDOUT, $formattedmessage); 493 494 // Wait for input. 495 fread(STDIN, 1024); 496 497 // Move the cursor back up to the previous position, then restore the original position stored earlier, and move 498 // it back down again. 499 fwrite(STDOUT, cli_ansi_format("<cursor:up><cursor:up><cursor:restore><cursor:down><cursor:down>")); 500 501 // Add any extra lines back if the provided message was spread over multiple lines. 502 $linecount = count(explode("\n", $formattedmessage)); 503 fwrite(STDOUT, str_repeat(cli_ansi_format("<cursor:down>"), $linecount - 1)); 504 } 505 506 /** 507 * Gets a text-based site version description. 508 * 509 * @return string The site info 510 */ 511 public static function get_site_info() { 512 $siteinfo = parent::get_site_info(); 513 514 $accessibility = empty(behat_config_manager::get_behat_run_config_value('axe')) ? 'No' : 'Yes'; 515 516 $siteinfo .= <<<EOF 517 Run optional tests: 518 - Accessibility: {$accessibility} 519 520 EOF; 521 522 return $siteinfo; 523 } 524 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body