Proyectos de Subversion Moodle

Rev

Rev 1 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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 noreplyaddress to an example domain, as it should be valid email address and test site can be a localhost.
123
        set_config('noreplyaddress', 'noreply@example.com');
124
 
125
        // Set the support email address.
126
        set_config('supportemail', 'email@example.com');
127
 
128
        // Remove any default blocked hosts and port restrictions, to avoid blocking tests (eg those using local files).
129
        set_config('curlsecurityblockedhosts', '');
130
        set_config('curlsecurityallowedport', '');
131
 
132
        // Execute all the adhoc tasks.
133
        while ($task = \core\task\manager::get_next_adhoc_task(time())) {
134
            $task->execute();
135
            \core\task\manager::adhoc_task_complete($task);
136
        }
137
 
138
        // Keeps the current version of database and dataroot.
139
        self::store_versions_hash();
140
 
141
        // Stores the database contents for fast reset.
142
        self::store_database_state();
143
    }
144
 
145
    /**
146
     * Build theme CSS.
147
     */
148
    public static function build_themes($mtraceprogress = false) {
149
        global $CFG;
150
        require_once("{$CFG->libdir}/outputlib.php");
151
 
152
        $themenames = array_keys(\core_component::get_plugin_list('theme'));
153
 
154
        // Load the theme configs.
155
        $themeconfigs = array_map(function($themename) {
156
            return \theme_config::load($themename);
157
        }, $themenames);
158
 
159
        // Build the list of themes and cache them in local cache.
160
        $themes = theme_build_css_for_themes($themeconfigs, ['ltr'], true, $mtraceprogress);
161
 
162
        $framework = self::get_framework();
163
        $storageroot = self::get_dataroot() . "/{$framework}/themedata";
164
 
165
        foreach ($themes as $themename => $themedata) {
166
            $dirname = "{$storageroot}/{$themename}";
167
            check_dir_exists($dirname);
168
            foreach ($themedata as $direction => $css) {
169
                file_put_contents("{$dirname}/{$direction}.css", $css);
170
            }
171
        }
172
    }
173
 
174
    /**
175
     * Drops dataroot and remove test database tables
176
     * @throws coding_exception
177
     * @return void
178
     */
179
    public static function drop_site() {
180
 
181
        if (!defined('BEHAT_UTIL')) {
182
            throw new coding_exception('This method can be only used by Behat CLI tool');
183
        }
184
 
185
        self::reset_dataroot();
186
        self::drop_database(true);
187
        self::drop_dataroot();
188
    }
189
 
190
    /**
191
     * Delete files and directories under dataroot.
192
     */
193
    public static function drop_dataroot() {
194
        global $CFG;
195
 
196
        // As behat directory is now created under default $CFG->behat_dataroot_parent, so remove the whole dir.
197
        if ($CFG->behat_dataroot !== $CFG->behat_dataroot_parent) {
198
            remove_dir($CFG->behat_dataroot, false);
199
        } else {
200
            // It should never come here.
201
            throw new moodle_exception("Behat dataroot should not be same as parent behat data root.");
202
        }
203
    }
204
 
205
    /**
206
     * Checks if $CFG->behat_wwwroot is available and using same versions for cli and web.
207
     *
208
     * @return void
209
     */
210
    public static function check_server_status() {
211
        global $CFG;
212
 
213
        $url = $CFG->behat_wwwroot . '/admin/tool/behat/tests/behat/fixtures/environment.php';
214
 
215
        // Get web versions used by behat site.
216
        $ch = curl_init($url);
217
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
218
        $result = curl_exec($ch);
219
        $statuscode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
220
        curl_close($ch);
221
 
222
        if ($statuscode !== 200 || empty($result) || (!$result = json_decode($result, true))) {
223
 
224
            behat_error (BEHAT_EXITCODE_REQUIREMENT, $CFG->behat_wwwroot . ' is not available, ensure you specified ' .
225
                'correct url and that the server is set up and started.' . PHP_EOL . ' More info in ' .
226
                behat_command::DOCS_URL . PHP_EOL . parent::get_site_info());
227
        }
228
 
229
        // Check if cli version is same as web version.
230
        $clienv = self::get_environment();
231
        if ($result != $clienv) {
232
            $output = 'Differences detected between cli and webserver...'.PHP_EOL;
233
            foreach ($result as $key => $version) {
234
                if ($clienv[$key] != $version) {
235
                    $output .= ' ' . $key . ': ' . PHP_EOL;
236
                    $output .= ' - web server: ' . $version . PHP_EOL;
237
                    $output .= ' - cli: ' . $clienv[$key] . PHP_EOL;
238
                }
239
            }
240
            echo $output;
241
            ob_flush();
242
        }
243
    }
