2009-05-26 03:57:03 +00:00
< ? 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/>.
2006-06-30 14:59:05 +00:00
/**
* Utility functions to make unit testing easier .
2008-09-16 12:19:43 +00:00
*
2006-06-30 14:59:05 +00:00
* These functions , particularly the the database ones , are quick and
2008-09-16 12:19:43 +00:00
* dirty methods for getting things done in test cases . None of these
2006-06-30 14:59:05 +00:00
* methods should be used outside test code .
*
2009-05-26 03:57:03 +00:00
* Major Contirbutors
* - T . J . Hunt @ open . ac . uk
*
* @ package moodlecore
* @ subpackage simpletestex
2006-06-30 14:59:05 +00:00
* @ copyright & copy ; 2006 The Open University
2009-05-26 03:57:03 +00:00
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2006-06-30 14:59:05 +00:00
*/
2009-05-26 03:57:03 +00:00
/**
* Includes
*/
2006-06-30 14:59:05 +00:00
require_once ( dirname ( __FILE__ ) . '/../config.php' );
require_once ( $CFG -> libdir . '/simpletestlib/simpletest.php' );
require_once ( $CFG -> libdir . '/simpletestlib/unit_tester.php' );
require_once ( $CFG -> libdir . '/simpletestlib/expectation.php' );
2007-03-01 02:35:13 +00:00
require_once ( $CFG -> libdir . '/simpletestlib/reporter.php' );
2007-03-06 05:05:45 +00:00
require_once ( $CFG -> libdir . '/simpletestlib/web_tester.php' );
2007-10-11 09:15:57 +00:00
require_once ( $CFG -> libdir . '/simpletestlib/mock_objects.php' );
2006-06-30 14:59:05 +00:00
/**
* Recursively visit all the files in the source tree . Calls the callback
2008-09-16 12:19:43 +00:00
* function with the pathname of each file found .
*
* @ param $path the folder to start searching from .
2006-06-30 14:59:05 +00:00
* @ param $callback the function to call with the name of each file found .
* @ param $fileregexp a regexp used to filter the search ( optional ) .
2008-09-16 12:19:43 +00:00
* @ param $exclude If true , pathnames that match the regexp will be ingored . If false ,
2006-06-30 14:59:05 +00:00
* only files that match the regexp will be included . ( default false ) .
* @ param array $ignorefolders will not go into any of these folders ( optional ) .
2008-09-16 12:19:43 +00:00
*/
2006-06-30 14:59:05 +00:00
function recurseFolders ( $path , $callback , $fileregexp = '/.*/' , $exclude = false , $ignorefolders = array ()) {
$files = scandir ( $path );
foreach ( $files as $file ) {
$filepath = $path . '/' . $file ;
2009-03-10 07:53:42 +00:00
if ( strpos ( $file , '.' ) === 0 ) {
/// Don't check hidden files.
2006-06-30 14:59:05 +00:00
continue ;
} else if ( is_dir ( $filepath )) {
if ( ! in_array ( $filepath , $ignorefolders )) {
recurseFolders ( $filepath , $callback , $fileregexp , $exclude , $ignorefolders );
}
} else if ( $exclude xor preg_match ( $fileregexp , $filepath )) {
call_user_func ( $callback , $filepath );
}
}
}
/**
* An expectation for comparing strings ignoring whitespace .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2006-06-30 14:59:05 +00:00
*/
class IgnoreWhitespaceExpectation extends SimpleExpectation {
var $expect ;
function IgnoreWhitespaceExpectation ( $content , $message = '%s' ) {
$this -> SimpleExpectation ( $message );
$this -> expect = $this -> normalise ( $content );
}
function test ( $ip ) {
return $this -> normalise ( $ip ) == $this -> expect ;
}
function normalise ( $text ) {
return preg_replace ( '/\s+/m' , ' ' , trim ( $text ));
}
function testMessage ( $ip ) {
return " Input string [ $ip ] doesn't match the required value. " ;
}
}
/**
* An Expectation that two arrays contain the same list of values .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2006-06-30 14:59:05 +00:00
*/
class ArraysHaveSameValuesExpectation extends SimpleExpectation {
var $expect ;
function ArraysHaveSameValuesExpectation ( $expected , $message = '%s' ) {
$this -> SimpleExpectation ( $message );
if ( ! is_array ( $expected )) {
trigger_error ( 'Attempt to create an ArraysHaveSameValuesExpectation ' .
'with an expected value that is not an array.' );
}
$this -> expect = $this -> normalise ( $expected );
}
function test ( $actual ) {
return $this -> normalise ( $actual ) == $this -> expect ;
}
function normalise ( $array ) {
sort ( $array );
return $array ;
}
function testMessage ( $actual ) {
return 'Array [' . implode ( ', ' , $actual ) .
'] does not contain the expected list of values [' . implode ( ', ' , $this -> expect ) . '].' ;
}
}
2009-06-23 10:41:22 +00:00
2006-06-30 14:59:05 +00:00
/**
* An Expectation that compares to objects , and ensures that for every field in the
* expected object , there is a key of the same name in the actual object , with
* the same value . ( The actual object may have other fields to , but we ignore them . )
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2006-06-30 14:59:05 +00:00
*/
class CheckSpecifiedFieldsExpectation extends SimpleExpectation {
var $expect ;
function CheckSpecifiedFieldsExpectation ( $expected , $message = '%s' ) {
$this -> SimpleExpectation ( $message );
if ( ! is_object ( $expected )) {
trigger_error ( 'Attempt to create a CheckSpecifiedFieldsExpectation ' .
'with an expected value that is not an object.' );
}
$this -> expect = $expected ;
}
function test ( $actual ) {
foreach ( $this -> expect as $key => $value ) {
if ( isset ( $value ) && isset ( $actual -> $key ) && $actual -> $key == $value ) {
// OK
} else if ( is_null ( $value ) && is_null ( $actual -> $key )) {
// OK
} else {
return false ;
}
}
return true ;
}
function testMessage ( $actual ) {
$mismatches = array ();
foreach ( $this -> expect as $key => $value ) {
if ( isset ( $value ) && isset ( $actual -> $key ) && $actual -> $key == $value ) {
// OK
} else if ( is_null ( $value ) && is_null ( $actual -> $key )) {
// OK
} else {
2009-04-10 09:32:53 +00:00
$mismatches [] = $key . ' (expected [' . $value . '] got [' . $actual -> $key . '].' ;
2006-06-30 14:59:05 +00:00
}
}
return 'Actual object does not have all the same fields with the same values as the expected object (' .
implode ( ', ' , $mismatches ) . ').' ;
}
}
2009-09-30 14:52:12 +00:00
abstract class XMLStructureExpectation extends SimpleExpectation {
/**
* Parse a string as XML and return a DOMDocument ;
* @ param $html
* @ return unknown_type
*/
protected function load_xml ( $html ) {
$prevsetting = libxml_use_internal_errors ( true );
$parser = new DOMDocument ();
2009-09-30 16:54:49 +00:00
if ( ! $parser -> loadXML ( '<html>' . $html . '</html>' )) {
2009-09-30 14:52:12 +00:00
$parser = new DOMDocument ();
}
2009-09-30 17:01:05 +00:00
libxml_clear_errors ();
libxml_use_internal_errors ( $prevsetting );
2009-09-30 14:52:12 +00:00
return $parser ;
}
}
2009-06-23 10:41:22 +00:00
/**
* An Expectation that looks to see whether some HMTL contains a tag with a certain attribute .
*
* @ copyright 2009 Tim Hunt
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2009-09-30 14:52:12 +00:00
class ContainsTagWithAttribute extends XMLStructureExpectation {
2009-06-23 10:41:22 +00:00
protected $tag ;
protected $attribute ;
protected $value ;
function __construct ( $tag , $attribute , $value , $message = '%s' ) {
2009-09-30 14:52:12 +00:00
parent :: __construct ( $message );
2009-06-23 10:41:22 +00:00
$this -> tag = $tag ;
$this -> attribute = $attribute ;
$this -> value = $value ;
}
function test ( $html ) {
2009-09-30 14:52:12 +00:00
$parser = $this -> load_xml ( $html );
2009-07-22 04:21:13 +00:00
$list = $parser -> getElementsByTagName ( $this -> tag );
2009-11-01 11:31:16 +00:00
2009-07-22 04:21:13 +00:00
foreach ( $list as $node ) {
if ( $node -> attributes -> getNamedItem ( $this -> attribute ) -> nodeValue == $this -> value ) {
return true ;
}
}
return false ;
2009-06-23 10:41:22 +00:00
}
function testMessage ( $html ) {
return 'Content [' . $html . '] does not contain the tag [' .
$this -> tag . '] with attribute [' . $this -> attribute . '="' . $this -> value . '"].' ;
}
}
2009-07-22 04:21:13 +00:00
/**
* An Expectation that looks to see whether some HMTL contains a tag with an array of attributes .
* All attributes must be present and their values must match the expected values .
2009-07-27 10:33:00 +00:00
* A third parameter can be used to specify attribute => value pairs which must not be present in a positive match .
2009-07-22 04:21:13 +00:00
*
* @ copyright 2009 Nicolas Connault
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2009-09-30 14:52:12 +00:00
class ContainsTagWithAttributes extends XMLStructureExpectation {
2009-07-27 10:33:00 +00:00
/**
* @ var string $tag The name of the Tag to search
*/
2009-07-22 04:21:13 +00:00
protected $tag ;
2009-07-27 10:33:00 +00:00
/**
* @ var array $expectedvalues An associative array of parameters , all of which must be matched
*/
protected $expectedvalues = array ();
/**
* @ var array $forbiddenvalues An associative array of parameters , none of which must be matched
*/
protected $forbiddenvalues = array ();
/**
* @ var string $failurereason The reason why the test failed : nomatch or forbiddenmatch
*/
protected $failurereason = 'nomatch' ;
function __construct ( $tag , $expectedvalues , $forbiddenvalues = array (), $message = '%s' ) {
2009-09-30 14:52:12 +00:00
parent :: __construct ( $message );
2009-07-22 04:21:13 +00:00
$this -> tag = $tag ;
2009-07-27 10:33:00 +00:00
$this -> expectedvalues = $expectedvalues ;
$this -> forbiddenvalues = $forbiddenvalues ;
2009-07-22 04:21:13 +00:00
}
2009-07-27 10:33:00 +00:00
2009-07-22 04:21:13 +00:00
function test ( $html ) {
2009-09-30 14:52:12 +00:00
$parser = $this -> load_xml ( $html );
2009-07-22 04:21:13 +00:00
$list = $parser -> getElementsByTagName ( $this -> tag );
2009-07-27 10:33:00 +00:00
$foundamatch = false ;
2009-07-22 04:21:13 +00:00
// Iterating through inputs
foreach ( $list as $node ) {
if ( empty ( $node -> attributes ) || ! is_a ( $node -> attributes , 'DOMNamedNodeMap' )) {
continue ;
}
2009-07-27 10:33:00 +00:00
// For the current expected attribute under consideration, check that values match
$allattributesmatch = true ;
2009-07-22 04:21:13 +00:00
2009-07-27 10:33:00 +00:00
foreach ( $this -> expectedvalues as $expectedattribute => $expectedvalue ) {
2009-07-29 01:23:47 +00:00
if ( ! $node -> getAttribute ( $expectedattribute ) && $expectedvalue != '' ) {
2009-07-27 10:33:00 +00:00
$this -> failurereason = 'nomatch' ;
continue 2 ; // Skip this tag, it doesn't have all the expected attributes
2009-07-22 04:21:13 +00:00
}
2009-07-27 10:33:00 +00:00
if ( $node -> getAttribute ( $expectedattribute ) != $expectedvalue ) {
$allattributesmatch = false ;
$this -> failurereason = 'nomatch' ;
2009-07-22 04:21:13 +00:00
}
}
2009-07-27 10:33:00 +00:00
if ( $allattributesmatch ) {
$foundamatch = true ;
// Now make sure this node doesn't have any of the forbidden attributes either
$nodeattrlist = $node -> attributes ;
foreach ( $nodeattrlist as $domattrname => $domattr ) {
if ( array_key_exists ( $domattrname , $this -> forbiddenvalues ) && $node -> getAttribute ( $domattrname ) == $this -> forbiddenvalues [ $domattrname ]) {
$this -> failurereason = " forbiddenmatch: $domattrname : " . $node -> getAttribute ( $domattrname );
$foundamatch = false ;
}
}
2009-07-22 04:21:13 +00:00
}
}
2009-07-27 10:33:00 +00:00
return $foundamatch ;
2009-07-22 04:21:13 +00:00
}
2009-07-27 10:33:00 +00:00
2009-07-22 04:21:13 +00:00
function testMessage ( $html ) {
2009-07-27 10:33:00 +00:00
$output = 'Content [' . $html . '] ' ;
if ( preg_match ( '/forbiddenmatch:(.*):(.*)/' , $this -> failurereason , $matches )) {
$output .= " contains the tag $this->tag with the forbidden attribute=>value pair: [ $matches[1] => $matches[2] ] " ;
} else if ( $this -> failurereason == 'nomatch' ) {
$output .= 'does not contain the tag [' . $this -> tag . '] with attributes [' ;
foreach ( $this -> expectedvalues as $var => $val ) {
$output .= " $var = \" $val\ " " ;
}
$output = rtrim ( $output );
$output .= '].' ;
2009-07-22 04:21:13 +00:00
}
2009-07-27 10:33:00 +00:00
2009-07-22 04:21:13 +00:00
return $output ;
}
}
2009-06-23 10:41:22 +00:00
/**
* An Expectation that looks to see whether some HMTL contains a tag with a certain text inside it .
*
* @ copyright 2009 Tim Hunt
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2009-09-30 14:52:12 +00:00
class ContainsTagWithContents extends XMLStructureExpectation {
2009-06-23 10:41:22 +00:00
protected $tag ;
protected $content ;
function __construct ( $tag , $content , $message = '%s' ) {
2009-09-30 14:52:12 +00:00
parent :: __construct ( $message );
2009-06-23 10:41:22 +00:00
$this -> tag = $tag ;
$this -> content = $content ;
}
function test ( $html ) {
2009-09-30 14:52:12 +00:00
$parser = $this -> load_xml ( $html );
2009-07-22 04:21:13 +00:00
$list = $parser -> getElementsByTagName ( $this -> tag );
foreach ( $list as $node ) {
if ( $node -> textContent == $this -> content ) {
return true ;
}
}
2009-11-01 11:31:16 +00:00
2009-07-22 04:21:13 +00:00
return false ;
2009-06-23 10:41:22 +00:00
}
function testMessage ( $html ) {
return 'Content [' . $html . '] does not contain the tag [' .
$this -> tag . '] with contents [' . $this -> content . '].' ;
2009-07-22 04:21:13 +00:00
}
}
/**
* An Expectation that looks to see whether some HMTL contains an empty tag of a specific type .
*
* @ copyright 2009 Nicolas Connault
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2009-09-30 14:52:12 +00:00
class ContainsEmptyTag extends XMLStructureExpectation {
2009-07-22 04:21:13 +00:00
protected $tag ;
function __construct ( $tag , $message = '%s' ) {
2009-09-30 14:52:12 +00:00
parent :: __construct ( $message );
2009-07-22 04:21:13 +00:00
$this -> tag = $tag ;
}
function test ( $html ) {
2009-09-30 14:52:12 +00:00
$parser = $this -> load_xml ( $html );
2009-07-22 04:21:13 +00:00
$list = $parser -> getElementsByTagName ( $this -> tag );
foreach ( $list as $node ) {
if ( ! $node -> hasAttributes () && ! $node -> hasChildNodes ()) {
return true ;
}
}
2009-11-01 11:31:16 +00:00
2009-07-22 04:21:13 +00:00
return false ;
}
function testMessage ( $html ) {
return 'Content [' . $html . '] does not contain the empty tag [' . $this -> tag . '].' ;
2009-06-23 10:41:22 +00:00
}
}
2009-03-23 04:12:37 +00:00
/**
* This class lets you write unit tests that access a separate set of test
* tables with a different prefix . Only those tables you explicitly ask to
* be created will be .
2009-03-30 07:05:29 +00:00
*
* This class has failities for flipping $USER -> id .
*
* The tear - down method for this class should automatically revert any changes
* you make during test set - up using the metods defined here . That is , it will
* drop tables for you automatically and revert to the real $DB and $USER -> id .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2009-03-23 04:12:37 +00:00
*/
class UnitTestCaseUsingDatabase extends UnitTestCase {
private $realdb ;
protected $testdb ;
2009-03-23 08:15:21 +00:00
private $realuserid = null ;
2009-03-23 04:12:37 +00:00
private $tables = array ();
public function __construct ( $label = false ) {
global $DB , $CFG ;
2009-03-23 08:15:21 +00:00
// Complain if we get this far and $CFG->unittestprefix is not set.
2009-03-23 04:12:37 +00:00
if ( empty ( $CFG -> unittestprefix )) {
throw new coding_exception ( 'You cannot use UnitTestCaseUsingDatabase unless you set $CFG->unittestprefix.' );
}
2009-03-23 08:15:21 +00:00
// Only do this after the above text.
2009-03-23 04:12:37 +00:00
parent :: UnitTestCase ( $label );
2009-03-23 08:15:21 +00:00
// Create the test DB instance.
2009-03-23 04:12:37 +00:00
$this -> realdb = $DB ;
$this -> testdb = moodle_database :: get_driver_instance ( $CFG -> dbtype , $CFG -> dblibrary );
$this -> testdb -> connect ( $CFG -> dbhost , $CFG -> dbuser , $CFG -> dbpass , $CFG -> dbname , $CFG -> unittestprefix );
}
/**
* Switch to using the test database for all queries until further notice .
*/
protected function switch_to_test_db () {
global $DB ;
if ( $DB === $this -> testdb ) {
debugging ( 'switch_to_test_db called when the test DB was already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.' , DEBUG_DEVELOPER );
}
$DB = $this -> testdb ;
}
/**
* Revert to using the test database for all future queries .
*/
protected function revert_to_real_db () {
global $DB ;
if ( $DB !== $this -> testdb ) {
2009-04-03 08:38:30 +00:00
debugging ( 'revert_to_real_db called when the test DB was not already selected. This suggest you are doing something wrong and dangerous. Please review your code immediately.' , DEBUG_DEVELOPER );
2009-03-23 04:12:37 +00:00
}
$DB = $this -> realdb ;
}
2009-03-23 08:15:21 +00:00
/**
* Switch $USER -> id to a test value .
*
* It might be worth making this method do more robuse $USER switching in future ,
* however , this is sufficient for my needs at present .
*/
protected function switch_global_user_id ( $userid ) {
global $USER ;
if ( ! is_null ( $this -> realuserid )) {
debugging ( 'switch_global_user_id called when $USER->id was already switched to a different value. This suggest you are doing something wrong and dangerous. Please review your code immediately.' , DEBUG_DEVELOPER );
} else {
$this -> realuserid = $USER -> id ;
}
$USER -> id = $userid ;
}
/**
* Revert $USER -> id to the real value .
*/
protected function revert_global_user_id () {
global $USER ;
if ( is_null ( $this -> realuserid )) {
debugging ( 'revert_global_user_id called without switch_global_user_id having been called first. This suggest you are doing something wrong and dangerous. Please review your code immediately.' , DEBUG_DEVELOPER );
} else {
$USER -> id = $this -> realuserid ;
$this -> realuserid = null ;
}
}
2009-03-23 04:12:37 +00:00
/**
* Check that the user has not forgotten to clean anything up , and if they
* have , display a rude message and clean it up for them .
*/
2009-03-30 07:05:29 +00:00
private function automatic_clean_up () {
2009-03-23 04:12:37 +00:00
global $DB ;
2009-03-23 08:15:21 +00:00
$cleanmore = false ;
2009-03-23 04:12:37 +00:00
2009-03-30 07:05:29 +00:00
// Drop any test tables that were created.
2009-03-23 04:12:37 +00:00
foreach ( $this -> tables as $tablename => $notused ) {
$this -> drop_test_table ( $tablename );
}
2009-03-30 07:05:29 +00:00
// Switch back to the real DB if necessary.
2009-03-23 04:12:37 +00:00
if ( $DB !== $this -> realdb ) {
$this -> revert_to_real_db ();
2009-03-23 08:15:21 +00:00
$cleanmore = true ;
}
2009-03-30 07:05:29 +00:00
// revert_global_user_id if necessary.
2009-03-23 08:15:21 +00:00
if ( ! is_null ( $this -> realuserid )) {
$this -> revert_global_user_id ();
$cleanmore = true ;
}
if ( $cleanmore ) {
accesslib_clear_all_caches_for_unit_testing ();
2009-03-23 04:12:37 +00:00
}
}
public function tearDown () {
2009-03-30 07:05:29 +00:00
$this -> automatic_clean_up ();
2009-03-23 04:12:37 +00:00
parent :: tearDown ();
}
public function __destruct () {
2009-03-30 07:05:29 +00:00
// Should not be necessary thanks to tearDown, but no harm in belt and braces.
$this -> automatic_clean_up ();
2009-03-23 04:12:37 +00:00
}
/**
* Create a test table just like a real one , getting getting the definition from
* the specified install . xml file .
* @ param string $tablename the name of the test table .
* @ param string $installxmlfile the install . xml file in which this table is defined .
* $CFG -> dirroot . '/' will be prepended , and '/db/install.xml' appended ,
* so you need only specify , for example , 'mod/quiz' .
*/
protected function create_test_table ( $tablename , $installxmlfile ) {
global $CFG ;
if ( isset ( $this -> tables [ $tablename ])) {
2009-04-20 06:47:01 +00:00
debugging ( 'You are attempting to create test table ' . $tablename . ' again. It already exists. Please review your code immediately.' , DEBUG_DEVELOPER );
2009-03-23 04:12:37 +00:00
return ;
}
$dbman = $this -> testdb -> get_manager ();
$dbman -> install_one_table_from_xmldb_file ( $CFG -> dirroot . '/' . $installxmlfile . '/db/install.xml' , $tablename );
$this -> tables [ $tablename ] = 1 ;
}
/**
* Convenience method for calling create_test_table repeatedly .
* @ param array $tablenames an array of table names .
* @ param string $installxmlfile the install . xml file in which this table is defined .
* $CFG -> dirroot . '/' will be prepended , and '/db/install.xml' appended ,
* so you need only specify , for example , 'mod/quiz' .
*/
protected function create_test_tables ( $tablenames , $installxmlfile ) {
foreach ( $tablenames as $tablename ) {
$this -> create_test_table ( $tablename , $installxmlfile );
}
}
/**
* Drop a test table .
* @ param $tablename the name of the test table .
*/
protected function drop_test_table ( $tablename ) {
if ( ! isset ( $this -> tables [ $tablename ])) {
debugging ( 'You are attempting to drop test table ' . $tablename . ' but it does not exist. Please review your code immediately.' , DEBUG_DEVELOPER );
return ;
}
$dbman = $this -> testdb -> get_manager ();
$table = new xmldb_table ( $tablename );
$dbman -> drop_table ( $table );
unset ( $this -> tables [ $tablename ]);
}
/**
* Convenience method for calling drop_test_table repeatedly .
* @ param array $tablenames an array of table names .
*/
protected function drop_test_tables ( $tablenames ) {
foreach ( $tablenames as $tablename ) {
$this -> drop_test_table ( $tablename );
}
}
/**
* Load a table with some rows of data . A typical call would look like :
*
* $config = $this -> load_test_data ( 'config_plugins' ,
* array ( 'plugin' , 'name' , 'value' ), array (
* array ( 'frog' , 'numlegs' , 2 ),
* array ( 'frog' , 'sound' , 'croak' ),
* array ( 'frog' , 'action' , 'jump' ),
* ));
*
* @ param string $table the table name .
* @ param array $cols the columns to fill .
* @ param array $data the data to load .
* @ return array $objects corresponding to $data .
*/
protected function load_test_data ( $table , array $cols , array $data ) {
$results = array ();
foreach ( $data as $rowid => $row ) {
$obj = new stdClass ;
foreach ( $cols as $key => $colname ) {
$obj -> $colname = $row [ $key ];
}
$obj -> id = $this -> testdb -> insert_record ( $table , $obj );
$results [ $rowid ] = $obj ;
}
return $results ;
}
/**
* Clean up data loaded with load_test_data . The call corresponding to the
* example load above would be :
*
* $this -> delete_test_data ( 'config_plugins' , $config );
*
* @ param string $table the table name .
* @ param array $rows the rows to delete . Actually , only $rows [ $key ] -> id is used .
*/
protected function delete_test_data ( $table , array $rows ) {
$ids = array ();
foreach ( $rows as $row ) {
$ids [] = $row -> id ;
}
$this -> testdb -> delete_records_list ( $table , 'id' , $ids );
}
}
2009-06-23 10:41:22 +00:00
2009-05-26 03:57:03 +00:00
/**
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
*/
2009-01-10 13:47:26 +00:00
class FakeDBUnitTestCase extends UnitTestCase {
2008-09-16 16:40:15 +00:00
public $tables = array ();
2008-09-18 10:05:20 +00:00
public $pkfile ;
public $cfg ;
2008-09-22 07:06:08 +00:00
public $DB ;
2008-09-16 16:40:15 +00:00
2008-09-18 10:05:20 +00:00
/**
* In the constructor , record the max ( id ) of each test table into a csv file .
* If this file already exists , it means that a previous run of unit tests
* did not complete , and has left data undeleted in the DB . This data is then
* deleted and the file is retained . Otherwise it is created .
2009-05-26 03:57:03 +00:00
*
* throws moodle_exception if CSV file cannot be created
2008-09-18 10:05:20 +00:00
*/
2008-09-16 12:19:43 +00:00
public function __construct ( $label = false ) {
2009-01-10 16:06:53 +00:00
global $DB , $CFG ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-16 12:19:43 +00:00
parent :: UnitTestCase ( $label );
2008-09-18 10:05:20 +00:00
// MDL-16483 Get PKs and save data to text file
2009-01-10 16:06:53 +00:00
2008-09-18 10:05:20 +00:00
$this -> pkfile = $CFG -> dataroot . '/testtablespks.csv' ;
$this -> cfg = $CFG ;
2008-09-22 07:06:08 +00:00
UnitTestDB :: instantiate ();
2008-09-18 10:05:20 +00:00
$tables = $DB -> get_tables ();
// The file exists, so use it to truncate tables (tests aborted before test data could be removed)
if ( file_exists ( $this -> pkfile )) {
$this -> truncate_test_tables ( $this -> get_table_data ( $this -> pkfile ));
} else { // Create the file
$tabledata = '' ;
foreach ( $tables as $table ) {
2009-01-10 13:47:26 +00:00
if ( $table != 'sessions' ) {
2008-09-18 10:05:20 +00:00
if ( ! $max_id = $DB -> get_field_sql ( " SELECT MAX(id) FROM { $CFG -> unittestprefix } { $table } " )) {
$max_id = 0 ;
}
$tabledata .= " $table , $max_id\n " ;
}
}
if ( ! file_put_contents ( $this -> pkfile , $tabledata )) {
2008-09-19 14:28:22 +00:00
$a = new stdClass ();
$a -> filename = $this -> pkfile ;
throw new moodle_exception ( 'testtablescsvfileunwritable' , 'simpletest' , '' , $a );
2008-09-18 10:05:20 +00:00
}
}
}
/**
* Given an array of tables and their max id , truncates all test table records whose id is higher than the ones in the $tabledata array .
* @ param array $tabledata
*/
private function truncate_test_tables ( $tabledata ) {
global $CFG , $DB ;
2009-01-10 16:06:53 +00:00
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-18 10:05:20 +00:00
$tables = $DB -> get_tables ();
foreach ( $tables as $table ) {
2009-01-14 17:08:29 +00:00
if ( $table != 'sessions' && isset ( $tabledata [ $table ])) {
2008-09-19 14:28:22 +00:00
// $DB->delete_records_select($table, "id > ?", array($tabledata[$table]));
2008-09-18 10:05:20 +00:00
}
}
2008-09-16 16:40:15 +00:00
}
2008-09-18 10:05:20 +00:00
/**
* Given a filename , opens it and parses the csv contained therein . It expects two fields per line :
* 1. Table name
* 2. Max id
2009-05-26 03:57:03 +00:00
*
* throws moodle_exception if file doesn ' t exist
*
2008-09-18 10:05:20 +00:00
* @ param string $filename
*/
public function get_table_data ( $filename ) {
2009-01-10 16:06:53 +00:00
global $CFG ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-18 10:05:20 +00:00
if ( file_exists ( $this -> pkfile )) {
$handle = fopen ( $this -> pkfile , 'r' );
$tabledata = array ();
while (( $data = fgetcsv ( $handle , 1000 , " , " )) !== false ) {
$tabledata [ $data [ 0 ]] = $data [ 1 ];
}
return $tabledata ;
} else {
2008-09-19 14:28:22 +00:00
$a = new stdClass ();
$a -> filename = $this -> pkfile ;
throw new moodle_exception ( 'testtablescsvfilemissing' , 'simpletest' , '' , $a );
2008-09-18 10:05:20 +00:00
return false ;
}
}
/**
* Method called before each test method . Replaces the real $DB with the one configured for unit tests ( different prefix , $CFG -> unittestprefix ) .
* Also detects if this config setting is properly set , and if the user table exists .
2009-05-26 03:57:03 +00:00
* @ todo Improve detection of incorrectly built DB test tables ( e . g . detect version discrepancy and offer to upgrade / rebuild )
2008-09-18 10:05:20 +00:00
*/
2008-09-16 16:40:15 +00:00
public function setUp () {
2009-01-10 16:06:53 +00:00
global $DB , $CFG ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-16 16:40:15 +00:00
parent :: setUp ();
2008-09-22 07:06:08 +00:00
$this -> DB =& $DB ;
2008-10-13 10:17:01 +00:00
ob_start ();
2008-09-19 14:28:22 +00:00
}
/**
* Method called after each test method . Doesn ' t do anything extraordinary except restore the global $DB to the real one .
*/
public function tearDown () {
2009-01-10 16:06:53 +00:00
global $DB , $CFG ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-22 07:06:08 +00:00
if ( empty ( $DB )) {
$DB = $this -> DB ;
}
2008-09-19 14:28:22 +00:00
$DB -> cleanup ();
parent :: tearDown ();
2008-09-23 05:11:35 +00:00
// Output buffering
2008-10-13 10:17:01 +00:00
if ( ob_get_length () > 0 ) {
ob_end_flush ();
}
2008-09-19 14:28:22 +00:00
}
/**
* This will execute once all the tests have been run . It should delete the text file holding info about database contents prior to the tests
* It should also detect if data is missing from the original tables .
*/
public function __destruct () {
global $CFG , $DB ;
2009-01-10 16:06:53 +00:00
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-09-19 14:28:22 +00:00
$CFG = $this -> cfg ;
$this -> tearDown ();
UnitTestDB :: restore ();
fulldelete ( $this -> pkfile );
}
2008-12-15 02:39:55 +00:00
/**
* Load a table with some rows of data . A typical call would look like :
*
* $config = $this -> load_test_data ( 'config_plugins' ,
* array ( 'plugin' , 'name' , 'value' ), array (
* array ( 'frog' , 'numlegs' , 2 ),
* array ( 'frog' , 'sound' , 'croak' ),
* array ( 'frog' , 'action' , 'jump' ),
* ));
*
* @ param string $table the table name .
* @ param array $cols the columns to fill .
* @ param array $data the data to load .
* @ return array $objects corresponding to $data .
*/
public function load_test_data ( $table , array $cols , array $data ) {
2009-01-10 16:06:53 +00:00
global $CFG , $DB ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-12-15 02:39:55 +00:00
$results = array ();
foreach ( $data as $rowid => $row ) {
$obj = new stdClass ;
foreach ( $cols as $key => $colname ) {
$obj -> $colname = $row [ $key ];
}
$obj -> id = $DB -> insert_record ( $table , $obj );
$results [ $rowid ] = $obj ;
}
return $results ;
}
/**
* Clean up data loaded with load_test_data . The call corresponding to the
* example load above would be :
*
* $this -> delete_test_data ( 'config_plugins' , $config );
*
* @ param string $table the table name .
* @ param array $rows the rows to delete . Actually , only $rows [ $key ] -> id is used .
*/
public function delete_test_data ( $table , array $rows ) {
2009-01-10 16:06:53 +00:00
global $CFG , $DB ;
if ( empty ( $CFG -> unittestprefix )) {
return ;
}
2008-12-15 02:39:55 +00:00
$ids = array ();
foreach ( $rows as $row ) {
$ids [] = $row -> id ;
}
$DB -> delete_records_list ( $table , 'id' , $ids );
}
2008-09-19 14:28:22 +00:00
}
/**
* This is a Database Engine proxy class : It replaces the global object $DB with itself through a call to the
* static instantiate () method , and restores the original global $DB through restore () .
* Internally , it routes all calls to $DB to a real instance of the database engine ( aggregated as a member variable ),
* except those that are defined in this proxy class . This makes it possible to add extra code to the database engine
* without subclassing it .
2009-05-26 03:57:03 +00:00
*
* @ package moodlecore
* @ subpackage simpletestex
* @ copyright & copy ; 2006 The Open University
* @ license http :// www . gnu . org / copyleft / gpl . html GNU GPL v3 or later
2008-09-19 14:28:22 +00:00
*/
class UnitTestDB {
public static $DB ;
private static $real_db ;
2008-09-16 16:40:15 +00:00
2008-09-19 14:28:22 +00:00
public $table_data = array ();
/**
* Call this statically to connect to the DB using the unittest prefix , instantiate
* the unit test db , store it as a member variable , instantiate $this and use it as the new global $DB .
*/
public static function instantiate () {
global $CFG , $DB ;
UnitTestDB :: $real_db = clone ( $DB );
2008-09-18 10:05:20 +00:00
if ( empty ( $CFG -> unittestprefix )) {
2008-09-16 12:19:43 +00:00
print_error ( " prefixnotset " , 'simpletest' );
}
2008-09-22 07:06:08 +00:00
if ( empty ( UnitTestDB :: $DB )) {
UnitTestDB :: $DB = moodle_database :: get_driver_instance ( $CFG -> dbtype , $CFG -> dblibrary );
2008-10-27 22:21:34 +00:00
UnitTestDB :: $DB -> connect ( $CFG -> dbhost , $CFG -> dbuser , $CFG -> dbpass , $CFG -> dbname , $CFG -> unittestprefix );
2008-09-22 07:06:08 +00:00
}
2008-09-19 14:28:22 +00:00
$manager = UnitTestDB :: $DB -> get_manager ();
2008-09-16 16:40:15 +00:00
if ( ! $manager -> table_exists ( 'user' )) {
2008-09-16 12:19:43 +00:00
print_error ( 'tablesnotsetup' , 'simpletest' );
}
2008-09-19 14:28:22 +00:00
$DB = new UnitTestDB ();
}
public function __call ( $method , $args ) {
// Set args to null if they don't exist (up to 10 args should do)
if ( ! method_exists ( $this , $method )) {
return call_user_func_array ( array ( UnitTestDB :: $DB , $method ), $args );
} else {
call_user_func_array ( array ( $this , $method ), $args );
}
}
public function __get ( $variable ) {
return UnitTestDB :: $DB -> $variable ;
}
public function __set ( $variable , $value ) {
UnitTestDB :: $DB -> $variable = $value ;
}
public function __isset ( $variable ) {
return isset ( UnitTestDB :: $DB -> $variable );
}
public function __unset ( $variable ) {
unset ( UnitTestDB :: $DB -> $variable );
2008-09-16 12:19:43 +00:00
}
2008-09-18 10:05:20 +00:00
/**
2008-09-19 14:28:22 +00:00
* Overriding insert_record to keep track of the ids inserted during unit tests , so that they can be deleted afterwards
2008-09-18 10:05:20 +00:00
*/
2008-09-19 14:28:22 +00:00
public function insert_record ( $table , $dataobject , $returnid = true , $bulk = false ) {
2008-09-16 16:40:15 +00:00
global $DB ;
2008-09-19 14:28:22 +00:00
$id = UnitTestDB :: $DB -> insert_record ( $table , $dataobject , $returnid , $bulk );
$this -> table_data [ $table ][] = $id ;
return $id ;
}
2008-09-16 16:40:15 +00:00
2008-09-19 14:28:22 +00:00
/**
* Overriding update_record : If we are updating a record that was NOT inserted by unit tests ,
* throw an exception and cancel update .
2009-11-01 11:31:16 +00:00
*
2009-05-26 03:57:03 +00:00
* throws moodle_exception If trying to update a record not inserted by unit tests .
2008-09-19 14:28:22 +00:00
*/
public function update_record ( $table , $dataobject , $bulk = false ) {
global $DB ;
2008-09-23 05:11:35 +00:00
if (( empty ( $this -> table_data [ $table ]) || ! in_array ( $dataobject -> id , $this -> table_data [ $table ])) && ! ( $table == 'course_categories' && $dataobject -> id == 1 )) {
2008-09-22 07:06:08 +00:00
// return UnitTestDB::$DB->update_record($table, $dataobject, $bulk);
$a = new stdClass ();
$a -> id = $dataobject -> id ;
$a -> table = $table ;
throw new moodle_exception ( 'updatingnoninsertedrecord' , 'simpletest' , '' , $a );
2008-09-19 14:28:22 +00:00
} else {
return UnitTestDB :: $DB -> update_record ( $table , $dataobject , $bulk );
}
2008-09-16 12:19:43 +00:00
}
/**
2008-09-19 14:28:22 +00:00
* Overriding delete_record : If we are deleting a record that was NOT inserted by unit tests ,
* throw an exception and cancel delete .
2009-05-26 03:57:03 +00:00
*
* throws moodle_exception If trying to delete a record not inserted by unit tests .
2008-09-16 12:19:43 +00:00
*/
2008-09-22 07:06:08 +00:00
public function delete_records ( $table , array $conditions = array ()) {
2008-09-19 14:28:22 +00:00
global $DB ;
2008-09-23 05:11:35 +00:00
$tables_to_ignore = array ( 'context_temp' );
2008-09-19 14:28:22 +00:00
$a = new stdClass ();
$a -> table = $table ;
// Get ids matching conditions
if ( ! $ids_to_delete = $DB -> get_field ( $table , 'id' , $conditions )) {
return UnitTestDB :: $DB -> delete_records ( $table , $conditions );
}
$proceed_with_delete = true ;
if ( ! is_array ( $ids_to_delete )) {
$ids_to_delete = array ( $ids_to_delete );
}
foreach ( $ids_to_delete as $id ) {
2008-09-23 05:11:35 +00:00
if ( ! in_array ( $table , $tables_to_ignore ) && ( empty ( $this -> table_data [ $table ]) || ! in_array ( $id , $this -> table_data [ $table ]))) {
2008-09-19 14:28:22 +00:00
$proceed_with_delete = false ;
$a -> id = $id ;
break ;
}
}
if ( $proceed_with_delete ) {
return UnitTestDB :: $DB -> delete_records ( $table , $conditions );
} else {
throw new moodle_exception ( 'deletingnoninsertedrecord' , 'simpletest' , '' , $a );
}
2008-09-16 12:19:43 +00:00
}
2008-09-19 14:28:22 +00:00
/**
* Overriding delete_records_select : If we are deleting a record that was NOT inserted by unit tests ,
* throw an exception and cancel delete .
2009-05-26 03:57:03 +00:00
*
* throws moodle_exception If trying to delete a record not inserted by unit tests .
2008-09-19 14:28:22 +00:00
*/
public function delete_records_select ( $table , $select , array $params = null ) {
global $DB ;
$a = new stdClass ();
$a -> table = $table ;
// Get ids matching conditions
if ( ! $ids_to_delete = $DB -> get_field_select ( $table , 'id' , $select , $params )) {
return UnitTestDB :: $DB -> delete_records_select ( $table , $select , $params );
}
$proceed_with_delete = true ;
foreach ( $ids_to_delete as $id ) {
if ( ! in_array ( $id , $this -> table_data [ $table ])) {
$proceed_with_delete = false ;
$a -> id = $id ;
break ;
}
}
if ( $proceed_with_delete ) {
return UnitTestDB :: $DB -> delete_records_select ( $table , $select , $params );
} else {
throw new moodle_exception ( 'deletingnoninsertedrecord' , 'simpletest' , '' , $a );
}
}
/**
* Removes from the test DB all the records that were inserted during unit tests ,
*/
public function cleanup () {
global $DB ;
foreach ( $this -> table_data as $table => $ids ) {
foreach ( $ids as $id ) {
$DB -> delete_records ( $table , array ( 'id' => $id ));
}
}
}
/**
* Restores the global $DB object .
*/
public static function restore () {
global $DB ;
$DB = UnitTestDB :: $real_db ;
}
public function get_field ( $table , $return , array $conditions ) {
if ( ! is_array ( $conditions )) {
2009-06-25 06:56:41 +00:00
throw new coding_exception ( '$conditions is not an array.' );
2008-09-19 14:28:22 +00:00
}
return UnitTestDB :: $DB -> get_field ( $table , $return , $conditions );
}
}