267406bb5bf9786977a915716a9d8533152363b2
[moodle.git] / lib / behat / classes / util.php
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/>.
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  */
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;
37 use Behat\Mink\Exception\ExpectationException;
39 /**
40  * Init/reset utilities for Behat database and dataroot
41  *
42  * @package   core
43  * @category  test
44  * @copyright 2013 David MonllaĆ³
45  * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
46  */
47 class behat_util extends testing_util {
49     /**
50      * The behat test site fullname and shortname.
51      */
52     const BEHATSITENAME = "Acceptance test site";
54     /**
55      * @var array Files to skip when resetting dataroot folder
56      */
57     protected static $datarootskiponreset = array('.', '..', 'behat', 'behattestdir.txt');
59     /**
60      * @var array Files to skip when dropping dataroot folder
61      */
62     protected static $datarootskipondrop = array('.', '..', 'lock');
64     /**
65      * Installs a site using $CFG->dataroot and $CFG->prefix
66      * @throws coding_exception
67      * @return void
68      */
69     public static function install_site() {
70         global $DB, $CFG;
71         require_once($CFG->dirroot.'/user/lib.php');
72         if (!defined('BEHAT_UTIL')) {
73             throw new coding_exception('This method can be only used by Behat CLI tool');
74         }
76         $tables = $DB->get_tables(false);
77         if (!empty($tables)) {
78             behat_error(BEHAT_EXITCODE_INSTALLED);
79         }
81         // New dataroot.
82         self::reset_dataroot();
84         $options = array();
85         $options['adminuser'] = 'admin';
86         $options['adminpass'] = 'admin';
87         $options['fullname'] = self::BEHATSITENAME;
88         $options['shortname'] = self::BEHATSITENAME;
90         install_cli_database($options, false);
92         // We need to keep the installed dataroot filedir files.
93         // So each time we reset the dataroot before running a test, the default files are still installed.
94         self::save_original_data_files();
96         $frontpagesummary = new admin_setting_special_frontpagedesc();
97         $frontpagesummary->write_setting(self::BEHATSITENAME);
99         // Update admin user info.
100         $user = $DB->get_record('user', array('username' => 'admin'));
101         $user->email = 'moodle@example.com';
102         $user->firstname = 'Admin';
103         $user->lastname = 'User';
104         $user->city = 'Perth';
105         $user->country = 'AU';
106         user_update_user($user, false);
108         // Disable email message processor.
109         $DB->set_field('message_processors', 'enabled', '0', array('name' => 'email'));
111         // Sets maximum debug level.
112         set_config('debug', DEBUG_DEVELOPER);
113         set_config('debugdisplay', 1);
115         // Disable some settings that are not wanted on test sites.
116         set_config('noemailever', 1);
118         // Enable web cron.
119         set_config('cronclionly', 0);
121         // Set editor autosave to high value, so as to avoid unwanted ajax.
122         set_config('autosavefrequency', '604800', 'editor_atto');
124         // Set noreplyaddress to an example domain, as it should be valid email address and test site can be a localhost.
125         set_config('noreplyaddress', 'noreply@example.com');
127         // Keeps the current version of database and dataroot.
128         self::store_versions_hash();
130         // Stores the database contents for fast reset.
131         self::store_database_state();
132     }
134     /**
135      * Drops dataroot and remove test database tables
136      * @throws coding_exception
137      * @return void
138      */
139     public static function drop_site() {
141         if (!defined('BEHAT_UTIL')) {
142             throw new coding_exception('This method can be only used by Behat CLI tool');
143         }
145         self::reset_dataroot();
146         self::drop_database(true);
147         self::drop_dataroot();
148     }
150     /**
151      * Delete files and directories under dataroot.
152      */
153     public static function drop_dataroot() {
154         global $CFG;
156         // As behat directory is now created under default $CFG->behat_dataroot_parent, so remove the whole dir.
157         if ($CFG->behat_dataroot !== $CFG->behat_dataroot_parent) {
158             remove_dir($CFG->behat_dataroot, false);
159         } else {
160             // It should never come here.
161             throw new moodle_exception("Behat dataroot should not be same as parent behat data root.");
162         }
163     }
165     /**
166      * Checks if $CFG->behat_wwwroot is available and using same versions for cli and web.
167      *
168      * @return void
169      */
170     public static function check_server_status() {
171         global $CFG;
173         $url = $CFG->behat_wwwroot . '/admin/tool/behat/tests/behat/fixtures/environment.php';
175         // Get web versions used by behat site.
176         $ch = curl_init($url);
177         curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
178         $result = curl_exec($ch);
179         $statuscode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
180         curl_close($ch);
182         if ($statuscode !== 200 || empty($result) || (!$result = json_decode($result, true))) {
184             behat_error (BEHAT_EXITCODE_REQUIREMENT, $CFG->behat_wwwroot . ' is not available, ensure you specified ' .
185                 'correct url and that the server is set up and started.' . PHP_EOL . ' More info in ' .
186                 behat_command::DOCS_URL . PHP_EOL);
187         }
189         // Check if cli version is same as web version.
190         $clienv = self::get_environment();
191         if ($result != $clienv) {
192             $output = 'Differences detected between cli and webserver...'.PHP_EOL;
193             foreach ($result as $key => $version) {
194                 if ($clienv[$key] != $version) {
195                     $output .= ' ' . $key . ': ' . PHP_EOL;
196                     $output .= ' - web server: ' . $version . PHP_EOL;
197                     $output .= ' - cli: ' . $clienv[$key] . PHP_EOL;
198                 }
199             }
200             echo $output;
201             ob_flush();
202         }
203     }
205     /**
206      * Checks whether the test database and dataroot is ready
207      * Stops execution if something went wrong
208      * @throws coding_exception
209      * @return void
210      */
211     protected static function test_environment_problem() {
212         global $CFG, $DB;
214         if (!defined('BEHAT_UTIL')) {
215             throw new coding_exception('This method can be only used by Behat CLI tool');
216         }
218         if (!self::is_test_site()) {
219             behat_error(1, 'This is not a behat test site!');
220         }
222         $tables = $DB->get_tables(false);
223         if (empty($tables)) {
224             behat_error(BEHAT_EXITCODE_INSTALL, '');
225         }
227         if (!self::is_test_data_updated()) {
228             behat_error(BEHAT_EXITCODE_REINSTALL, 'The test environment was initialised for a different version');
229         }
230     }
232     /**
233      * Enables test mode
234      *
235      * It uses CFG->behat_dataroot
236      *
237      * Starts the test mode checking the composer installation and
238      * the test environment and updating the available
239      * features and steps definitions.
240      *
241      * Stores a file in dataroot/behat to allow Moodle to switch
242      * to the test environment when using cli-server.
243      * @param bool $themesuitewithallfeatures List themes to include core features.
244      * @param string $tags comma separated tag, which will be given preference while distributing features in parallel run.
245      * @param int $parallelruns number of parallel runs.
246      * @param int $run current run.
247      * @throws coding_exception
248      * @return void
249      */
250     public static function start_test_mode($themesuitewithallfeatures = false, $tags = '', $parallelruns = 0, $run = 0) {
251         global $CFG;
253         if (!defined('BEHAT_UTIL')) {
254             throw new coding_exception('This method can be only used by Behat CLI tool');
255         }
257         // Checks the behat set up and the PHP version.
258         if ($errorcode = behat_command::behat_setup_problem()) {
259             exit($errorcode);
260         }
262         // Check that test environment is correctly set up.
263         self::test_environment_problem();
265         // Updates all the Moodle features and steps definitions.
266         behat_config_manager::update_config_file('', true, $tags, $themesuitewithallfeatures, $parallelruns, $run);
268         if (self::is_test_mode_enabled()) {
269             return;
270         }
272         $contents = '$CFG->behat_wwwroot, $CFG->behat_prefix and $CFG->behat_dataroot' .
273             ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
274         $filepath = self::get_test_file_path();
275         if (!file_put_contents($filepath, $contents)) {
276             behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $filepath . ' can not be created');
277         }
278     }
280     /**
281      * Returns the status of the behat test environment
282      *
283      * @return int Error code
284      */
285     public static function get_behat_status() {
287         if (!defined('BEHAT_UTIL')) {
288             throw new coding_exception('This method can be only used by Behat CLI tool');
289         }
291         // Checks the behat set up and the PHP version, returning an error code if something went wrong.
292         if ($errorcode = behat_command::behat_setup_problem()) {
293             return $errorcode;
294         }
296         // Check that test environment is correctly set up, stops execution.
297         self::test_environment_problem();
298     }
300     /**
301      * Disables test mode
302      * @throws coding_exception
303      * @return void
304      */
305     public static function stop_test_mode() {
307         if (!defined('BEHAT_UTIL')) {
308             throw new coding_exception('This method can be only used by Behat CLI tool');
309         }
311         $testenvfile = self::get_test_file_path();
312         behat_config_manager::set_behat_run_config_value('behatsiteenabled', 0);
314         if (!self::is_test_mode_enabled()) {
315             echo "Test environment was already disabled\n";
316         } else {
317             if (!unlink($testenvfile)) {
318                 behat_error(BEHAT_EXITCODE_PERMISSIONS, 'Can not delete test environment file');
319             }
320         }
321     }
323     /**
324      * Checks whether test environment is enabled or disabled
325      *
326      * To check is the current script is running in the test
327      * environment
328      *
329      * @return bool
330      */
331     public static function is_test_mode_enabled() {
333         $testenvfile = self::get_test_file_path();
334         if (file_exists($testenvfile)) {
335             return true;
336         }
338         return false;
339     }
341     /**
342      * Returns the path to the file which specifies if test environment is enabled
343      * @return string
344      */
345     public final static function get_test_file_path() {
346         return behat_command::get_parent_behat_dir() . '/test_environment_enabled.txt';
347     }
349     /**
350      * Removes config settings that were added to the main $CFG config within the Behat CLI
351      * run.
352      *
353      * Database storage is already handled by reset_database and existing config values will
354      * be reset automatically by initialise_cfg(), so we only need to remove added ones.
355      */
356     public static function remove_added_config() {
357         global $CFG;
358         if (!empty($CFG->behat_cli_added_config)) {
359             foreach ($CFG->behat_cli_added_config as $key => $value) {
360                 unset($CFG->{$key});
361             }
362             unset($CFG->behat_cli_added_config);
363         }
364     }
366     /**
367      * Reset contents of all database tables to initial values, reset caches, etc.
368      */
369     public static function reset_all_data() {
370         // Reset database.
371         self::reset_database();
373         // Purge dataroot directory.
374         self::reset_dataroot();
376         // Reset all static caches.
377         accesslib_clear_all_caches(true);
378         accesslib_reset_role_cache();
379         // Reset the nasty strings list used during the last test.
380         nasty_strings::reset_used_strings();
382         filter_manager::reset_caches();
384         // Reset course and module caches.
385         if (class_exists('format_base')) {
386             // If file containing class is not loaded, there is no cache there anyway.
387             format_base::reset_course_cache(0);
388         }
389         get_fast_modinfo(0, 0, true);
391         // Inform data generator.
392         self::get_data_generator()->reset();
394         // Initialise $CFG with default values. This is needed for behat cli process, so we don't have modified
395         // $CFG values from the old run. @see set_config.
396         self::remove_added_config();
397         initialise_cfg();
398     }
400     /**
401      * Pause execution immediately.
402      *
403      * @param Session $session
404      * @param string $message The message to show when pausing.
405      * This will be passed through cli_ansi_format so appropriate ANSI formatting and features are available.
406      */
407     public static function pause(Session $session, string $message): void {
408         $posixexists = function_exists('posix_isatty');
410         // Make sure this step is only used with interactive terminal (if detected).
411         if ($posixexists && !@posix_isatty(STDOUT)) {
412             throw new ExpectationException('Break point should only be used with interactive terminal.', $session);
413         }
415         // Save the cursor position, ring the bell, and add a new line.
416         fwrite(STDOUT, cli_ansi_format("<cursor:save><bell><newline>"));
418         // Output the formatted message and reset colour back to normal.
419         $formattedmessage = cli_ansi_format("{$message}<colour:normal>");
420         fwrite(STDOUT, $formattedmessage);
422         // Wait for input.
423         fread(STDIN, 1024);
425         // Move the cursor back up to the previous position, then restore the original position stored earlier, and move
426         // it back down again.
427         fwrite(STDOUT, cli_ansi_format("<cursor:up><cursor:up><cursor:restore><cursor:down><cursor:down>"));
429         // Add any extra lines back if the provided message was spread over multiple lines.
430         $linecount = count(explode("\n", $formattedmessage));
431         fwrite(STDOUT, str_repeat(cli_ansi_format("<cursor:down>"), $linecount - 1));
432     }