Merge branch 'MDL-52060-master-arn' of https://github.com/andrewnicols/moodle
This commit is contained in:
@@ -404,6 +404,14 @@ abstract class moodle_database {
|
||||
case SQL_QUERY_UPDATE:
|
||||
case SQL_QUERY_STRUCTURE:
|
||||
$this->writes++;
|
||||
default:
|
||||
if ((PHPUNIT_TEST) || (defined('BEHAT_TEST') && BEHAT_TEST) ||
|
||||
defined('BEHAT_SITE_RUNNING')) {
|
||||
|
||||
// Set list of tables that are updated.
|
||||
require_once(__DIR__.'/../testing/classes/util.php');
|
||||
testing_util::set_table_modified_by_sql($sql);
|
||||
}
|
||||
}
|
||||
|
||||
$this->print_debug($sql, $params);
|
||||
@@ -577,6 +585,11 @@ abstract class moodle_database {
|
||||
protected function where_clause($table, array $conditions=null) {
|
||||
// We accept nulls in conditions
|
||||
$conditions = is_null($conditions) ? array() : $conditions;
|
||||
|
||||
if (empty($conditions)) {
|
||||
return array('', array());
|
||||
}
|
||||
|
||||
// Some checks performed under debugging only
|
||||
if (debugging()) {
|
||||
$columns = $this->get_columns($table);
|
||||
@@ -600,9 +613,6 @@ abstract class moodle_database {
|
||||
}
|
||||
|
||||
$allowed_types = $this->allowed_param_types();
|
||||
if (empty($conditions)) {
|
||||
return array('', array());
|
||||
}
|
||||
$where = array();
|
||||
$params = array();
|
||||
|
||||
|
||||
@@ -1432,9 +1432,9 @@ class core_dml_testcase extends database_driver_testcase {
|
||||
$this->assertSame('ddltablenotexist', $e->errorcode);
|
||||
}
|
||||
}
|
||||
// And without params.
|
||||
|
||||
try {
|
||||
$records = $DB->get_records('xxxx', array());
|
||||
$records = $DB->get_records('xxxx', array('id' => '1'));
|
||||
$this->fail('An Exception is missing, expected due to query against non-existing table');
|
||||
} catch (moodle_exception $e) {
|
||||
$this->assertInstanceOf('dml_exception', $e);
|
||||
@@ -5347,7 +5347,7 @@ class core_dml_testcase extends database_driver_testcase {
|
||||
|
||||
// The get_records() method generates 2 queries the first time is called
|
||||
// as it is fetching the table structure.
|
||||
$whatever = $DB->get_records($tablename);
|
||||
$whatever = $DB->get_records($tablename, array('id' => '1'));
|
||||
$this->assertEquals($initreads + 3, $DB->perf_get_reads());
|
||||
$this->assertEquals($initwrites, $DB->perf_get_writes());
|
||||
|
||||
|
||||
@@ -207,7 +207,7 @@ class phpunit_util extends testing_util {
|
||||
reset_text_filters_cache(true);
|
||||
events_get_handlers('reset');
|
||||
core_text::reset_caches();
|
||||
get_message_processors(false, true);
|
||||
get_message_processors(false, true, true);
|
||||
filter_manager::reset_caches();
|
||||
core_filetypes::reset_caches();
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
//
|
||||
// Moodle is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Moodle is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/**
|
||||
* PHPUnit Util tests
|
||||
*
|
||||
* @package core
|
||||
* @category phpunit
|
||||
* @copyright 2015 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Test util extra features.
|
||||
*
|
||||
* @package core
|
||||
* @category phpunit
|
||||
* @copyright 2015 Andrew Nicols <[email protected]>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class core_phpunit_util_testcase extends advanced_testcase {
|
||||
/**
|
||||
* @dataProvider set_table_modified_by_sql_provider
|
||||
*/
|
||||
public function test_set_table_modified_by_sql($sql, $expectations) {
|
||||
phpunit_util::reset_updated_table_list();
|
||||
phpunit_util::set_table_modified_by_sql($sql);
|
||||
foreach ($expectations as $table => $present) {
|
||||
$this->assertEquals($present, !empty(phpunit_util::$tableupdated[$table]));
|
||||
}
|
||||
}
|
||||
|
||||
public function set_table_modified_by_sql_provider() {
|
||||
global $DB;
|
||||
$prefix = $DB->get_prefix();
|
||||
|
||||
return array(
|
||||
'Basic update' => array(
|
||||
'sql' => "UPDATE {$prefix}user SET username = username || '_test'",
|
||||
'expectations' => array(
|
||||
'user' => true,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Basic update with a fieldname sharing the same prefix' => array(
|
||||
'sql' => "UPDATE {$prefix}user SET {$prefix}username = username || '_test'",
|
||||
'expectations' => array(
|
||||
'user' => true,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Basic update with a table which contains the prefix' => array(
|
||||
'sql' => "UPDATE {$prefix}user{$prefix} SET username = username || '_test'",
|
||||
'expectations' => array(
|
||||
"user{$prefix}" => true,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Update table with a numeric name' => array(
|
||||
'sql' => "UPDATE {$prefix}example42 SET username = username || '_test'",
|
||||
'expectations' => array(
|
||||
'example42' => true,
|
||||
'user' => false,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Drop basic table' => array(
|
||||
'sql' => "DROP TABLE {$prefix}user",
|
||||
'expectations' => array(
|
||||
'user' => true,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Drop table with a numeric name' => array(
|
||||
'sql' => "DROP TABLE {$prefix}example42",
|
||||
'expectations' => array(
|
||||
'example42' => true,
|
||||
'user' => false,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
'Insert in table' => array(
|
||||
'sql' => "INSERT INTO {$prefix}user (username,password) VALUES ('moodle', 'test')",
|
||||
'expectations' => array(
|
||||
'user' => true,
|
||||
'course' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+135
-32
@@ -59,6 +59,16 @@ abstract class testing_util {
|
||||
*/
|
||||
protected static $tablestructure = null;
|
||||
|
||||
/**
|
||||
* @var array keep list of sequenceid used in a table.
|
||||
*/
|
||||
private static $tablesequences = array();
|
||||
|
||||
/**
|
||||
* @var array list of updated tables.
|
||||
*/
|
||||
public static $tableupdated = array();
|
||||
|
||||
/**
|
||||
* @var array original structure of all database tables
|
||||
*/
|
||||
@@ -78,6 +88,7 @@ abstract class testing_util {
|
||||
* @var int next sequence value for a single test cycle.
|
||||
*/
|
||||
protected static $sequencenextstartingid = null;
|
||||
|
||||
/**
|
||||
* Return the name of the JSON file containing the init filenames.
|
||||
*
|
||||
@@ -263,17 +274,15 @@ abstract class testing_util {
|
||||
* @return array $table=>$records
|
||||
*/
|
||||
protected static function get_tabledata() {
|
||||
global $CFG;
|
||||
|
||||
$framework = self::get_framework();
|
||||
|
||||
$datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
|
||||
if (!file_exists($datafile)) {
|
||||
// Not initialised yet.
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!isset(self::$tabledata)) {
|
||||
$framework = self::get_framework();
|
||||
|
||||
$datafile = self::get_dataroot() . '/' . $framework . '/tabledata.ser';
|
||||
if (!file_exists($datafile)) {
|
||||
// Not initialised yet.
|
||||
return array();
|
||||
}
|
||||
|
||||
$data = file_get_contents($datafile);
|
||||
self::$tabledata = unserialize($data);
|
||||
}
|
||||
@@ -291,17 +300,15 @@ abstract class testing_util {
|
||||
* @return array $table=>$records
|
||||
*/
|
||||
public static function get_tablestructure() {
|
||||
global $CFG;
|
||||
|
||||
$framework = self::get_framework();
|
||||
|
||||
$structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
|
||||
if (!file_exists($structurefile)) {
|
||||
// Not initialised yet.
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!isset(self::$tablestructure)) {
|
||||
$framework = self::get_framework();
|
||||
|
||||
$structurefile = self::get_dataroot() . '/' . $framework . '/tablestructure.ser';
|
||||
if (!file_exists($structurefile)) {
|
||||
// Not initialised yet.
|
||||
return array();
|
||||
}
|
||||
|
||||
$data = file_get_contents($structurefile);
|
||||
self::$tablestructure = unserialize($data);
|
||||
}
|
||||
@@ -361,11 +368,10 @@ abstract class testing_util {
|
||||
// incorrect table match caused by _
|
||||
continue;
|
||||
}
|
||||
if (!is_null($info->auto_increment)) {
|
||||
|
||||
if (!is_null($info->auto_increment) && $info->rows == 0 && ($info->auto_increment == 1)) {
|
||||
$table = preg_replace('/^'.preg_quote($prefix, '/').'/', '', $table);
|
||||
if ($info->auto_increment == 1) {
|
||||
$empties[$table] = $table;
|
||||
}
|
||||
$empties[$table] = $table;
|
||||
}
|
||||
}
|
||||
$rs->close();
|
||||
@@ -418,9 +424,14 @@ abstract class testing_util {
|
||||
*
|
||||
* @static
|
||||
* @param array $records The records to use to determine the starting value for the table.
|
||||
* @param string $table table name.
|
||||
* @return int The value the sequence should be set to.
|
||||
*/
|
||||
private static function get_next_sequence_starting_value($records) {
|
||||
private static function get_next_sequence_starting_value($records, $table) {
|
||||
if (isset(self::$tablesequences[$table])) {
|
||||
return self::$tablesequences[$table];
|
||||
}
|
||||
|
||||
$id = self::$sequencenextstartingid;
|
||||
|
||||
// If there are records, calculate the minimum id we can use.
|
||||
@@ -431,6 +442,9 @@ abstract class testing_util {
|
||||
}
|
||||
|
||||
self::$sequencenextstartingid = $id + 1000;
|
||||
|
||||
self::$tablesequences[$table] = $id;
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
@@ -453,6 +467,8 @@ abstract class testing_util {
|
||||
return;
|
||||
}
|
||||
|
||||
$updatedtables = self::$tableupdated;
|
||||
|
||||
// If all starting Id's are the same, it's difficult to detect coding and testing
|
||||
// errors that use the incorrect id in tests. The classic case is cmid vs instance id.
|
||||
// To reduce the chance of the coding error, we start sequences at different values where possible.
|
||||
@@ -469,8 +485,12 @@ abstract class testing_util {
|
||||
$queries = array();
|
||||
$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) {
|
||||
$nextid = self::get_next_sequence_starting_value($records);
|
||||
$nextid = self::get_next_sequence_starting_value($records, $table);
|
||||
$queries[] = "ALTER SEQUENCE {$prefix}{$table}_id_seq RESTART WITH $nextid";
|
||||
}
|
||||
}
|
||||
@@ -479,6 +499,7 @@ abstract class testing_util {
|
||||
}
|
||||
|
||||
} else if ($dbfamily === 'mysql') {
|
||||
$queries = array();
|
||||
$sequences = array();
|
||||
$prefix = $DB->get_prefix();
|
||||
$rs = $DB->get_recordset_sql("SHOW TABLE STATUS LIKE ?", array($prefix.'%'));
|
||||
@@ -496,19 +517,25 @@ abstract class testing_util {
|
||||
$rs->close();
|
||||
$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($sequences[$table])) {
|
||||
$nextid = self::get_next_sequence_starting_value($records);
|
||||
$nextid = self::get_next_sequence_starting_value($records, $table);
|
||||
if ($sequences[$table] != $nextid) {
|
||||
$DB->change_database_structure("ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid");
|
||||
$queries[] = "ALTER TABLE {$prefix}{$table} AUTO_INCREMENT = $nextid";
|
||||
}
|
||||
|
||||
} else {
|
||||
// some problem exists, fallback to standard code
|
||||
$DB->get_manager()->reset_sequence($table);
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($queries) {
|
||||
$DB->change_database_structure(implode(';', $queries));
|
||||
}
|
||||
|
||||
} else if ($dbfamily === 'oracle') {
|
||||
$sequences = self::get_sequencenames();
|
||||
@@ -526,6 +553,10 @@ abstract class testing_util {
|
||||
$rs->close();
|
||||
|
||||
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) {
|
||||
$lastrecord = end($records);
|
||||
if ($lastrecord) {
|
||||
@@ -549,11 +580,12 @@ abstract class testing_util {
|
||||
} else {
|
||||
// note: does mssql support any kind of faster reset?
|
||||
// This also implies mssql will not use unique sequence values.
|
||||
if (is_null($empties)) {
|
||||
if (is_null($empties) and (empty($updatedtables))) {
|
||||
$empties = self::guess_unmodified_empty_tables();
|
||||
}
|
||||
foreach ($data as $table => $records) {
|
||||
if (isset($empties[$table])) {
|
||||
// If table is not modified then no need to do anything.
|
||||
if (isset($empties[$table]) or (!isset($updatedtables[$table]))) {
|
||||
continue;
|
||||
}
|
||||
if (isset($structure[$table]['id']) and $structure[$table]['id']->auto_increment) {
|
||||
@@ -586,7 +618,26 @@ abstract class testing_util {
|
||||
return false;
|
||||
}
|
||||
|
||||
$empties = self::guess_unmodified_empty_tables();
|
||||
$empties = array();
|
||||
// Use local copy of self::$tableupdated, as list gets updated in for loop.
|
||||
$updatedtables = self::$tableupdated;
|
||||
|
||||
// If empty tablesequences list then it's the very first run.
|
||||
if (empty(self::$tablesequences) && (($DB->get_dbfamily() != 'mysql') && ($DB->get_dbfamily() != 'postgres'))) {
|
||||
// Only Mysql and Postgres support random sequence, so don't guess, just reset everything on very first run.
|
||||
$empties = self::guess_unmodified_empty_tables();
|
||||
}
|
||||
|
||||
// Check if any table has been modified by behat selenium process.
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
// Crazy way to reset :(.
|
||||
$tablesupdatedfile = self::get_tables_updated_by_scenario_list_path();
|
||||
if ($tablesupdated = @json_decode(file_get_contents($tablesupdatedfile), true)) {
|
||||
self::$tableupdated = array_merge(self::$tableupdated, $tablesupdated);
|
||||
unlink($tablesupdatedfile);
|
||||
}
|
||||
$updatedtables = self::$tableupdated;
|
||||
}
|
||||
|
||||
$borkedmysql = false;
|
||||
if ($DB->get_dbfamily() === 'mysql') {
|
||||
@@ -627,6 +678,12 @@ abstract class testing_util {
|
||||
}
|
||||
|
||||
foreach ($data as $table => $records) {
|
||||
// If table is not modified then no need to do anything.
|
||||
// $updatedtables tables is set after the first run, so check before checking for specific table update.
|
||||
if (!empty($updatedtables) && !isset($updatedtables[$table])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($borkedmysql) {
|
||||
if (empty($records) and isset($empties[$table])) {
|
||||
continue;
|
||||
@@ -699,6 +756,8 @@ abstract class testing_util {
|
||||
}
|
||||
}
|
||||
|
||||
self::reset_updated_table_list();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -833,6 +892,50 @@ abstract class testing_util {
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state of modified tables.
|
||||
*
|
||||
* @param string $sql sql which is updating the table.
|
||||
*/
|
||||
public static function set_table_modified_by_sql($sql) {
|
||||
global $DB;
|
||||
|
||||
$prefix = $DB->get_prefix();
|
||||
|
||||
preg_match('/( ' . $prefix . '\w*)(.*)/', $sql, $matches);
|
||||
// Ignore random sql for testing like "XXUPDATE SET XSSD".
|
||||
if (!empty($matches[1])) {
|
||||
$table = trim($matches[1]);
|
||||
$table = preg_replace('/^' . preg_quote($prefix, '/') . '/', '', $table);
|
||||
self::$tableupdated[$table] = true;
|
||||
|
||||
if (defined('BEHAT_SITE_RUNNING')) {
|
||||
$tablesupdatedfile = self::get_tables_updated_by_scenario_list_path();
|
||||
if ($tablesupdated = @json_decode(file_get_contents($tablesupdatedfile), true)) {
|
||||
$tablesupdated[$table] = true;
|
||||
} else {
|
||||
$tablesupdated[$table] = true;
|
||||
}
|
||||
@file_put_contents($tablesupdatedfile, json_encode($tablesupdated, JSON_PRETTY_PRINT));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset updated table list. This should be done after every reset.
|
||||
*/
|
||||
public static function reset_updated_table_list() {
|
||||
self::$tableupdated = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
return self::get_dataroot() . '/tablesupdatedbyscenario.txt';
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the whole test database
|
||||
* @static
|
||||
|
||||
+6
-1
@@ -2571,14 +2571,19 @@ function message_mark_message_read($message, $timeread, $messageworkingempty=fal
|
||||
*
|
||||
* @param bool $ready only return ready-to-use processors
|
||||
* @param bool $reset Reset list of message processors (used in unit tests)
|
||||
* @param bool $resetonly Just reset, then exit
|
||||
* @return mixed $processors array of objects containing information on message processors
|
||||
*/
|
||||
function get_message_processors($ready = false, $reset = false) {
|
||||
function get_message_processors($ready = false, $reset = false, $resetonly = false) {
|
||||
global $DB, $CFG;
|
||||
|
||||
static $processors;
|
||||
if ($reset) {
|
||||
$processors = array();
|
||||
|
||||
if ($resetonly) {
|
||||
return $processors;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($processors)) {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
This files describes API changes in /message/ messaging system,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.1 ===
|
||||
* get_message_processors accepts an addition parameter for testing, which will just reset processor and exit.
|
||||
|
||||
=== 2.9 ===
|
||||
* Renderer method \core_message_renderer::manage_messagingoptions now accepts a user id parameter as well.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user