Merge branch 'MDL-79637-master' of https://github.com/andrewnicols/moodle

This commit is contained in:
Jun Pataleta 2023-10-24 10:48:43 +08:00
commit 73267a7848
No known key found for this signature in database
GPG Key ID: F83510526D99E2C7
5 changed files with 223 additions and 210 deletions

View File

@ -26,18 +26,20 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// phpcs:disable moodle.Files.MoodleInternal.MoodleInternalGlobalState
if (isset($_SERVER['REMOTE_ADDR'])) {
die; // No access from web!
}
// we want to know about all problems
// We want to know about all problems.
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
// Make sure OPcache does not strip comments, we need them in phpunit!
if (ini_get('opcache.enable') and strtolower(ini_get('opcache.enable')) !== 'off') {
if (!ini_get('opcache.save_comments') or strtolower(ini_get('opcache.save_comments')) === 'off') {
if (ini_get('opcache.enable') && strtolower(ini_get('opcache.enable')) !== 'off') {
if (!ini_get('opcache.save_comments') || strtolower(ini_get('opcache.save_comments')) === 'off') {
ini_set('opcache.enable', 0);
}
}
@ -56,6 +58,7 @@ if (isset($_SERVER['REMOTE_ADDR'])) {
if (defined('PHPUNIT_TEST')) {
phpunit_bootstrap_error(1, "PHPUNIT_TEST constant must not be manually defined anywhere!");
}
/** PHPUnit testing framework active */
define('PHPUNIT_TEST', true);
@ -70,14 +73,15 @@ if (defined('CLI_SCRIPT')) {
define('CLI_SCRIPT', true);
$phpunitversion = PHPUnit\Runner\Version::id();
// phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedIf
if ($phpunitversion === '@package_version@') {
// library checked out from git, let's hope dev knows that 3.6.0 is required
// Library checked out from git, let's hope dev knows that 3.6.0 is required.
} else if (version_compare($phpunitversion, '3.6.0', 'lt')) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_PHPUNITWRONG, $phpunitversion);
}
unset($phpunitversion);
// only load CFG from config.php, stop ASAP in lib/setup.php
// Only load CFG from config.php, stop ASAP in lib/setup.php.
define('ABORT_AFTER_CONFIG', true);
require(__DIR__ . '/../../config.php');
@ -86,13 +90,13 @@ if (!defined('PHPUNIT_LONGTEST')) {
define('PHPUNIT_LONGTEST', false);
}
// remove error handling overrides done in config.php
// Remove error handling overrides done in config.php.
error_reporting(E_ALL | E_STRICT);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
set_time_limit(0); // no time limit in CLI scripts, user may cancel execution
set_time_limit(0); // No time limit in CLI scripts, user may cancel execution.
// prepare dataroot
// Prepare dataroot.
umask(0);
if (isset($CFG->phpunit_directorypermissions)) {
$CFG->directorypermissions = $CFG->phpunit_directorypermissions;
@ -101,7 +105,10 @@ if (isset($CFG->phpunit_directorypermissions)) {
}
$CFG->filepermissions = ($CFG->directorypermissions & 0666);
if (!isset($CFG->phpunit_dataroot)) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Missing $CFG->phpunit_dataroot in config.php, can not run tests!');
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'Missing $CFG->phpunit_dataroot in config.php, can not run tests!',
);
}
// Create test dir if does not exists yet.
@ -109,18 +116,24 @@ if (!file_exists($CFG->phpunit_dataroot)) {
mkdir($CFG->phpunit_dataroot, $CFG->directorypermissions);
}
if (!is_dir($CFG->phpunit_dataroot)) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->phpunit_dataroot directory can not be created, can not run tests!');
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'$CFG->phpunit_dataroot directory can not be created, can not run tests!',
);
}
// Ensure we access to phpunit_dataroot realpath always.
$CFG->phpunit_dataroot = realpath($CFG->phpunit_dataroot);
if (isset($CFG->dataroot) and $CFG->phpunit_dataroot === $CFG->dataroot) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->dataroot and $CFG->phpunit_dataroot must not be identical, can not run tests!');
if (isset($CFG->dataroot) && $CFG->phpunit_dataroot === $CFG->dataroot) {
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'$CFG->dataroot and $CFG->phpunit_dataroot must not be identical, can not run tests!',
);
}
if (!is_writable($CFG->phpunit_dataroot)) {
// try to fix permissions if possible
// Try to fix permissions if possible.
if (function_exists('posix_getuid')) {
$chmod = fileperms($CFG->phpunit_dataroot);
if (fileowner($CFG->phpunit_dataroot) == posix_getuid()) {
@ -129,38 +142,47 @@ if (!is_writable($CFG->phpunit_dataroot)) {
}
}
if (!is_writable($CFG->phpunit_dataroot)) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->phpunit_dataroot directory is not writable, can not run tests!');
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'$CFG->phpunit_dataroot directory is not writable, can not run tests!',
);
}
}
if (!file_exists("$CFG->phpunit_dataroot/phpunittestdir.txt")) {
if ($dh = opendir($CFG->phpunit_dataroot)) {
while (($file = readdir($dh)) !== false) {
if ($file === 'phpunit' or $file === '.' or $file === '..' or $file === '.DS_Store') {
if ($file === 'phpunit' || $file === '.' || $file === '..' || $file === '.DS_Store') {
continue;
}
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->phpunit_dataroot directory is not empty, can not run tests! Is it used for anything else?');
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'$CFG->phpunit_dataroot directory is not empty, can not run tests! Is it used for anything else?',
);
}
closedir($dh);
unset($dh);
unset($file);
}
// now we are 100% sure this dir is used only for phpunit tests
// Now we are 100% sure this dir is used only for phpunit tests.
testing_initdataroot($CFG->phpunit_dataroot, 'phpunit');
}
// verify db prefix
// Verify db prefix.
if (!isset($CFG->phpunit_prefix)) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Missing $CFG->phpunit_prefix in config.php, can not run tests!');
}
if ($CFG->phpunit_prefix === '') {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->phpunit_prefix can not be empty, can not run tests!');
}
if (isset($CFG->prefix) and $CFG->prefix === $CFG->phpunit_prefix) {
phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, '$CFG->prefix and $CFG->phpunit_prefix must not be identical, can not run tests!');
if (isset($CFG->prefix) && $CFG->prefix === $CFG->phpunit_prefix) {
phpunit_bootstrap_error(
PHPUNIT_EXITCODE_CONFIGERROR,
'$CFG->prefix and $CFG->phpunit_prefix must not be identical, can not run tests!',
);
}
// override CFG settings if necessary and throw away extra CFG settings
// Override CFG settings if necessary and throw away extra CFG settings.
$CFG->wwwroot = 'https://www.example.com/moodle';
$CFG->dataroot = $CFG->phpunit_dataroot;
$CFG->prefix = $CFG->phpunit_prefix;
@ -173,17 +195,18 @@ $CFG->dbpass = isset($CFG->phpunit_dbpass) ? $CFG->phpunit_dbpass : $CFG->dbp
$CFG->prefix = isset($CFG->phpunit_prefix) ? $CFG->phpunit_prefix : $CFG->prefix;
$CFG->dboptions = isset($CFG->phpunit_dboptions) ? $CFG->phpunit_dboptions : $CFG->dboptions;
$allowed = array('wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
$allowed = ['wwwroot', 'dataroot', 'dirroot', 'admin', 'directorypermissions', 'filepermissions',
'dbtype', 'dblibrary', 'dbhost', 'dbname', 'dbuser', 'dbpass', 'prefix', 'dboptions',
'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass', // keep proxy settings from config.php
// Keep proxy settings from config.php.
'proxyhost', 'proxyport', 'proxytype', 'proxyuser', 'proxypassword', 'proxybypass',
'altcacheconfigpath', 'pathtogs', 'pathtophp', 'pathtodu', 'aspellpath', 'pathtodot',
'pathtounoconv', 'alternative_file_system_class', 'pathtopython'
);
'pathtounoconv', 'alternative_file_system_class', 'pathtopython',
];
$productioncfg = (array) $CFG;
$CFG = new stdClass();
foreach ($productioncfg as $key => $value) {
if (!in_array($key, $allowed) and strpos($key, 'phpunit_') !== 0 and strpos($key, 'behat_') !== 0) {
// ignore
if (!in_array($key, $allowed) && strpos($key, 'phpunit_') !== 0 && strpos($key, 'behat_') !== 0) {
// Ignore.
continue;
}
$CFG->{$key} = $value;
@ -193,22 +216,36 @@ unset($value);
unset($allowed);
unset($productioncfg);
// force the same CFG settings in all sites
$CFG->debug = (E_ALL | E_STRICT); // can not use DEBUG_DEVELOPER yet
// Force the same CFG settings in all sites.
$CFG->debug = (E_ALL | E_STRICT); // Can not use DEBUG_DEVELOPER yet.
$CFG->debugdeveloper = true;
$CFG->debugdisplay = 1;
error_reporting($CFG->debug);
ini_set('display_errors', '1');
ini_set('log_errors', '1');
// some ugly hacks
// Some ugly hacks.
$CFG->themerev = 1;
$CFG->jsrev = 1;
// load test case stub classes and other stuff
(function () {
// Determine if this test is being run with isolation.
// This is tricky because neither PHPUnit, nor PHP provide an official way to work this out.
// PHPUnit does set a value, but not until later on and we need this earlier.
// PHPUnit runs isolated tests by creating a class on the fly and running it through proc_open as standard input.
// There is no other legitimate reason to run PHPUnit this way that I'm aware of.
// When run in this way, PHP sets the value of $_SERVER['PHP_SELF'] to "Standard input code".
// It has done this since 2016, and it is unlikely to change.
define(
'PHPUNIT_ISOLATED_TEST',
$_SERVER['PHP_SELF'] === 'Standard input code',
);
})();
// Load test case stub classes and other stuff.
require_once("$CFG->dirroot/lib/phpunit/lib.php");
// finish moodle init
// Finish moodle init.
define('ABORT_AFTER_CONFIG_CANCEL', true);
if (isset($CFG->phpunit_profilingenabled) && $CFG->phpunit_profilingenabled) {
$CFG->profilingenabled = true;
@ -219,7 +256,7 @@ require("$CFG->dirroot/lib/setup.php");
raise_memory_limit(MEMORY_HUGE);
if (PHPUNIT_UTIL) {
// we are not going to do testing, this is 'true' in utility scripts that only init database
// We are not going to do testing, this is 'true' in utility scripts that only init database.
return;
}
@ -227,13 +264,13 @@ if (PHPUNIT_UTIL) {
// this is not using caches intentionally to help with development.
\core\hook\manager::get_instance();
// is database and dataroot ready for testing?
list($errorcode, $message) = phpunit_util::testing_ready_problem();
// print some version info
// Is database and dataroot ready for testing?
[$errorcode, $message] = phpunit_util::testing_ready_problem();
// Print some version info.
phpunit_util::bootstrap_moodle_info();
if ($errorcode) {
phpunit_bootstrap_error($errorcode, $message);
}
// prepare for the first test run - store fresh globals, reset database and dataroot, etc.
// Prepare for the first test run - store fresh globals, reset database and dataroot, etc.
phpunit_util::bootstrap_init();

View File

@ -14,16 +14,6 @@
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Advanced test case.
*
* @package core
* @category phpunit
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Advanced PHPUnit test case customised for Moodle.
*
@ -34,6 +24,7 @@
*/
abstract class advanced_testcase extends base_testcase {
/** @var bool automatically reset everything? null means log changes */
// phpcs:ignore moodle.NamingConventions.ValidVariableName.MemberNameUnderscore
private $resetAfterTest;
/** @var moodle_transaction */
@ -51,48 +42,32 @@ abstract class advanced_testcase extends base_testcase {
* @param array $data
* @param string $dataName
*/
final public function __construct($name = null, array $data = array(), $dataName = '') {
parent::__construct($name, $data, $dataName);
final public function __construct($name = null, array $data = [], $dataname = '') {
parent::__construct($name, $data, $dataname);
$this->setBackupGlobals(false);
$this->setBackupStaticAttributes(false);
$this->setPreserveGlobalState(false);
}
/**
* Hook into the setInIsolation method to define an optional constant.
*
* @param bool $inisolation
*/
public function setInIsolation(bool $inisolation): void {
parent::setInIsolation($inisolation);
if ($inisolation) {
// Note: This is safe to do because it will only be set once per test run.
define('PHPUNIT_ISOLATED_TEST', true);
}
}
/**
* Runs the bare test sequence.
* @return void
*/
final public function runBare(): void {
global $DB;
if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) {
// this happens when previous test does not reset, we can not use transactions
// This happens when previous test does not reset, we can not use transactions.
$this->testdbtransaction = null;
} else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') {
// database must allow rollback of DDL, so no mysql here
} else if ($DB->get_dbfamily() === 'postgres' || $DB->get_dbfamily() === 'mssql') {
// Database must allow rollback of DDL, so no mysql here.
$this->testdbtransaction = $DB->start_delegated_transaction();
}
try {
$this->setCurrentTimeStart();
parent::runBare();
// set DB reference in case somebody mocked it in test
// Set DB reference in case somebody mocked it in test.
$DB = phpunit_util::get_global_backup('DB');
// Deal with any debugging messages.
@ -101,7 +76,6 @@ abstract class advanced_testcase extends base_testcase {
if (!empty($debugerror)) {
trigger_error('Unexpected debugging() call detected.' . "\n" . $debugerror, E_USER_NOTICE);
}
} catch (Exception $ex) {
$e = $ex;
} catch (Throwable $ex) {
@ -110,12 +84,12 @@ abstract class advanced_testcase extends base_testcase {
}
if (isset($e)) {
// cleanup after failed expectation
// Cleanup after failed expectation.
self::resetAllData();
throw $e;
}
if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
if (!$this->testdbtransaction || $this->testdbtransaction->is_disposed()) {
$this->testdbtransaction = null;
}
@ -123,18 +97,16 @@ abstract class advanced_testcase extends base_testcase {
if ($this->testdbtransaction) {
$DB->force_transaction_rollback();
phpunit_util::reset_all_database_sequences();
phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // No db reset necessary.
}
self::resetAllData(null);
} else if ($this->resetAfterTest === false) {
if ($this->testdbtransaction) {
$this->testdbtransaction->allow_commit();
}
// keep all data untouched for other tests
// Keep all data untouched for other tests.
} else {
// reset but log what changed
// Reset but log what changed.
if ($this->testdbtransaction) {
try {
$this->testdbtransaction->allow_commit();
@ -149,12 +121,14 @@ abstract class advanced_testcase extends base_testcase {
// Reset context cache.
context_helper::reset_caches();
// make sure test did not forget to close transaction
// Make sure test did not forget to close transaction.
if ($DB->is_transaction_started()) {
self::resetAllData();
if ($this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_PASSED
or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED
or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) {
if (
$this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_PASSED
|| $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED
|| $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE
) {
throw new coding_exception('Test ' . $this->getName() . ' did not close database transaction');
}
}
@ -252,7 +226,7 @@ abstract class advanced_testcase extends base_testcase {
* @return void
*/
public function preventResetByRollback() {
if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
if ($this->testdbtransaction && !$this->testdbtransaction->is_disposed()) {
$this->testdbtransaction->allow_commit();
$this->testdbtransaction = null;
}
@ -333,7 +307,7 @@ abstract class advanced_testcase extends base_testcase {
* @param string $message
* @return void
*/
public function assertDebuggingCalledCount($expectedcount, $debugmessages = array(), $debuglevels = array(), $message = '') {
public function assertdebuggingcalledcount($expectedcount, $debugmessages = [], $debuglevels = [], $message = '') {
if (!is_int($expectedcount)) {
throw new coding_exception('assertDebuggingCalledCount $expectedcount argument should be an integer.');
}
@ -346,7 +320,9 @@ abstract class advanced_testcase extends base_testcase {
if ($debugmessages) {
if (!is_array($debugmessages) || count($debugmessages) != $expectedcount) {
throw new coding_exception('assertDebuggingCalledCount $debugmessages should contain ' . $expectedcount . ' messages');
throw new coding_exception(
'assertDebuggingCalledCount $debugmessages should contain ' . $expectedcount . ' messages',
);
}
foreach ($debugmessages as $key => $debugmessage) {
$this->assertSame($debugmessage, $debugging[$key]->message, $message);
@ -355,7 +331,9 @@ abstract class advanced_testcase extends base_testcase {
if ($debuglevels) {
if (!is_array($debuglevels) || count($debuglevels) != $expectedcount) {
throw new coding_exception('assertDebuggingCalledCount $debuglevels should contain ' . $expectedcount . ' messages');
throw new coding_exception(
'assertDebuggingCalledCount $debuglevels should contain ' . $expectedcount . ' messages',
);
}
foreach ($debuglevels as $key => $debuglevel) {
$this->assertSame($debuglevel, $debugging[$key]->level, $message);
@ -548,7 +526,7 @@ abstract class advanced_testcase extends base_testcase {
$user->id = 0;
$user->mnethostid = $CFG->mnet_localhost_id;
} else {
$user = $DB->get_record('user', array('id'=>$user));
$user = $DB->get_record('user', ['id' => $user]);
}
unset($user->description);
unset($user->access);
@ -658,7 +636,7 @@ abstract class advanced_testcase extends base_testcase {
foreach ($files as $file) {
$filepath = $path . '/' . $file;
if (strpos($file, '.') === 0) {
/// Don't check hidden files.
// Don't check hidden files.
continue;
} else if (is_dir($filepath)) {
if (!in_array($filepath, $ignorefolders)) {

View File

@ -320,7 +320,13 @@ class phpunit_util extends testing_util {
public static function reset_database() {
global $DB;
if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
if (defined('PHPUNIT_ISOLATED_TEST') && PHPUNIT_ISOLATED_TEST && self::$lastdbwrites === null) {
// This is an isolated test and the lastdbwrites has not yet been initialised.
// Isolated test runs are reset by the test runner before the run starts.
self::$lastdbwrites = $DB->perf_get_writes();
}
if (!is_null(self::$lastdbwrites) && self::$lastdbwrites == $DB->perf_get_writes()) {
return false;
}

View File

@ -2202,7 +2202,7 @@ function require_phpunit_isolation(): void {
return;
}
if (defined('PHPUNIT_ISOLATED_TEST')) {
if (defined('PHPUNIT_ISOLATED_TEST') && PHPUNIT_ISOLATED_TEST) {
// Already isolated.
return;
}

View File

@ -17,23 +17,12 @@
/**
* Testing util classes
*
* @abstract
* @package core
* @category test
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
/**
* Utils for test sites creation
*
* @package core
* @category test
* @copyright 2012 Petr Skoda {@link http://skodak.org}
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class testing_util {
/**
* @var string dataroot (likely to be $CFG->dataroot).
*/
@ -62,12 +51,12 @@ abstract class testing_util {
/**
* @var array keep list of sequenceid used in a table.
*/
private static $tablesequences = array();
private static $tablesequences = [];
/**
* @var array list of updated tables.
*/
public static $tableupdated = array();
public static $tableupdated = [];
/**
* @var array original structure of all database tables
@ -131,7 +120,7 @@ abstract class testing_util {
* @static
* @return string
*/
protected static final function get_framework() {
final protected static function get_framework() {
$classname = get_called_class();
return substr($classname, 0, strpos($classname, '_'));
}
@ -162,8 +151,8 @@ abstract class testing_util {
$framework = self::get_framework();
if (!file_exists(self::get_dataroot() . '/' . $framework . 'testdir.txt')) {
// this is already tested in bootstrap script,
// but anyway presence of this file means the dataroot is for testing
// This is already tested in bootstrap script,
// But anyway presence of this file means the dataroot is for testing.
return false;
}
@ -191,7 +180,7 @@ abstract class testing_util {
$framework = self::get_framework();
$datarootpath = self::get_dataroot() . '/' . $framework;
if (!file_exists($datarootpath . '/tabledata.ser') or !file_exists($datarootpath . '/tablestructure.ser')) {
if (!file_exists($datarootpath . '/tabledata.ser') || !file_exists($datarootpath . '/tablestructure.ser')) {
return false;
}
@ -207,7 +196,7 @@ abstract class testing_util {
}
// A direct database request must be used to avoid any possible caching of an older value.
$dbhash = $DB->get_field('config', 'value', array('name' => $framework . 'test'));
$dbhash = $DB->get_field('config', 'value', ['name' => $framework . 'test']);
if ($hash !== $dbhash) {
return false;
}
@ -226,18 +215,18 @@ abstract class testing_util {
$framework = self::get_framework();
// store data for all tables
$data = array();
$structure = array();
// Store data for all tables.
$data = [];
$structure = [];
$tables = $DB->get_tables();
foreach ($tables as $table) {
$columns = $DB->get_columns($table);
$structure[$table] = $columns;
if (isset($columns['id']) and $columns['id']->auto_increment) {
$data[$table] = $DB->get_records($table, array(), 'id ASC');
if (isset($columns['id']) && $columns['id']->auto_increment) {
$data[$table] = $DB->get_records($table, [], 'id ASC');
} else {
// there should not be many of these
$data[$table] = $DB->get_records($table, array());
// There should not be many of these.
$data[$table] = $DB->get_records($table, []);
}
}
$data = serialize($data);
@ -260,10 +249,10 @@ abstract class testing_util {
$framework = self::get_framework();
$hash = core_component::get_all_versions_hash();
// add test db flag
// Add test db flag.
set_config($framework . 'test', $hash);
// hash all plugin versions - helps with very fast detection of db structure changes
// Hash all plugin versions - helps with very fast detection of db structure changes.
$hashfile = self::get_dataroot() . '/' . $framework . '/versionshash.txt';
file_put_contents($hashfile, $hash);
testing_fix_file_permissions($hashfile);
@ -281,7 +270,7 @@ abstract class testing_util {
$datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
if (!file_exists($datafile)) {
// Not initialised yet.
return array();
return [];
}
$data = file_get_contents($datafile);
@ -289,7 +278,10 @@ abstract class testing_util {
}
if (!is_array(self::$tabledata)) {
testing_error(1, 'Can not read dataroot/' . $framework . '/tabledata.ser or invalid format, reinitialize test database.');
testing_error(
1,
'Can not read dataroot/' . $framework . '/tabledata.ser or invalid format, reinitialize test database.',
);
}
return self::$tabledata;
@ -307,7 +299,7 @@ abstract class testing_util {
$structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
if (!file_exists($structurefile)) {
// Not initialised yet.
return array();
return [];
}
$data = file_get_contents($structurefile);
@ -315,7 +307,10 @@ abstract class testing_util {
}
if (!is_array(self::$tablestructure)) {
testing_error(1, 'Can not read dataroot/' . $framework . '/tablestructure.ser or invalid format, reinitialize test database.');
testing_error(
1,
"Can not read dataroot/{$framework}/tablestructure.ser or invalid format, reinitialize test database.",
);
}
return self::$tablestructure;
@ -334,10 +329,10 @@ abstract class testing_util {
}
if (!$structure = self::get_tablestructure()) {
return array();
return [];
}
self::$sequencenames = array();
self::$sequencenames = [];
foreach ($structure as $table => $ignored) {
$name = $DB->get_manager()->generator->getSequenceFromDB(new xmldb_table($table));
if ($name !== false) {
@ -360,13 +355,13 @@ abstract class testing_util {
$dbfamily = $DB->get_dbfamily();
if ($dbfamily === 'mysql') {
$empties = array();
$empties = [];
$prefix = $DB->get_prefix();
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", [$prefix . '%']);
foreach ($rs as $info) {
$table = strtolower($info->name);
if (strpos($table, $prefix) !== 0) {
// incorrect table match caused by _
// Incorrect table match caused by _.
continue;
}
@ -377,9 +372,8 @@ abstract class testing_util {
}
$rs->close();
return $empties;
} else if ($dbfamily === 'mssql') {
$empties = array();
$empties = [];
$prefix = $DB->get_prefix();
$sql = "SELECT t.name
FROM sys.identity_columns i
@ -387,11 +381,11 @@ abstract class testing_util {
WHERE t.name LIKE ?
AND i.name = 'id'
AND i.last_value IS NULL";
$rs = $DB->get_recordset_sql($sql, array($prefix.'%'));
$rs = $DB->get_recordset_sql($sql, [$prefix . '%']);
foreach ($rs as $info) {
$table = strtolower($info->name);
if (strpos($table, $prefix) !== 0) {
// incorrect table match caused by _
// Incorrect table match caused by _.
continue;
}
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
@ -399,13 +393,12 @@ abstract class testing_util {
}
$rs->close();
return $empties;
} else if ($dbfamily === 'oracle') {
$sequences = self::get_sequencenames();
$sequences = array_map('strtoupper', $sequences);
$lookup = array_flip($sequences);
$empties = array();
list($seqs, $params) = $DB->get_in_or_equal($sequences);
$empties = [];
[$seqs, $params] = $DB->get_in_or_equal($sequences);
$sql = "SELECT sequence_name FROM user_sequences WHERE last_number = 1 AND sequence_name $seqs";
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $seq) {
@ -414,9 +407,8 @@ abstract class testing_util {
}
$rs->close();
return $empties;
} else {
return array();
return [];
}
}
@ -475,7 +467,7 @@ abstract class testing_util {
// To reduce the chance of the coding error, we start sequences at different values where possible.
// In a attempt to avoid tables with existing id's we start at a high number.
// Reset the value each time all database sequences are reset.
if (defined('PHPUNIT_SEQUENCE_START') and PHPUNIT_SEQUENCE_START) {
if (defined('PHPUNIT_SEQUENCE_START') && PHPUNIT_SEQUENCE_START) {
self::$sequencenextstartingid = PHPUNIT_SEQUENCE_START;
} else {
self::$sequencenextstartingid = 100000;
@ -483,14 +475,14 @@ abstract class testing_util {
$dbfamily = $DB->get_dbfamily();
if ($dbfamily === 'postgres') {
$queries = array();
$queries = [];
$prefix = $DB->get_prefix();
foreach ($data as $table => $records) {
// If table is not modified then no need to do anything.
if (!isset($updatedtables[$table])) {
continue;
}
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
$nextid = self::get_next_sequence_starting_value($records, $table);
$queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
}
@ -498,16 +490,15 @@ abstract class testing_util {
if ($queries) {
$DB->change_database_structure(implode(';', $queries));
}
} else if ($dbfamily === 'mysql') {
$queries = array();
$sequences = array();
$queries = [];
$sequences = [];
$prefix = $DB->get_prefix();
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", [$prefix . '%']);
foreach ($rs as $info) {
$table = strtolower($info->name);
if (strpos($table, $prefix) !== 0) {
// incorrect table match caused by _
// Incorrect table match caused by _.
continue;
}
if (!is_null($info->auto_increment)) {
@ -522,14 +513,14 @@ abstract class testing_util {
if (!isset($updatedtables[$table])) {
continue;
}
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
if (isset($sequences[$table])) {
$nextid = self::get_next_sequence_starting_value($records, $table);
if ($sequences[$table] != $nextid) {
$queries[] = "ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid";
}
} else {
// some problem exists, fallback to standard code
// Some problem exists, fallback to standard code.
$DB->get_manager()->reset_sequence($table);
}
}
@ -537,14 +528,13 @@ abstract class testing_util {
if ($queries) {
$DB->change_database_structure(implode(';', $queries));
}
} else if ($dbfamily === 'oracle') {
$sequences = self::get_sequencenames();
$sequences = array_map('strtoupper', $sequences);
$lookup = array_flip($sequences);
$current = array();
list($seqs, $params) = $DB->get_in_or_equal($sequences);
$current = [];
[$seqs, $params] = $DB->get_in_or_equal($sequences);
$sql = "SELECT sequence_name, last_number FROM user_sequences WHERE sequence_name $seqs";
$rs = $DB->get_recordset_sql($sql, $params);
foreach ($rs as $seq) {
@ -558,7 +548,7 @@ abstract class testing_util {
if (!isset($updatedtables[$table])) {
continue;
}
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
if (isset($structure[$table]['id']) ** $structure[$table]['id']->auto_increment) {
$lastrecord = end($records);
if ($lastrecord) {
$nextid = $lastrecord->id + 1;
@ -570,26 +560,28 @@ abstract class testing_util {
} else if ($nextid == $current[$table]) {
continue;
}
// reset as fast as possible - alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle
// Reset as fast as possible.
// Alternatively we could use http://stackoverflow.com/questions/51470/how-do-i-reset-a-sequence-in-oracle.
$seqname = $sequences[$table];
$cachesize = $DB->get_manager()->generator->sequence_cache_size;
$DB->change_database_structure("DROP SEQUENCE $seqname");
$DB->change_database_structure("CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize");
$DB->change_database_structure(
"CREATE SEQUENCE $seqname START WITH $nextid INCREMENT BY 1 NOMAXVALUE CACHE $cachesize",
);
}
}
} else {
// note: does mssql support any kind of faster reset?
// Note: does mssql support any kind of faster reset?
// This also implies mssql will not use unique sequence values.
if (is_null($empties) and (empty($updatedtables))) {
if (is_null($empties) && (empty($updatedtables))) {
$empties = self::guess_unmodified_empty_tables();
}
foreach ($data as $table => $records) {
// If table is not modified then no need to do anything.
if (isset($empties[$table]) or (!isset($updatedtables[$table]))) {
if (isset($empties[$table]) || (!isset($updatedtables[$table]))) {
continue;
}
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
$DB->get_manager()->reset_sequence($table);
}
}
@ -605,21 +597,21 @@ abstract class testing_util {
global $DB;
$tables = $DB->get_tables(false);
if (!$tables or empty($tables['config'])) {
// not installed yet
if (!$tables || empty($tables['config'])) {
// Not installed yet.
return false;
}
if (!$data = self::get_tabledata()) {
// not initialised yet
// Not initialised yet.
return false;
}
if (!$structure = self::get_tablestructure()) {
// not initialised yet
// Not initialised yet.
return false;
}
$empties = array();
$empties = [];
// Use local copy of self::$tableupdated, as list gets updated in for loop.
$updatedtables = self::$tableupdated;
@ -650,13 +642,13 @@ abstract class testing_util {
if (empty($records)) {
if (!isset($empties[$table])) {
// Table has been modified and is not empty.
$DB->delete_records($table, array());
$DB->delete_records($table, []);
}
continue;
}
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
$currentrecords = $DB->get_records($table, array(), 'id ASC');
if (isset($structure[$table]['id']) && $structure[$table]['id']->auto_increment) {
$currentrecords = $DB->get_records($table, [], 'id ASC');
$changed = false;
foreach ($records as $id => $record) {
if (!isset($currentrecords[$id])) {
@ -672,7 +664,7 @@ abstract class testing_util {
if (!$changed) {
if ($currentrecords) {
$lastrecord = end($records);
$DB->delete_records_select($table, "id > ?", array($lastrecord->id));
$DB->delete_records_select($table, "id > ?", [$lastrecord->id]);
continue;
} else {
continue;
@ -680,16 +672,16 @@ abstract class testing_util {
}
}
$DB->delete_records($table, array());
$DB->delete_records($table, []);
foreach ($records as $record) {
$DB->import_record($table, $record, false, true);
}
}
// reset all next record ids - aka sequences
// Reset all next record ids - aka sequences.
self::reset_all_database_sequences($empties);
// remove extra tables
// Remove extra tables.
foreach ($tables as $table) {
if (!isset($data[$table])) {
$DB->get_manager()->drop_table(new xmldb_table($table));
@ -874,7 +866,7 @@ abstract class testing_util {
* Reset updated table list. This should be done after every reset.
*/
public static function reset_updated_table_list() {
self::$tableupdated = array();
self::$tableupdated = [];
}
/**
@ -895,7 +887,7 @@ abstract class testing_util {
* Returns the path to the file which holds list of tables updated in scenario.
* @return string
*/
protected final static function get_tables_updated_by_scenario_list_path() {
final protected static function get_tables_updated_by_scenario_list_path() {
return self::get_dataroot() . '/tablesupdatedbyscenario.json';
}
@ -909,7 +901,7 @@ abstract class testing_util {
$tables = $DB->get_tables(false);
if (isset($tables['config'])) {
// config always last to prevent problems with interrupted drops!
// Config always last to prevent problems with interrupted drops!
unset($tables['config']);
$tables['config'] = 'config';
}
@ -979,7 +971,6 @@ abstract class testing_util {
protected static function skip_original_data_files($utilclassname) {
$jsonfilepath = self::get_dataroot() . '/' . self::$originaldatafilesjson;
if (file_exists($jsonfilepath)) {
$listfiles = file_get_contents($jsonfilepath);
// Mark each files as to not be reset.
@ -987,8 +978,10 @@ abstract class testing_util {
$originaldatarootfiles = json_decode($listfiles);
// Keep the json file. Only drop_dataroot() should delete it.
$originaldatarootfiles[] = self::$originaldatafilesjson;
$utilclassname::$datarootskiponreset = array_merge($utilclassname::$datarootskiponreset,
$originaldatarootfiles);
$utilclassname::$datarootskiponreset = array_merge(
$utilclassname::$datarootskiponreset,
$originaldatarootfiles
);
self::$originaldatafilesjsonadded = true;
}
}
@ -1004,8 +997,7 @@ abstract class testing_util {
// Save the original dataroot files if not done (only executed the first time).
if (!file_exists($jsonfilepath)) {
$listfiles = array();
$listfiles = [];
$currentdir = 'filedir' . DIRECTORY_SEPARATOR . '.';
$parentdir = 'filedir' . DIRECTORY_SEPARATOR . '..';
$listfiles[$currentdir] = $currentdir;
@ -1045,7 +1037,7 @@ abstract class testing_util {
public static function get_environment() {
global $CFG, $DB;
$env = array();
$env = [];
// Add moodle version.
$release = null;