244
 
245
    /**
246
     * Checks whether the test database and dataroot is ready
247
     * Stops execution if something went wrong
248
     * @throws coding_exception
249
     * @return void
250
     */
251
    protected static function test_environment_problem() {
252
        global $CFG, $DB;
253
 
254
        if (!defined('BEHAT_UTIL')) {
255
            throw new coding_exception('This method can be only used by Behat CLI tool');
256
        }
257
 
258
        if (!self::is_test_site()) {
259
            behat_error(1, 'This is not a behat test site!');
260
        }
261
 
262
        $tables = $DB->get_tables(false);
263
        if (empty($tables)) {
264
            behat_error(BEHAT_EXITCODE_INSTALL, '');
265
        }
266
 
267
        if (!self::is_test_data_updated()) {
268
            behat_error(BEHAT_EXITCODE_REINSTALL, 'The test environment was initialised for a different version');
269
        }
270
    }
271
 
272
    /**
273
     * Enables test mode
274
     *
275
     * It uses CFG->behat_dataroot
276
     *
277
     * Starts the test mode checking the composer installation and
278
     * the test environment and updating the available
279
     * features and steps definitions.
280
     *
281
     * Stores a file in dataroot/behat to allow Moodle to switch
282
     * to the test environment when using cli-server.
283
     * @param bool $themesuitewithallfeatures List themes to include core features.
284
     * @param string $tags comma separated tag, which will be given preference while distributing features in parallel run.
285
     * @param int $parallelruns number of parallel runs.
286
     * @param int $run current run.
287
     * @throws coding_exception
288
     * @return void
289
     */
290
    public static function start_test_mode($themesuitewithallfeatures = false, $tags = '', $parallelruns = 0, $run = 0) {
291
 
292
        if (!defined('BEHAT_UTIL')) {
293
            throw new coding_exception('This method can be only used by Behat CLI tool');
294
        }
295
 
296
        // Checks the behat set up and the PHP version.
297
        if ($errorcode = behat_command::behat_setup_problem()) {
298
            exit($errorcode);
299
        }
300
 
301
        // Check that test environment is correctly set up.
302
        self::test_environment_problem();
303
 
304
        // Updates all the Moodle features and steps definitions.
305
        behat_config_manager::update_config_file('', true, $tags, $themesuitewithallfeatures, $parallelruns, $run);
306
 
307
        if (self::is_test_mode_enabled()) {
308
            return;
309
        }
310
 
311
        $contents = '$CFG->behat_wwwroot, $CFG->behat_prefix and $CFG->behat_dataroot' .
312
            ' are currently used as $CFG->wwwroot, $CFG->prefix and $CFG->dataroot';
313
        $filepath = self::get_test_file_path();
314
        if (!file_put_contents($filepath, $contents)) {
315
            behat_error(BEHAT_EXITCODE_PERMISSIONS, 'File ' . $filepath . ' can not be created');
316
        }
317
    }
318
 
319
    /**
320
     * Returns the status of the behat test environment
321
     *
322
     * @return int Error code
323
     */
1441 ariadna 324
    public static function get_behat_status(): int {
1 efrain 325
        if (!defined('BEHAT_UTIL')) {
326
            throw new coding_exception('This method can be only used by Behat CLI tool');
327
        }
328
 
329
        // Checks the behat set up and the PHP version, returning an error code if something went wrong.
1441 ariadna 330
        $errorcode = behat_command::behat_setup_problem();
331
 
332
        if ($errorcode !== 0) {
1 efrain 333
            return $errorcode;
334
        }
335
 
336
        // Check that test environment is correctly set up, stops execution.
337
        self::test_environment_problem();
1441 ariadna 338
 
339
        return $errorcode;
1 efrain 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
    final public 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
        // Reset the DI container.
433
        \core\di::reset_container();
434
 
435
        // Inform data generator.
436
        self::get_data_generator()->reset();
437
 
438
        // Reset the task manager.
439
        \core\task\manager::reset_state();
440
 
441
        // Initialise $CFG with default values. This is needed for behat cli process, so we don't have modified
442
        // $CFG values from the old run. @see set_config.
443
        self::remove_added_config();
444
        initialise_cfg();
445
    }
