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 * Tests lock
19 *
20 * @package    core
21 * @category   test
22 * @copyright  2012 Petr Skoda {@link http://skodak.org}
23 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25
26require_once(__DIR__.'/../lib.php');
27
28/**
29 * Tests lock to prevent concurrent executions of the same test suite
30 *
31 * @package    core
32 * @category   test
33 * @copyright  2012 Petr Skoda {@link http://skodak.org}
34 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35 */
36class test_lock {
37
38    /**
39     * @var array Array of resource used for prevention of parallel test execution
40     */
41    protected static $lockhandles = array();
42
43    /**
44     * Prevent parallel test execution - this can not work in Moodle because we modify database and dataroot.
45     *
46     * Note: do not call manually!
47     *
48     * @internal
49     * @static
50     * @param   string $framework phpunit|behat
51     * @param   string $lockfilesuffix A sub-type used by the framework
52     * @return  void
53     */
54    public static function acquire(string $framework, string $lockfilesuffix = '') {
55        global $CFG;
56
57        $datarootpath = $CFG->{$framework . '_dataroot'} . '/' . $framework;
58        $lockfile = "{$datarootpath}/lock{$lockfilesuffix}";
59        if (!file_exists($datarootpath)) {
60            // Dataroot not initialised yet.
61            return;
62        }
63        if (!file_exists($lockfile)) {
64            file_put_contents($lockfile, 'This file prevents concurrent execution of Moodle ' . $framework . ' tests');
65            testing_fix_file_permissions($lockfile);
66        }
67
68        $lockhandlename = self::get_lock_handle_name($framework, $lockfilesuffix);
69        if (self::$lockhandles[$lockhandlename] = fopen($lockfile, 'r')) {
70            $wouldblock = null;
71            $locked = flock(self::$lockhandles[$lockhandlename], (LOCK_EX | LOCK_NB), $wouldblock);
72            if (!$locked) {
73                if ($wouldblock) {
74                    echo "Waiting for other test execution to complete...\n";
75                }
76                $locked = flock(self::$lockhandles[$lockhandlename], LOCK_EX);
77            }
78            if (!$locked) {
79                fclose(self::$lockhandles[$lockhandlename]);
80                self::$lockhandles[$lockhandlename] = null;
81            }
82        }
83        register_shutdown_function(['test_lock', 'release'], $framework, $lockfilesuffix);
84    }
85
86    /**
87     * Note: do not call manually!
88     * @internal
89     * @static
90     * @param   string $framework phpunit|behat
91     * @param   string $lockfilesuffix A sub-type used by the framework
92     * @return  void
93     */
94    public static function release(string $framework, string $lockfilesuffix = '') {
95        $lockhandlename = self::get_lock_handle_name($framework, $lockfilesuffix);
96
97        if (self::$lockhandles[$lockhandlename]) {
98            flock(self::$lockhandles[$lockhandlename], LOCK_UN);
99            fclose(self::$lockhandles[$lockhandlename]);
100            self::$lockhandles[$lockhandlename] = null;
101        }
102    }
103
104    /**
105     * Get the name of the lock handle stored in the class.
106     *
107     * @param   string $framework
108     * @param   string $lockfilesuffix
109     * @return  string
110     */
111    protected static function get_lock_handle_name(string $framework, string $lockfilesuffix): string {
112        $lockhandlepieces = [$framework];
113
114        if (!empty($lockfilesuffix)) {
115            $lockhandlepieces[] = $lockfilesuffix;
116        }
117
118        return implode('%', $lockhandlepieces);
119    }
120
121}
122