[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/phpunit/classes/ -> advanced_testcase.php (source)

   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  }


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1