[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Advanced test case. 19 * 20 * @package core 21 * @category phpunit 22 * @copyright 2012 Petr Skoda {@link http://skodak.org} 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 27 /** 28 * Advanced PHPUnit test case customised for Moodle. 29 * 30 * @package core 31 * @category phpunit 32 * @copyright 2012 Petr Skoda {@link http://skodak.org} 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 abstract class advanced_testcase extends base_testcase { 36 /** @var bool automatically reset everything? null means log changes */ 37 private $resetAfterTest; 38 39 /** @var moodle_transaction */ 40 private $testdbtransaction; 41 42 /** @var int timestamp used for current time asserts */ 43 private $currenttimestart; 44 45 /** 46 * Constructs a test case with the given name. 47 * 48 * Note: use setUp() or setUpBeforeClass() in your test cases. 49 * 50 * @param string $name 51 * @param array $data 52 * @param string $dataName 53 */ 54 final public function __construct($name = null, array $data = array(), $dataName = '') { 55 parent::__construct($name, $data, $dataName); 56 57 $this->setBackupGlobals(false); 58 $this->setBackupStaticAttributes(false); 59 $this->setRunTestInSeparateProcess(false); 60 } 61 62 /** 63 * Runs the bare test sequence. 64 * @return void 65 */ 66 final public function runBare() { 67 global $DB; 68 69 if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) { 70 // this happens when previous test does not reset, we can not use transactions 71 $this->testdbtransaction = null; 72 73 } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') { 74 // database must allow rollback of DDL, so no mysql here 75 $this->testdbtransaction = $DB->start_delegated_transaction(); 76 } 77 78 try { 79 $this->setCurrentTimeStart(); 80 parent::runBare(); 81 // set DB reference in case somebody mocked it in test 82 $DB = phpunit_util::get_global_backup('DB'); 83 84 // Deal with any debugging messages. 85 $debugerror = phpunit_util::display_debugging_messages(); 86 $this->resetDebugging(); 87 if ($debugerror) { 88 trigger_error('Unexpected debugging() call detected.', E_USER_NOTICE); 89 } 90 91 } catch (Exception $ex) { 92 $e = $ex; 93 } catch (Throwable $ex) { 94 // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5). 95 $e = $ex; 96 } 97 98 if (isset($e)) { 99 // cleanup after failed expectation 100 self::resetAllData(); 101 throw $e; 102 } 103 104 if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) { 105 $this->testdbtransaction = null; 106 } 107 108 if ($this->resetAfterTest === true) { 109 if ($this->testdbtransaction) { 110 $DB->force_transaction_rollback(); 111 phpunit_util::reset_all_database_sequences(); 112 phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary 113 } 114 self::resetAllData(null); 115 116 } else if ($this->resetAfterTest === false) { 117 if ($this->testdbtransaction) { 118 $this->testdbtransaction->allow_commit(); 119 } 120 // keep all data untouched for other tests 121 122 } else { 123 // reset but log what changed 124 if ($this->testdbtransaction) { 125 try { 126 $this->testdbtransaction->allow_commit(); 127 } catch (dml_transaction_exception $e) { 128 self::resetAllData(); 129 throw new coding_exception('Invalid transaction state detected in test '.$this->getName()); 130 } 131 } 132 self::resetAllData(true); 133 } 134 135 // make sure test did not forget to close transaction 136 if ($DB->is_transaction_started()) { 137 self::resetAllData(); 138 if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED 139 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED 140 or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) { 141 throw new coding_exception('Test '.$this->getName().' did not close database transaction'); 142 } 143 } 144 } 145 146 /** 147 * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.) 148 * 149 * @param string $xmlFile 150 * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet 151 */ 152 protected function createFlatXMLDataSet($xmlFile) { 153 return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile); 154 } 155 156 /** 157 * Creates a new XMLDataSet with the given $xmlFile. (absolute path.) 158 * 159 * @param string $xmlFile 160 * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet 161 */ 162 protected function createXMLDataSet($xmlFile) { 163 return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile); 164 } 165 166 /** 167 * Creates a new CsvDataSet from the given array of csv files. (absolute paths.) 168 * 169 * @param array $files array tablename=>cvsfile 170 * @param string $delimiter 171 * @param string $enclosure 172 * @param string $escape 173 * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet 174 */ 175 protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') { 176 $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape); 177 foreach($files as $table=>$file) { 178 $dataSet->addTable($table, $file); 179 } 180 return $dataSet; 181 } 182 183 /** 184 * Creates new ArrayDataSet from given array 185 * 186 * @param array $data array of tables, first row in each table is columns 187 * @return phpunit_ArrayDataSet 188 */ 189 protected function createArrayDataSet(array $data) { 190 return new phpunit_ArrayDataSet($data); 191 } 192 193 /** 194 * Load date into moodle database tables from standard PHPUnit data set. 195 * 196 * Note: it is usually better to use data generators 197 * 198 * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset 199 * @return void 200 */ 201 protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) { 202 global $DB; 203 204 $structure = phpunit_util::get_tablestructure(); 205 206 foreach($dataset->getTableNames() as $tablename) { 207 $table = $dataset->getTable($tablename); 208 $metadata = $dataset->getTableMetaData($tablename); 209 $columns = $metadata->getColumns(); 210 211 $doimport = false; 212 if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) { 213 $doimport = in_array('id', $columns); 214 } 215 216 for($r=0; $r<$table->getRowCount(); $r++) { 217 $record = $table->getRow($r); 218 if ($doimport) { 219 $DB->import_record($tablename, $record); 220 } else { 221 $DB->insert_record($tablename, $record); 222 } 223 } 224 if ($doimport) { 225 $DB->get_manager()->reset_sequence(new xmldb_table($tablename)); 226 } 227 } 228 } 229 230 /** 231 * Call this method from test if you want to make sure that 232 * the resetting of database is done the slow way without transaction 233 * rollback. 234 * 235 * This is useful especially when testing stuff that is not compatible with transactions. 236 * 237 * @return void 238 */ 239 public function preventResetByRollback() { 240 if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) { 241 $this->testdbtransaction->allow_commit(); 242 $this->testdbtransaction = null; 243 } 244 } 245 246 /** 247 * Reset everything after current test. 248 * @param bool $reset true means reset state back, false means keep all data for the next test, 249 * null means reset state and show warnings if anything changed 250 * @return void 251 */ 252 public function resetAfterTest($reset = true) { 253 $this->resetAfterTest = $reset; 254 } 255 256 /** 257 * Return debugging messages from the current test. 258 * @return array with instances having 'message', 'level' and 'stacktrace' property. 259 */ 260 public function getDebuggingMessages() { 261 return phpunit_util::get_debugging_messages(); 262 } 263 264 /** 265 * Clear all previous debugging messages in current test 266 * and revert to default DEVELOPER_DEBUG level. 267 */ 268 public function resetDebugging() { 269 phpunit_util::reset_debugging(); 270 } 271 272 /** 273 * Assert that exactly debugging was just called once. 274 * 275 * Discards the debugging message if successful. 276 * 277 * @param null|string $debugmessage null means any 278 * @param null|string $debuglevel null means any 279 * @param string $message 280 */ 281 public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') { 282 $debugging = $this->getDebuggingMessages(); 283 $count = count($debugging); 284 285 if ($count == 0) { 286 if ($message === '') { 287 $message = 'Expectation failed, debugging() not triggered.'; 288 } 289 $this->fail($message); 290 } 291 if ($count > 1) { 292 if ($message === '') { 293 $message = 'Expectation failed, debugging() triggered '.$count.' times.'; 294 } 295 $this->fail($message); 296 } 297 $this->assertEquals(1, $count); 298 299 $debug = reset($debugging); 300 if ($debugmessage !== null) { 301 $this->assertSame($debugmessage, $debug->message, $message); 302 } 303 if ($debuglevel !== null) { 304 $this->assertSame($debuglevel, $debug->level, $message); 305 } 306 307 $this->resetDebugging(); 308 } 309 310 /** 311 * Asserts how many times debugging has been called. 312 * 313 * @param int $expectedcount The expected number of times 314 * @param array $debugmessages Expected debugging messages, one for each expected message. 315 * @param array $debuglevels Expected debugging levels, one for each expected message. 316 * @param string $message 317 * @return void 318 */ 319 public function assertDebuggingCalledCount($expectedcount, $debugmessages = array(), $debuglevels = array(), $message = '') { 320 if (!is_int($expectedcount)) { 321 throw new coding_exception('assertDebuggingCalledCount $expectedcount argument should be an integer.'); 322 } 323 324 $debugging = $this->getDebuggingMessages(); 325 $this->assertEquals($expectedcount, count($debugging), $message); 326 327 if ($debugmessages) { 328 if (!is_array($debugmessages) || count($debugmessages) != $expectedcount) { 329 throw new coding_exception('assertDebuggingCalledCount $debugmessages should contain ' . $expectedcount . ' messages'); 330 } 331 foreach ($debugmessages as $key => $debugmessage) { 332 $this->assertSame($debugmessage, $debugging[$key]->message, $message); 333 } 334 } 335 336 if ($debuglevels) { 337 if (!is_array($debuglevels) || count($debuglevels) != $expectedcount) { 338 throw new coding_exception('assertDebuggingCalledCount $debuglevels should contain ' . $expectedcount . ' messages'); 339 } 340 foreach ($debuglevels as $key => $debuglevel) { 341 $this->assertSame($debuglevel, $debugging[$key]->level, $message); 342 } 343 } 344 345 $this->resetDebugging(); 346 } 347 348 /** 349 * Call when no debugging() messages expected. 350 * @param string $message 351 */ 352 public function assertDebuggingNotCalled($message = '') { 353 $debugging = $this->getDebuggingMessages(); 354 $count = count($debugging); 355 356 if ($message === '') { 357 $message = 'Expectation failed, debugging() was triggered.'; 358 } 359 $this->assertEquals(0, $count, $message); 360 } 361 362 /** 363 * Assert that an event legacy data is equal to the expected value. 364 * 365 * @param mixed $expected expected data. 366 * @param \core\event\base $event the event object. 367 * @param string $message 368 * @return void 369 */ 370 public function assertEventLegacyData($expected, \core\event\base $event, $message = '') { 371 $legacydata = phpunit_event_mock::testable_get_legacy_eventdata($event); 372 if ($message === '') { 373 $message = 'Event legacy data does not match expected value.'; 374 } 375 $this->assertEquals($expected, $legacydata, $message); 376 } 377 378 /** 379 * Assert that an event legacy log data is equal to the expected value. 380 * 381 * @param mixed $expected expected data. 382 * @param \core\event\base $event the event object. 383 * @param string $message 384 * @return void 385 */ 386 public function assertEventLegacyLogData($expected, \core\event\base $event, $message = '') { 387 $legacydata = phpunit_event_mock::testable_get_legacy_logdata($event); 388 if ($message === '') { 389 $message = 'Event legacy log data does not match expected value.'; 390 } 391 $this->assertEquals($expected, $legacydata, $message); 392 } 393 394 /** 395 * Assert that an event is not using event->contxet. 396 * While restoring context might not be valid and it should not be used by event url 397 * or description methods. 398 * 399 * @param \core\event\base $event the event object. 400 * @param string $message 401 * @return void 402 */ 403 public function assertEventContextNotUsed(\core\event\base $event, $message = '') { 404 // Save current event->context and set it to false. 405 $eventcontext = phpunit_event_mock::testable_get_event_context($event); 406 phpunit_event_mock::testable_set_event_context($event, false); 407 if ($message === '') { 408 $message = 'Event should not use context property of event in any method.'; 409 } 410 411 // Test event methods should not use event->context. 412 $event->get_url(); 413 $event->get_description(); 414 $event->get_legacy_eventname(); 415 phpunit_event_mock::testable_get_legacy_eventdata($event); 416 phpunit_event_mock::testable_get_legacy_logdata($event); 417 418 // Restore event->context. 419 phpunit_event_mock::testable_set_event_context($event, $eventcontext); 420 } 421 422 /** 423 * Stores current time as the base for assertTimeCurrent(). 424 * 425 * Note: this is called automatically before calling individual test methods. 426 * @return int current time 427 */ 428 public function setCurrentTimeStart() { 429 $this->currenttimestart = time(); 430 return $this->currenttimestart; 431 } 432 433 /** 434 * Assert that: start < $time < time() 435 * @param int $time 436 * @param string $message 437 * @return void 438 */ 439 public function assertTimeCurrent($time, $message = '') { 440 $msg = ($message === '') ? 'Time is lower that allowed start value' : $message; 441 $this->assertGreaterThanOrEqual($this->currenttimestart, $time, $msg); 442 $msg = ($message === '') ? 'Time is in the future' : $message; 443 $this->assertLessThanOrEqual(time(), $time, $msg); 444 } 445 446 /** 447 * Starts message redirection. 448 * 449 * You can verify if messages were sent or not by inspecting the messages 450 * array in the returned messaging sink instance. The redirection 451 * can be stopped by calling $sink->close(); 452 * 453 * @return phpunit_message_sink 454 */ 455 public function redirectMessages() { 456 return phpunit_util::start_message_redirection(); 457 } 458 459 /** 460 * Starts email redirection. 461 * 462 * You can verify if email were sent or not by inspecting the email 463 * array in the returned phpmailer sink instance. The redirection 464 * can be stopped by calling $sink->close(); 465 * 466 * @return phpunit_message_sink 467 */ 468 public function redirectEmails() { 469 return phpunit_util::start_phpmailer_redirection(); 470 } 471 472 /** 473 * Starts event redirection. 474 * 475 * You can verify if events were triggered or not by inspecting the events 476 * array in the returned event sink instance. The redirection 477 * can be stopped by calling $sink->close(); 478 * 479 * @return phpunit_event_sink 480 */ 481 public function redirectEvents() { 482 return phpunit_util::start_event_redirection(); 483 } 484 485 /** 486 * Cleanup after all tests are executed. 487 * 488 * Note: do not forget to call this if overridden... 489 * 490 * @static 491 * @return void 492 */ 493 public static function tearDownAfterClass() { 494 self::resetAllData(); 495 } 496 497 498 /** 499 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir. 500 * 501 * @param bool $detectchanges 502 * true - changes in global state and database are reported as errors 503 * false - no errors reported 504 * null - only critical problems are reported as errors 505 * @return void 506 */ 507 public static function resetAllData($detectchanges = false) { 508 phpunit_util::reset_all_data($detectchanges); 509 } 510 511 /** 512 * Set current $USER, reset access cache. 513 * @static 514 * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid 515 * @return void 516 */ 517 public static function setUser($user = null) { 518 global $CFG, $DB; 519 520 if (is_object($user)) { 521 $user = clone($user); 522 } else if (!$user) { 523 $user = new stdClass(); 524 $user->id = 0; 525 $user->mnethostid = $CFG->mnet_localhost_id; 526 } else { 527 $user = $DB->get_record('user', array('id'=>$user)); 528 } 529 unset($user->description); 530 unset($user->access); 531 unset($user->preference); 532 533 // Enusre session is empty, as it may contain caches and user specific info. 534 \core\session\manager::init_empty_session(); 535 536 \core\session\manager::set_user($user); 537 } 538 539 /** 540 * Set current $USER to admin account, reset access cache. 541 * @static 542 * @return void 543 */ 544 public static function setAdminUser() { 545 self::setUser(2); 546 } 547 548 /** 549 * Set current $USER to guest account, reset access cache. 550 * @static 551 * @return void 552 */ 553 public static function setGuestUser() { 554 self::setUser(1); 555 } 556 557 /** 558 * Change server and default php timezones. 559 * 560 * @param string $servertimezone timezone to set in $CFG->timezone (not validated) 561 * @param string $defaultphptimezone timezone to fake default php timezone (must be valid) 562 */ 563 public static function setTimezone($servertimezone = 'Australia/Perth', $defaultphptimezone = 'Australia/Perth') { 564 global $CFG; 565 $CFG->timezone = $servertimezone; 566 core_date::phpunit_override_default_php_timezone($defaultphptimezone); 567 core_date::set_default_server_timezone(); 568 } 569 570 /** 571 * Get data generator 572 * @static 573 * @return testing_data_generator 574 */ 575 public static function getDataGenerator() { 576 return phpunit_util::get_data_generator(); 577 } 578 579 /** 580 * Returns UTL of the external test file. 581 * 582 * The result depends on the value of following constants: 583 * - TEST_EXTERNAL_FILES_HTTP_URL 584 * - TEST_EXTERNAL_FILES_HTTPS_URL 585 * 586 * They should point to standard external test files repository, 587 * it defaults to 'http://download.moodle.org/unittest'. 588 * 589 * False value means skip tests that require external files. 590 * 591 * @param string $path 592 * @param bool $https true if https required 593 * @return string url 594 */ 595 public function getExternalTestFileUrl($path, $https = false) { 596 $path = ltrim($path, '/'); 597 if ($path) { 598 $path = '/'.$path; 599 } 600 if ($https) { 601 if (defined('TEST_EXTERNAL_FILES_HTTPS_URL')) { 602 if (!TEST_EXTERNAL_FILES_HTTPS_URL) { 603 $this->markTestSkipped('Tests using external https test files are disabled'); 604 } 605 return TEST_EXTERNAL_FILES_HTTPS_URL.$path; 606 } 607 return 'https://download.moodle.org/unittest'.$path; 608 } 609 610 if (defined('TEST_EXTERNAL_FILES_HTTP_URL')) { 611 if (!TEST_EXTERNAL_FILES_HTTP_URL) { 612 $this->markTestSkipped('Tests using external http test files are disabled'); 613 } 614 return TEST_EXTERNAL_FILES_HTTP_URL.$path; 615 } 616 return 'http://download.moodle.org/unittest'.$path; 617 } 618 619 /** 620 * Recursively visit all the files in the source tree. Calls the callback 621 * function with the pathname of each file found. 622 * 623 * @param string $path the folder to start searching from. 624 * @param string $callback the method of this class to call with the name of each file found. 625 * @param string $fileregexp a regexp used to filter the search (optional). 626 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false, 627 * only files that match the regexp will be included. (default false). 628 * @param array $ignorefolders will not go into any of these folders (optional). 629 * @return void 630 */ 631 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { 632 $files = scandir($path); 633 634 foreach ($files as $file) { 635 $filepath = $path .'/'. $file; 636 if (strpos($file, '.') === 0) { 637 /// Don't check hidden files. 638 continue; 639 } else if (is_dir($filepath)) { 640 if (!in_array($filepath, $ignorefolders)) { 641 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); 642 } 643 } else if ($exclude xor preg_match($fileregexp, $filepath)) { 644 $this->$callback($filepath); 645 } 646 } 647 } 648 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |