Proyectos de Subversion Moodle

Rev

| 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 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 . parent::get_site_info());
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
    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';
522
 
523
        $siteinfo .= <<<EOF
524
Run optional tests:
525
- Accessibility: {$accessibility}
526
- SCSS deprecations: {$scssdeprecations}
527
 
528
EOF;
529
 
530
        return $siteinfo;
531
    }
532
}