2 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
18 * Utils for behat-related stuff
22 * @copyright 2012 David MonllaĆ³
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 defined('MOODLE_INTERNAL') ||
die();
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');
33 require_once(__DIR__
. '/../../filelib.php');
34 require_once(__DIR__
. '/../../clilib.php');
36 use Behat\Mink\Session
;
39 * Init/reset utilities for Behat database and dataroot
43 * @copyright 2013 David MonllaĆ³
44 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46 class behat_util
extends testing_util
{
49 * The behat test site fullname and shortname.
51 const BEHATSITENAME
= "Acceptance test site";
54 * @var array Files to skip when resetting dataroot folder
56 protected static $datarootskiponreset = array('.', '..', 'behat', 'behattestdir.txt');
59 * @var array Files to skip when dropping dataroot folder
61 protected static $datarootskipondrop = array('.', '..', 'lock');
64 * Installs a site using $CFG->dataroot and $CFG->prefix
65 * @throws coding_exception
68 public static function install_site() {
70 require_once($CFG->dirroot
.'/user/lib.php');
71 if (!defined('BEHAT_UTIL')) {
72 throw new coding_exception('This method can be only used by Behat CLI tool');
75 $tables = $DB->get_tables(false);
76 if (!empty($tables)) {
77 behat_error(BEHAT_EXITCODE_INSTALLED
);
81 self
::reset_dataroot();
84 $options['adminuser'] = 'admin';
85 $options['adminpass'] = 'admin';
86 $options['fullname'] = self
::BEHATSITENAME
;
87 $options['shortname'] = self
::BEHATSITENAME
;
89 install_cli_database($options, false);
91 // We need to keep the installed dataroot filedir files.
92 // So each time we reset the dataroot before running a test, the default files are still installed.
93 self
::save_original_data_files();
95 $frontpagesummary = new admin_setting_special_frontpagedesc();
96 $frontpagesummary->write_setting(self
::BEHATSITENAME
);
98 // Update admin user info.
99 $user = $DB->get_record('user', array('username' => 'admin'));
100 $user->email
= 'moodle@example.com';
101 $user->firstname
= 'Admin';
102 $user->lastname
= 'User';
103 $user->city
= 'Perth';
104 $user->country
= 'AU';
105 user_update_user($user, false);
107 // Disable email message processor.
108 $DB->set_field('message_processors', 'enabled', '0', array('name' => 'email'));
110 // Sets maximum debug level.
111 set_config('debug', DEBUG_DEVELOPER
);
112 set_config('debugdisplay', 1);
114 // Disable some settings that are not wanted on test sites.
115 set_config('noemailever', 1);
118 set_config('cronclionly', 0);
120 // Set editor autosave to high value, so as to avoid unwanted ajax.
121 set_config('autosavefrequency', '604800', 'editor_atto');
123 // Set noreplyaddress to an example domain, as it should be valid email address and test site can be a localhost.
124 set_config('noreplyaddress', 'noreply@example.com');
126 // Keeps the current version of database and dataroot.
127 self
::store_versions_hash();
129 // Stores the database contents for fast reset.
130 self
::store_database_state();
134 * Drops dataroot and remove test database tables
135 * @throws coding_exception
138 public static function drop_site() {
140 if (!defined('BEHAT_UTIL')) {
141 throw new coding_exception('This method can be only used by Behat CLI tool');
144 self
::reset_dataroot();
145 self
::drop_database(true);
146 self
::drop_dataroot();
150 * Delete files and directories under dataroot.
152 public static function drop_dataroot() {
155 // As behat directory is now created under default $CFG->behat_dataroot_parent, so remove the whole dir.
156 if ($CFG->behat_dataroot
!== $CFG->behat_dataroot_parent
) {
157 remove_dir($CFG->behat_dataroot
, false);
159 // It should never come here.
160 throw new moodle_exception("Behat dataroot should not be same as parent behat data root.");
165 * Checks if $CFG->behat_wwwroot is available and using same versions for cli and web.
169 public static function check_server_status() {
172 $url = $CFG->behat_wwwroot
. '/admin/tool/behat/tests/behat/fixtures/environment.php';
174 // Get web versions used by behat site.
175 $ch = curl_init($url);
176 curl_setopt($ch, CURLOPT_RETURNTRANSFER
, true);
177 $result = curl_exec($ch);
178 $statuscode = curl_getinfo($ch, CURLINFO_HTTP_CODE
);
181 if ($statuscode !== 200 ||
empty($result) ||
(!$result = json_decode($result, true))) {
183 behat_error (BEHAT_EXITCODE_REQUIREMENT
, $CFG->behat_wwwroot
. ' is not available, ensure you specified ' .
184 'correct url and that the server is set up and started.' . PHP_EOL
. ' More info in ' .
185 behat_command
::DOCS_URL
. PHP_EOL
);
188 // Check if cli version is same as web version.
189 $clienv = self
::get_environment();
190 if ($result != $clienv) {
191 $output = 'Differences detected between cli and webserver...'.PHP_EOL
;
192 foreach ($result as $key => $version) {
193 if ($clienv[$key] != $version) {
194 $output .= ' ' . $key . ': ' . PHP_EOL
;
195 $output .= ' - web server: ' . $version . PHP_EOL
;
196 $output .= ' - cli: ' . $clienv[$key] . PHP_EOL
;
205 * Checks whether the test database and dataroot is ready
206 * Stops execution if something went wrong
207 * @throws coding_exception
210 protected static function test_environment_problem() {
213 if (!defined('BEHAT_UTIL')) {
214 throw new coding_exception('This method can be only used by Behat CLI tool');
217 if (!self
::is_test_site()) {
218 behat_error(1, 'This is not a behat test site!');
221 $tables = $DB->get_tables(false);
222 if (empty($tables)) {
223 behat_error(BEHAT_EXITCODE_INSTALL
, '');
226 if (!self
::is_test_data_updated()) {
227 behat_error(BEHAT_EXITCODE_REINSTALL
, 'The test environment was initialised for a different version');
234 * It uses CFG->behat_dataroot
236 * Starts the test mode checking the composer installation and
237 * the test environment and updating the available
238 * features and steps definitions.
240 * Stores a file in dataroot/behat to allow Moodle to switch
241 * to the test environment when using cli-server.
242 * @param bool $themesuitewithallfeatures List themes to include core features.
243 * @param string $tags comma separated tag, which will be given preference while distributing features in parallel run.
244 * @param int $parallelruns number of parallel runs.
245 * @param int $run current run.
246 * @throws coding_exception
249 public static function start_test_mode($themesuitewithallfeatures = false, $tags = '', $parallelruns = 0, $run = 0) {
252 if (!defined('BEHAT_UTIL')) {
253 throw new coding_exception('This method can be only used by Behat CLI tool');
256 // Checks the behat set up and the PHP version.
257 if ($errorcode = behat_command
::behat_setup_problem()) {
261 // Check that test environment is correctly set up.
262 self
::test_environment_problem();
264 // Updates all the Moodle features and steps definitions.
265 behat_config_manager
::update_config_file('', true, $tags, $themesuitewithallfeatures, $parallelruns, $run);
267 if (self
::is_test_mode_enabled()) {
271 $contents = '$CFG->behat_wwwroot, $CFG->behat_prefix and $CFG->behat_dataroot' .
272 ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
273 $filepath = self
::get_test_file_path();
274 if (!file_put_contents($filepath, $contents)) {
275 behat_error(BEHAT_EXITCODE_PERMISSIONS
, 'File ' . $filepath . ' can not be created');
280 * Returns the status of the behat test environment
282 * @return int Error code
284 public static function get_behat_status() {
286 if (!defined('BEHAT_UTIL')) {
287 throw new coding_exception('This method can be only used by Behat CLI tool');
290 // Checks the behat set up and the PHP version, returning an error code if something went wrong.
291 if ($errorcode = behat_command
::behat_setup_problem()) {
295 // Check that test environment is correctly set up, stops execution.
296 self
::test_environment_problem();
301 * @throws coding_exception
304 public static function stop_test_mode() {
306 if (!defined('BEHAT_UTIL')) {
307 throw new coding_exception('This method can be only used by Behat CLI tool');
310 $testenvfile = self
::get_test_file_path();
311 behat_config_manager
::set_behat_run_config_value('behatsiteenabled', 0);
313 if (!self
::is_test_mode_enabled()) {
314 echo "Test environment was already disabled\n";
316 if (!unlink($testenvfile)) {
317 behat_error(BEHAT_EXITCODE_PERMISSIONS
, 'Can not delete test environment file');
323 * Checks whether test environment is enabled or disabled
325 * To check is the current script is running in the test
330 public static function is_test_mode_enabled() {
332 $testenvfile = self
::get_test_file_path();
333 if (file_exists($testenvfile)) {
341 * Returns the path to the file which specifies if test environment is enabled
344 public final static function get_test_file_path() {
345 return behat_command
::get_parent_behat_dir() . '/test_environment_enabled.txt';
349 * Removes config settings that were added to the main $CFG config within the Behat CLI
352 * Database storage is already handled by reset_database and existing config values will
353 * be reset automatically by initialise_cfg(), so we only need to remove added ones.
355 public static function remove_added_config() {
357 if (!empty($CFG->behat_cli_added_config
)) {
358 foreach ($CFG->behat_cli_added_config
as $key => $value) {
361 unset($CFG->behat_cli_added_config
);
366 * Reset contents of all database tables to initial values, reset caches, etc.
368 public static function reset_all_data() {
370 self
::reset_database();
372 // Purge dataroot directory.
373 self
::reset_dataroot();
375 // Reset all static caches.
376 accesslib_clear_all_caches(true);
377 accesslib_reset_role_cache();
378 // Reset the nasty strings list used during the last test.
379 nasty_strings
::reset_used_strings();
381 filter_manager
::reset_caches();
383 // Reset course and module caches.
384 if (class_exists('format_base')) {
385 // If file containing class is not loaded, there is no cache there anyway.
386 format_base
::reset_course_cache(0);
388 get_fast_modinfo(0, 0, true);
390 // Inform data generator.
391 self
::get_data_generator()->reset();
393 // Initialise $CFG with default values. This is needed for behat cli process, so we don't have modified
394 // $CFG values from the old run. @see set_config.
395 self
::remove_added_config();
400 * Pause execution immediately.
402 * @param Session $session
403 * @param string $message The message to show when pausing.
404 * This will be passed through cli_ansi_format so appropriate ANSI formatting and features are available.
406 public static function pause(Session
$session, string $message): void
{
407 $posixexists = function_exists('posix_isatty');
409 // Make sure this step is only used with interactive terminal (if detected).
410 if ($posixexists && !@posix_isatty
(STDOUT
)) {
411 throw new ExpectationException('Break point should only be used with interactive terminal.', $session);
414 // Save the cursor position, ring the bell, and add a new line.
415 fwrite(STDOUT
, cli_ansi_format("<cursor:save><bell><newline>"));
417 // Output the formatted message and reset colour back to normal.
418 $formattedmessage = cli_ansi_format("{$message}<colour:normal>");
419 fwrite(STDOUT
, $formattedmessage);
424 // Move the cursor back up to the previous position, then restore the original position stored earlier, and move
425 // it back down again.
426 fwrite(STDOUT
, cli_ansi_format("<cursor:up><cursor:up><cursor:restore><cursor:down><cursor:down>"));
428 // Add any extra lines back if the provided message was spread over multiple lines.
429 $linecount = count(explode("\n", $formattedmessage));
430 fwrite(STDOUT
, str_repeat(cli_ansi_format("<cursor:down>"), $linecount - 1));