[ Index ]

PHP Cross Reference of moodle-2.8

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 PHPUnit_Framework_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              phpunit_util::reset_debugging();
  87              if ($debugerror) {
  88                  trigger_error('Unenxpected debugging() call detected.', E_USER_NOTICE);
  89              }
  90  
  91          } catch (Exception $e) {
  92              // cleanup after failed expectation
  93              phpunit_util::reset_all_data();
  94              throw $e;
  95          }
  96  
  97          if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) {
  98              $this->testdbtransaction = null;
  99          }
 100  
 101          if ($this->resetAfterTest === true) {
 102              if ($this->testdbtransaction) {
 103                  $DB->force_transaction_rollback();
 104                  phpunit_util::reset_all_database_sequences();
 105                  phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary
 106              }
 107              phpunit_util::reset_all_data(null);
 108  
 109          } else if ($this->resetAfterTest === false) {
 110              if ($this->testdbtransaction) {
 111                  $this->testdbtransaction->allow_commit();
 112              }
 113              // keep all data untouched for other tests
 114  
 115          } else {
 116              // reset but log what changed
 117              if ($this->testdbtransaction) {
 118                  try {
 119                      $this->testdbtransaction->allow_commit();
 120                  } catch (dml_transaction_exception $e) {
 121                      phpunit_util::reset_all_data();
 122                      throw new coding_exception('Invalid transaction state detected in test '.$this->getName());
 123                  }
 124              }
 125              phpunit_util::reset_all_data(true);
 126          }
 127  
 128          // make sure test did not forget to close transaction
 129          if ($DB->is_transaction_started()) {
 130              phpunit_util::reset_all_data();
 131              if ($this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_PASSED
 132                  or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_SKIPPED
 133                  or $this->getStatus() == PHPUnit_Runner_BaseTestRunner::STATUS_INCOMPLETE) {
 134                  throw new coding_exception('Test '.$this->getName().' did not close database transaction');
 135              }
 136          }
 137      }
 138  
 139      /**
 140       * Creates a new FlatXmlDataSet with the given $xmlFile. (absolute path.)
 141       *
 142       * @param string $xmlFile
 143       * @return PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet
 144       */
 145      protected function createFlatXMLDataSet($xmlFile) {
 146          return new PHPUnit_Extensions_Database_DataSet_FlatXmlDataSet($xmlFile);
 147      }
 148  
 149      /**
 150       * Creates a new XMLDataSet with the given $xmlFile. (absolute path.)
 151       *
 152       * @param string $xmlFile
 153       * @return PHPUnit_Extensions_Database_DataSet_XmlDataSet
 154       */
 155      protected function createXMLDataSet($xmlFile) {
 156          return new PHPUnit_Extensions_Database_DataSet_XmlDataSet($xmlFile);
 157      }
 158  
 159      /**
 160       * Creates a new CsvDataSet from the given array of csv files. (absolute paths.)
 161       *
 162       * @param array $files array tablename=>cvsfile
 163       * @param string $delimiter
 164       * @param string $enclosure
 165       * @param string $escape
 166       * @return PHPUnit_Extensions_Database_DataSet_CsvDataSet
 167       */
 168      protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') {
 169          $dataSet = new PHPUnit_Extensions_Database_DataSet_CsvDataSet($delimiter, $enclosure, $escape);
 170          foreach($files as $table=>$file) {
 171              $dataSet->addTable($table, $file);
 172          }
 173          return $dataSet;
 174      }
 175  
 176      /**
 177       * Creates new ArrayDataSet from given array
 178       *
 179       * @param array $data array of tables, first row in each table is columns
 180       * @return phpunit_ArrayDataSet
 181       */
 182      protected function createArrayDataSet(array $data) {
 183          return new phpunit_ArrayDataSet($data);
 184      }
 185  
 186      /**
 187       * Load date into moodle database tables from standard PHPUnit data set.
 188       *
 189       * Note: it is usually better to use data generators
 190       *
 191       * @param PHPUnit_Extensions_Database_DataSet_IDataSet $dataset
 192       * @return void
 193       */
 194      protected function loadDataSet(PHPUnit_Extensions_Database_DataSet_IDataSet $dataset) {
 195          global $DB;
 196  
 197          $structure = phpunit_util::get_tablestructure();
 198  
 199          foreach($dataset->getTableNames() as $tablename) {
 200              $table = $dataset->getTable($tablename);
 201              $metadata = $dataset->getTableMetaData($tablename);
 202              $columns = $metadata->getColumns();
 203  
 204              $doimport = false;
 205              if (isset($structure[$tablename]['id']) and $structure[$tablename]['id']->auto_increment) {
 206                  $doimport = in_array('id', $columns);
 207              }
 208  
 209              for($r=0; $r<$table->getRowCount(); $r++) {
 210                  $record = $table->getRow($r);
 211                  if ($doimport) {
 212                      $DB->import_record($tablename, $record);
 213                  } else {
 214                      $DB->insert_record($tablename, $record);
 215                  }
 216              }
 217              if ($doimport) {
 218                  $DB->get_manager()->reset_sequence(new xmldb_table($tablename));
 219              }
 220          }
 221      }
 222  
 223      /**
 224       * Call this method from test if you want to make sure that
 225       * the resetting of database is done the slow way without transaction
 226       * rollback.
 227       *
 228       * This is useful especially when testing stuff that is not compatible with transactions.
 229       *
 230       * @return void
 231       */
 232      public function preventResetByRollback() {
 233          if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) {
 234              $this->testdbtransaction->allow_commit();
 235              $this->testdbtransaction = null;
 236          }
 237      }
 238  
 239      /**
 240       * Reset everything after current test.
 241       * @param bool $reset true means reset state back, false means keep all data for the next test,
 242       *      null means reset state and show warnings if anything changed
 243       * @return void
 244       */
 245      public function resetAfterTest($reset = true) {
 246          $this->resetAfterTest = $reset;
 247      }
 248  
 249      /**
 250       * Return debugging messages from the current test.
 251       * @return array with instances having 'message', 'level' and 'stacktrace' property.
 252       */
 253      public function getDebuggingMessages() {
 254          return phpunit_util::get_debugging_messages();
 255      }
 256  
 257      /**
 258       * Clear all previous debugging messages in current test
 259       * and revert to default DEVELOPER_DEBUG level.
 260       */
 261      public function resetDebugging() {
 262          phpunit_util::reset_debugging();
 263      }
 264  
 265      /**
 266       * Assert that exactly debugging was just called once.
 267       *
 268       * Discards the debugging message if successful.
 269       *
 270       * @param null|string $debugmessage null means any
 271       * @param null|string $debuglevel null means any
 272       * @param string $message
 273       */
 274      public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') {
 275          $debugging = phpunit_util::get_debugging_messages();
 276          $count = count($debugging);
 277  
 278          if ($count == 0) {
 279              if ($message === '') {
 280                  $message = 'Expectation failed, debugging() not triggered.';
 281              }
 282              $this->fail($message);
 283          }
 284          if ($count > 1) {
 285              if ($message === '') {
 286                  $message = 'Expectation failed, debugging() triggered '.$count.' times.';
 287              }
 288              $this->fail($message);
 289          }
 290          $this->assertEquals(1, $count);
 291  
 292          $debug = reset($debugging);
 293          if ($debugmessage !== null) {
 294              $this->assertSame($debugmessage, $debug->message, $message);
 295          }
 296          if ($debuglevel !== null) {
 297              $this->assertSame($debuglevel, $debug->level, $message);
 298          }
 299  
 300          phpunit_util::reset_debugging();
 301      }
 302  
 303      /**
 304       * Call when no debugging() messages expected.
 305       * @param string $message
 306       */
 307      public function assertDebuggingNotCalled($message = '') {
 308          $debugging = phpunit_util::get_debugging_messages();
 309          $count = count($debugging);
 310  
 311          if ($message === '') {
 312              $message = 'Expectation failed, debugging() was triggered.';
 313          }
 314          $this->assertEquals(0, $count, $message);
 315      }
 316  
 317      /**
 318       * Assert that an event legacy data is equal to the expected value.
 319       *
 320       * @param mixed $expected expected data.
 321       * @param \core\event\base $event the event object.
 322       * @param string $message
 323       * @return void
 324       */
 325      public function assertEventLegacyData($expected, \core\event\base $event, $message = '') {
 326          $legacydata = phpunit_event_mock::testable_get_legacy_eventdata($event);
 327          if ($message === '') {
 328              $message = 'Event legacy data does not match expected value.';
 329          }
 330          $this->assertEquals($expected, $legacydata, $message);
 331      }
 332  
 333      /**
 334       * Assert that an event legacy log data is equal to the expected value.
 335       *
 336       * @param mixed $expected expected data.
 337       * @param \core\event\base $event the event object.
 338       * @param string $message
 339       * @return void
 340       */
 341      public function assertEventLegacyLogData($expected, \core\event\base $event, $message = '') {
 342          $legacydata = phpunit_event_mock::testable_get_legacy_logdata($event);
 343          if ($message === '') {
 344              $message = 'Event legacy log data does not match expected value.';
 345          }
 346          $this->assertEquals($expected, $legacydata, $message);
 347      }
 348  
 349      /**
 350       * Assert that an event is not using event->contxet.
 351       * While restoring context might not be valid and it should not be used by event url
 352       * or description methods.
 353       *
 354       * @param \core\event\base $event the event object.
 355       * @param string $message
 356       * @return void
 357       */
 358      public function assertEventContextNotUsed(\core\event\base $event, $message = '') {
 359          // Save current event->context and set it to false.
 360          $eventcontext = phpunit_event_mock::testable_get_event_context($event);
 361          phpunit_event_mock::testable_set_event_context($event, false);
 362          if ($message === '') {
 363              $message = 'Event should not use context property of event in any method.';
 364          }
 365  
 366          // Test event methods should not use event->context.
 367          $event->get_url();
 368          $event->get_description();
 369          $event->get_legacy_eventname();
 370          phpunit_event_mock::testable_get_legacy_eventdata($event);
 371          phpunit_event_mock::testable_get_legacy_logdata($event);
 372  
 373          // Restore event->context.
 374          phpunit_event_mock::testable_set_event_context($event, $eventcontext);
 375      }
 376  
 377      /**
 378       * Stores current time as the base for assertTimeCurrent().
 379       *
 380       * Note: this is called automatically before calling individual test methods.
 381       * @return int current time
 382       */
 383      public function setCurrentTimeStart() {
 384          $this->currenttimestart = time();
 385          return $this->currenttimestart;
 386      }
 387  
 388      /**
 389       * Assert that: start < $time < time()
 390       * @param int $time
 391       * @param string $message
 392       * @return void
 393       */
 394      public function assertTimeCurrent($time, $message = '') {
 395          $msg =  ($message === '') ? 'Time is lower that allowed start value' : $message;
 396          $this->assertGreaterThanOrEqual($this->currenttimestart, $time, $msg);
 397          $msg =  ($message === '') ? 'Time is in the future' : $message;
 398          $this->assertLessThanOrEqual(time(), $time, $msg);
 399      }
 400  
 401      /**
 402       * Starts message redirection.
 403       *
 404       * You can verify if messages were sent or not by inspecting the messages
 405       * array in the returned messaging sink instance. The redirection
 406       * can be stopped by calling $sink->close();
 407       *
 408       * @return phpunit_message_sink
 409       */
 410      public function redirectMessages() {
 411          return phpunit_util::start_message_redirection();
 412      }
 413  
 414      /**
 415       * Starts email redirection.
 416       *
 417       * You can verify if email were sent or not by inspecting the email
 418       * array in the returned phpmailer sink instance. The redirection
 419       * can be stopped by calling $sink->close();
 420       *
 421       * @return phpunit_message_sink
 422       */
 423      public function redirectEmails() {
 424          return phpunit_util::start_phpmailer_redirection();
 425      }
 426  
 427      /**
 428       * Starts event redirection.
 429       *
 430       * You can verify if events were triggered or not by inspecting the events
 431       * array in the returned event sink instance. The redirection
 432       * can be stopped by calling $sink->close();
 433       *
 434       * @return phpunit_event_sink
 435       */
 436      public function redirectEvents() {
 437          return phpunit_util::start_event_redirection();
 438      }
 439  
 440      /**
 441       * Cleanup after all tests are executed.
 442       *
 443       * Note: do not forget to call this if overridden...
 444       *
 445       * @static
 446       * @return void
 447       */
 448      public static function tearDownAfterClass() {
 449          phpunit_util::reset_all_data();
 450      }
 451  
 452      /**
 453       * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir.
 454       * @static
 455       * @return void
 456       */
 457      public static function resetAllData() {
 458          phpunit_util::reset_all_data();
 459      }
 460  
 461      /**
 462       * Set current $USER, reset access cache.
 463       * @static
 464       * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid
 465       * @return void
 466       */
 467      public static function setUser($user = null) {
 468          global $CFG, $DB;
 469  
 470          if (is_object($user)) {
 471              $user = clone($user);
 472          } else if (!$user) {
 473              $user = new stdClass();
 474              $user->id = 0;
 475              $user->mnethostid = $CFG->mnet_localhost_id;
 476          } else {
 477              $user = $DB->get_record('user', array('id'=>$user));
 478          }
 479          unset($user->description);
 480          unset($user->access);
 481          unset($user->preference);
 482  
 483          \core\session\manager::set_user($user);
 484      }
 485  
 486      /**
 487       * Set current $USER to admin account, reset access cache.
 488       * @static
 489       * @return void
 490       */
 491      public static function setAdminUser() {
 492          self::setUser(2);
 493      }
 494  
 495      /**
 496       * Set current $USER to guest account, reset access cache.
 497       * @static
 498       * @return void
 499       */
 500      public static function setGuestUser() {
 501          self::setUser(1);
 502      }
 503  
 504      /**
 505       * Get data generator
 506       * @static
 507       * @return testing_data_generator
 508       */
 509      public static function getDataGenerator() {
 510          return phpunit_util::get_data_generator();
 511      }
 512  
 513      /**
 514       * Returns UTL of the external test file.
 515       *
 516       * The result depends on the value of following constants:
 517       *  - TEST_EXTERNAL_FILES_HTTP_URL
 518       *  - TEST_EXTERNAL_FILES_HTTPS_URL
 519       *
 520       * They should point to standard external test files repository,
 521       * it defaults to 'http://download.moodle.org/unittest'.
 522       *
 523       * False value means skip tests that require external files.
 524       *
 525       * @param string $path
 526       * @param bool $https true if https required
 527       * @return string url
 528       */
 529      public function getExternalTestFileUrl($path, $https = false) {
 530          $path = ltrim($path, '/');
 531          if ($path) {
 532              $path = '/'.$path;
 533          }
 534          if ($https) {
 535              if (defined('TEST_EXTERNAL_FILES_HTTPS_URL')) {
 536                  if (!TEST_EXTERNAL_FILES_HTTPS_URL) {
 537                      $this->markTestSkipped('Tests using external https test files are disabled');
 538                  }
 539                  return TEST_EXTERNAL_FILES_HTTPS_URL.$path;
 540              }
 541              return 'https://download.moodle.org/unittest'.$path;
 542          }
 543  
 544          if (defined('TEST_EXTERNAL_FILES_HTTP_URL')) {
 545              if (!TEST_EXTERNAL_FILES_HTTP_URL) {
 546                  $this->markTestSkipped('Tests using external http test files are disabled');
 547              }
 548              return TEST_EXTERNAL_FILES_HTTP_URL.$path;
 549          }
 550          return 'http://download.moodle.org/unittest'.$path;
 551      }
 552  
 553      /**
 554       * Recursively visit all the files in the source tree. Calls the callback
 555       * function with the pathname of each file found.
 556       *
 557       * @param string $path the folder to start searching from.
 558       * @param string $callback the method of this class to call with the name of each file found.
 559       * @param string $fileregexp a regexp used to filter the search (optional).
 560       * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false,
 561       *     only files that match the regexp will be included. (default false).
 562       * @param array $ignorefolders will not go into any of these folders (optional).
 563       * @return void
 564       */
 565      public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) {
 566          $files = scandir($path);
 567  
 568          foreach ($files as $file) {
 569              $filepath = $path .'/'. $file;
 570              if (strpos($file, '.') === 0) {
 571                  /// Don't check hidden files.
 572                  continue;
 573              } else if (is_dir($filepath)) {
 574                  if (!in_array($filepath, $ignorefolders)) {
 575                      $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders);
 576                  }
 577              } else if ($exclude xor preg_match($fileregexp, $filepath)) {
 578                  $this->$callback($filepath);
 579              }
 580          }
 581      }
 582  }


Generated: Fri Nov 28 20:29:05 2014 Cross-referenced by PHPXref 0.7.1