446
 
447
    /**
448
     * Restore theme CSS stored during behat setup.
449
     */
450
    public static function restore_saved_themes(): void {
451
        global $CFG;
452
 
453
        $themerev = theme_get_revision();
454
 
455
        $framework = self::get_framework();
456
        $storageroot = self::get_dataroot() . "/{$framework}/themedata";
457
        $themenames = array_keys(\core_component::get_plugin_list('theme'));
458
        $directions = ['ltr', 'rtl'];
459
 
460
        $themeconfigs = array_map(function($themename) {
461
            return \theme_config::load($themename);
462
        }, $themenames);
463
 
464
        foreach ($themeconfigs as $themeconfig) {
465
            $themename = $themeconfig->name;
466
            $themesubrev = theme_get_sub_revision_for_theme($themename);
467
 
468
            $dirname = "{$storageroot}/{$themename}";
469
            foreach ($directions as $direction) {
470
                $cssfile = "{$dirname}/{$direction}.css";
471
                if (file_exists($cssfile)) {
472
                    $themeconfig->set_css_content_cache(file_get_contents($cssfile));
473
                }
474
            }
475
        }
476
    }
477
 
478
    /**
479
     * Pause execution immediately.
480
     *
481
     * @param Session $session
482
     * @param string $message The message to show when pausing.
483
     * This will be passed through cli_ansi_format so appropriate ANSI formatting and features are available.
484
     */
485
    public static function pause(Session $session, string $message): void {
486
        $posixexists = function_exists('posix_isatty');
487
 
488
        // Make sure this step is only used with interactive terminal (if detected).
489
        if ($posixexists && !@posix_isatty(STDOUT)) {
490
            throw new ExpectationException('Break point should only be used with interactive terminal.', $session);
491
        }
492
 
493
        // Save the cursor position, ring the bell, and add a new line.
494
        fwrite(STDOUT, cli_ansi_format("<cursor:save><bell><newline>"));
495
 
496
        // Output the formatted message and reset colour back to normal.
497
        $formattedmessage = cli_ansi_format("{$message}<colour:normal>");
498
        fwrite(STDOUT, $formattedmessage);
499
 
500
        // Wait for input.
501
        fread(STDIN, 1024);
502
 
503
        // Move the cursor back up to the previous position, then restore the original position stored earlier, and move
504
        // it back down again.
505
        fwrite(STDOUT, cli_ansi_format("<cursor:up><cursor:up><cursor:restore><cursor:down><cursor:down>"));
506
 
507
        // Add any extra lines back if the provided message was spread over multiple lines.
508
        $linecount = count(explode("\n", $formattedmessage));
509
        fwrite(STDOUT, str_repeat(cli_ansi_format("<cursor:down>"), $linecount - 1));
510
    }
511
 
512
    /**
513
     * Gets a text-based site version description.
514
     *
515
     * @return string The site info
516
     */
517
    public static function get_site_info() {
518
        $siteinfo = parent::get_site_info();
519
 
520
        $accessibility = empty(behat_config_manager::get_behat_run_config_value('axe')) ? 'No' : 'Yes';
521
        $scssdeprecations = empty(behat_config_manager::get_behat_run_config_value('scss-deprecations')) ? 'No' : 'Yes';
1441 ariadna 522
        $icondeprecations = empty(behat_config_manager::get_behat_run_config_value('no-icon-deprecations')) ? 'Yes' : 'No';
1 efrain 523
 
524
        $siteinfo .= <<<EOF
525
Run optional tests:
526
- Accessibility: {$accessibility}
527
- SCSS deprecations: {$scssdeprecations}
1441 ariadna 528
- Icon deprecations: {$icondeprecations}
1 efrain 529
 
530
EOF;
531
 
532
        return $siteinfo;
533
    }
534
}