| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 <?php // $Id$ 2 /** 3 * Utility functions to make unit testing easier. 4 * 5 * These functions, particularly the the database ones, are quick and 6 * dirty methods for getting things done in test cases. None of these 7 * methods should be used outside test code. 8 * 9 * @copyright © 2006 The Open University 10 * @author T.J.Hunt@open.ac.uk 11 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 12 * @version $Id$ 13 * @package SimpleTestEx 14 */ 15 16 require_once(dirname(__FILE__) . '/../config.php'); 17 require_once($CFG->libdir . '/simpletestlib/simpletest.php'); 18 require_once($CFG->libdir . '/simpletestlib/unit_tester.php'); 19 require_once($CFG->libdir . '/simpletestlib/expectation.php'); 20 require_once($CFG->libdir . '/simpletestlib/reporter.php'); 21 require_once($CFG->libdir . '/simpletestlib/web_tester.php'); 22 require_once($CFG->libdir . '/simpletestlib/mock_objects.php'); 23 24 /** 25 * Recursively visit all the files in the source tree. Calls the callback 26 * function with the pathname of each file found. 27 * 28 * @param $path the folder to start searching from. 29 * @param $callback the function to call with the name of each file found. 30 * @param $fileregexp a regexp used to filter the search (optional). 31 * @param $exclude If true, pathnames that match the regexp will be ingored. If false, 32 * only files that match the regexp will be included. (default false). 33 * @param array $ignorefolders will not go into any of these folders (optional). 34 */ 35 function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { 36 $files = scandir($path); 37 38 foreach ($files as $file) { 39 $filepath = $path .'/'. $file; 40 if ($file == '.' || $file == '..') { 41 continue; 42 } else if (is_dir($filepath)) { 43 if (!in_array($filepath, $ignorefolders)) { 44 recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); 45 } 46 } else if ($exclude xor preg_match($fileregexp, $filepath)) { 47 call_user_func($callback, $filepath); 48 } 49 } 50 } 51 52 /** 53 * An expectation for comparing strings ignoring whitespace. 54 */ 55 class IgnoreWhitespaceExpectation extends SimpleExpectation { 56 var $expect; 57 58 function IgnoreWhitespaceExpectation($content, $message = '%s') { 59 $this->SimpleExpectation($message); 60 $this->expect=$this->normalise($content); 61 } 62 63 function test($ip) { 64 return $this->normalise($ip)==$this->expect; 65 } 66 67 function normalise($text) { 68 return preg_replace('/\s+/m',' ',trim($text)); 69 } 70 71 function testMessage($ip) { 72 return "Input string [$ip] doesn't match the required value."; 73 } 74 } 75 76 /** 77 * An Expectation that two arrays contain the same list of values. 78 */ 79 class ArraysHaveSameValuesExpectation extends SimpleExpectation { 80 var $expect; 81 82 function ArraysHaveSameValuesExpectation($expected, $message = '%s') { 83 $this->SimpleExpectation($message); 84 if (!is_array($expected)) { 85 trigger_error('Attempt to create an ArraysHaveSameValuesExpectation ' . 86 'with an expected value that is not an array.'); 87 } 88 $this->expect = $this->normalise($expected); 89 } 90 91 function test($actual) { 92 return $this->normalise($actual) == $this->expect; 93 } 94 95 function normalise($array) { 96 sort($array); 97 return $array; 98 } 99 100 function testMessage($actual) { 101 return 'Array [' . implode(', ', $actual) . 102 '] does not contain the expected list of values [' . implode(', ', $this->expect) . '].'; 103 } 104 } 105 106 /** 107 * An Expectation that compares to objects, and ensures that for every field in the 108 * expected object, there is a key of the same name in the actual object, with 109 * the same value. (The actual object may have other fields to, but we ignore them.) 110 */ 111 class CheckSpecifiedFieldsExpectation extends SimpleExpectation { 112 var $expect; 113 114 function CheckSpecifiedFieldsExpectation($expected, $message = '%s') { 115 $this->SimpleExpectation($message); 116 if (!is_object($expected)) { 117 trigger_error('Attempt to create a CheckSpecifiedFieldsExpectation ' . 118 'with an expected value that is not an object.'); 119 } 120 $this->expect = $expected; 121 } 122 123 function test($actual) { 124 foreach ($this->expect as $key => $value) { 125 if (isset($value) && isset($actual->$key) && $actual->$key == $value) { 126 // OK 127 } else if (is_null($value) && is_null($actual->$key)) { 128 // OK 129 } else { 130 return false; 131 } 132 } 133 return true; 134 } 135 136 function testMessage($actual) { 137 $mismatches = array(); 138 foreach ($this->expect as $key => $value) { 139 if (isset($value) && isset($actual->$key) && $actual->$key == $value) { 140 // OK 141 } else if (is_null($value) && is_null($actual->$key)) { 142 // OK 143 } else { 144 $mismatches[] = $key; 145 } 146 } 147 return 'Actual object does not have all the same fields with the same values as the expected object (' . 148 implode(', ', $mismatches) . ').'; 149 } 150 } 151 152 /** 153 * Given a table name, a two-dimensional array of data, and a database connection, 154 * creates a table in the database. The array of data should look something like this. 155 * 156 * $testdata = array( 157 * array('id', 'username', 'firstname', 'lastname', 'email'), 158 * array(1, 'u1', 'user', 'one', 'u1@example.com'), 159 * array(2, 'u2', 'user', 'two', 'u2@example.com'), 160 * array(3, 'u3', 'user', 'three', 'u3@example.com'), 161 * array(4, 'u4', 'user', 'four', 'u4@example.com'), 162 * array(5, 'u5', 'user', 'five', 'u5@example.com'), 163 * ); 164 * 165 * The first 'row' of the test data gives the column names. The type of each column 166 * is set to either INT or VARCHAR($strlen), guessed by inspecting the first row of 167 * data. Unless the col name is 'id' in which case the col type will be SERIAL. 168 * The remaining 'rows' of the data array are values loaded into the table. All columns 169 * are created with a default of 0xdefa or 'Default' as appropriate. 170 * 171 * This function should not be used in real code. Only for testing and debugging. 172 * 173 * @param string $tablename the name of the table to create. E.g. 'mdl_unittest_user'. 174 * @param array $data a two-dimensional array of data, in the format described above. 175 * @param object $db an AdoDB database connection. 176 * @param int $strlen the width to use for string fields. 177 */ 178 function load_test_table($tablename, $data, $db = null, $strlen = 255) { 179 $colnames = array_shift($data); 180 $coldefs = array(); 181 foreach (array_combine($colnames, $data[0]) as $colname => $value) { 182 if ($colname == 'id') { 183 $type = 'SERIAL'; 184 } else if (is_int($value)) { 185 $type = 'INTEGER DEFAULT 57082'; // 0xdefa 186 } else { 187 $type = "VARCHAR($strlen) DEFAULT 'Default'"; 188 } 189 $coldefs[] = "$colname $type"; 190 } 191 _private_execute_sql("CREATE TABLE $tablename (" . join(',', $coldefs) . ');', $db); 192 193 array_unshift($data, $colnames); 194 load_test_data($tablename, $data, $db); 195 } 196 197 /** 198 * Given a table name, a two-dimensional array of data, and a database connection, 199 * adds data to the database table. The array should have the same format as for 200 * load_test_table(), with the first 'row' giving column names. 201 * 202 * This function should not be used in real code. Only for testing and debugging. 203 * 204 * @param string $tablename the name of the table to populate. E.g. 'mdl_unittest_user'. 205 * @param array $data a two-dimensional array of data, in the format described. 206 * @param object $localdb an AdoDB database connection. 207 */ 208 function load_test_data($tablename, $data, $localdb = null) { 209 global $CFG; 210 211 if (null == $localdb) { 212 global $db; 213 $localdb = $db; 214 } 215 $colnames = array_shift($data); 216 $idcol = array_search('id', $colnames); 217 $maxid = -1; 218 foreach ($data as $row) { 219 _private_execute_sql($localdb->GetInsertSQL($tablename, array_combine($colnames, $row)), $localdb); 220 if ($idcol !== false && $row[$idcol] > $maxid) { 221 $maxid = $row[$idcol]; 222 } 223 } 224 if ($CFG->dbfamily == 'postgres' && $idcol !== false) { 225 $maxid += 1; 226 _private_execute_sql("ALTER SEQUENCE {$tablename}_id_seq RESTART WITH $maxid;", $localdb); 227 } 228 } 229 230 /** 231 * Make multiple tables that are the same as a real table but empty. 232 * 233 * This function should not be used in real code. Only for testing and debugging. 234 * 235 * @param mixed $tablename Array of strings containing the names of the table to populate (without prefix). 236 * @param string $realprefix the prefix used for real tables. E.g. 'mdl_'. 237 * @param string $testprefix the prefix used for test tables. E.g. 'mdl_unittest_'. 238 * @param object $db an AdoDB database connection. 239 */ 240 function make_test_tables_like_real_one($tablenames, $realprefix, $testprefix, $db,$dropconstraints=false) { 241 foreach($tablenames as $individual) { 242 make_test_table_like_real_one($individual,$realprefix,$testprefix,$db,$dropconstraints); 243 } 244 } 245 246 /** 247 * Make a test table that has all the same columns as a real moodle table, 248 * but which is empty. 249 * 250 * This function should not be used in real code. Only for testing and debugging. 251 * 252 * @param string $tablename Name of the table to populate. E.g. 'user'. 253 * @param string $realprefix the prefix used for real tables. E.g. 'mdl_'. 254 * @param string $testprefix the prefix used for test tables. E.g. 'mdl_unittest_'. 255 * @param object $db an AdoDB database connection. 256 */ 257 function make_test_table_like_real_one($tablename, $realprefix, $testprefix, $db, $dropconstraints=false) { 258 _private_execute_sql("CREATE TABLE $testprefix$tablename (LIKE $realprefix$tablename INCLUDING DEFAULTS);", $db); 259 if (_private_has_id_column($testprefix . $tablename, $db)) { 260 _private_execute_sql("CREATE SEQUENCE $testprefix{$tablename}_id_seq;", $db); 261 _private_execute_sql("ALTER TABLE $testprefix$tablename ALTER COLUMN id SET DEFAULT nextval('{$testprefix}{$tablename}_id_seq'::regclass);", $db); 262 _private_execute_sql("ALTER TABLE $testprefix$tablename ADD PRIMARY KEY (id);", $db); 263 } 264 if($dropconstraints) { 265 $cols=$db->MetaColumnNames($testprefix.$tablename); 266 foreach($cols as $col) { 267 $rs=_private_execute_sql( 268 "SELECT constraint_name FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WHERE table_name='$testprefix$tablename'",$db); 269 while(!$rs->EOF) { 270 $constraintname=$rs->fields['constraint_name']; 271 _private_execute_sql("ALTER TABLE $testprefix$tablename DROP CONSTRAINT $constraintname",$db); 272 $rs->MoveNext(); 273 } 274 275 _private_execute_sql("ALTER TABLE $testprefix$tablename ALTER COLUMN $col DROP NOT NULL",$db); 276 } 277 } 278 } 279 280 /** 281 * Drops a table from the database pointed to by the database connection. 282 * This undoes the create performed by load_test_table(). 283 * 284 * This function should not be used in real code. Only for testing and debugging. 285 * 286 * @param string $tablename the name of the table to populate. E.g. 'mdl_unittest_user'. 287 * @param object $db an AdoDB database connection. 288 * @param bool $cascade If true, also drop tables that depend on this one, e.g. through 289 * foreign key constraints. 290 */ 291 function remove_test_table($tablename, $db, $cascade = false) { 292 global $CFG; 293 _private_execute_sql('DROP TABLE ' . $tablename . ($cascade ? ' CASCADE' : '') . ';', $db); 294 295 if ($CFG->dbfamily == 'postgres') { 296 $rs = $db->Execute("SELECT relname FROM pg_class WHERE relname = '{$tablename}_id_seq' AND relkind = 'S';"); 297 if ($rs && !rs_EOF($rs)) { 298 _private_execute_sql("DROP SEQUENCE {$tablename}_id_seq;", $db); 299 } 300 } 301 } 302 303 /** 304 * Drops all the tables with a particular prefix from the database pointed to by the database connection. 305 * Useful for cleaning up after a unit test run has crashed leaving the DB full of junk. 306 * 307 * This function should not be used in real code. Only for testing and debugging. 308 * 309 * @param string $prefix the prfix of tables to drop 'mdl_unittest_'. 310 * @param object $db an AdoDB database connection. 311 */ 312 function wipe_tables($prefix, $db) { 313 if (strpos($prefix, 'test') === false) { 314 notice('The wipe_tables function should only be used to wipe test tables.'); 315 return; 316 } 317 $tables = $db->Metatables('TABLES', false, "$prefix%"); 318 foreach ($tables as $table) { 319 _private_execute_sql("DROP TABLE $table CASCADE", $db); 320 } 321 } 322 323 /** 324 * Drops all the sequences with a particular prefix from the database pointed to by the database connection. 325 * Useful for cleaning up after a unit test run has crashed leaving the DB full of junk. 326 * 327 * This function should not be used in real code. Only for testing and debugging. 328 * 329 * @param string $prefix the prfix of sequences to drop 'mdl_unittest_'. 330 * @param object $db an AdoDB database connection. 331 */ 332 function wipe_sequences($prefix, $db) { 333 global $CFG; 334 335 if ($CFG->dbfamily == 'postgres') { 336 $sequences = $db->GetCol("SELECT relname FROM pg_class WHERE relname LIKE '$prefix%_id_seq' AND relkind = 'S';"); 337 if ($sequences) { 338 foreach ($sequences as $sequence) { 339 _private_execute_sql("DROP SEQUENCE $sequence CASCADE", $db); 340 } 341 } 342 } 343 } 344 345 function _private_has_id_column($table, $db) { 346 return in_array('id', $db->MetaColumnNames($table)); 347 } 348 349 function _private_execute_sql($sql, $localdb = null) { 350 351 if (null == $localdb) { 352 global $db; 353 $localdb = $db; 354 } 355 if (!$rs = $localdb->Execute($sql)) { 356 echo '<p>SQL ERROR: ', $localdb->ErrorMsg(), ". STATEMENT: $sql</p>"; 357 } 358 return $rs; 359 } 360 361 /** 362 * Base class for testcases that want a different DB prefix. 363 * 364 * That is, when you need to load test data into the database for 365 * unit testing, instead of messing with the real mdl_course table, 366 * we will temporarily change $CFG->prefix from (say) mdl_ to mdl_unittest_ 367 * and create a table called mdl_unittest_course to hold the test data. 368 */ 369 class prefix_changing_test_case extends UnitTestCase { 370 var $old_prefix; 371 372 function change_prefix() { 373 global $CFG; 374 $this->old_prefix = $CFG->prefix; 375 $CFG->prefix = $CFG->prefix . 'unittest_'; 376 } 377 378 function change_prefix_back() { 379 global $CFG; 380 $CFG->prefix = $this->old_prefix; 381 } 382 383 function setUp() { 384 $this->change_prefix(); 385 } 386 387 function tearDown() { 388 $this->change_prefix_back(); 389 } 390 } 391 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |