MediaWiki  master
MediaWikiTestCase.php
Go to the documentation of this file.
1 <?php
6 use Psr\Log\LoggerInterface;
7 
11 abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
12 
20  private static $serviceLocator = null;
21 
34  private $called = [];
35 
40  public static $users;
41 
48  protected $db;
49 
54  protected $tablesUsed = []; // tables with data
55 
56  private static $useTemporaryTables = true;
57  private static $reuseDB = false;
58  private static $dbSetup = false;
59  private static $oldTablePrefix = false;
60 
66  private $phpErrorLevel;
67 
74  private $tmpFiles = [];
75 
82  private $mwGlobals = [];
83 
88  private $loggers = [];
89 
93  const DB_PREFIX = 'unittest_';
94  const ORA_DB_PREFIX = 'ut_';
95 
100  protected $supportedDBs = [
101  'mysql',
102  'sqlite',
103  'postgres',
104  'oracle'
105  ];
106 
107  public function __construct( $name = null, array $data = [], $dataName = '' ) {
108  parent::__construct( $name, $data, $dataName );
109 
110  $this->backupGlobals = false;
111  $this->backupStaticAttributes = false;
112  }
113 
114  public function __destruct() {
115  // Complain if self::setUp() was called, but not self::tearDown()
116  // $this->called['setUp'] will be checked by self::testMediaWikiTestCaseParentSetupCalled()
117  if ( isset( $this->called['setUp'] ) && !isset( $this->called['tearDown'] ) ) {
118  throw new MWException( static::class . "::tearDown() must call parent::tearDown()" );
119  }
120  }
121 
122  public static function setUpBeforeClass() {
123  parent::setUpBeforeClass();
124 
125  // NOTE: Usually, PHPUnitMaintClass::finalSetup already called this,
126  // but let's make doubly sure.
127  self::prepareServices( new GlobalVarConfig() );
128  }
129 
138  public static function getTestUser( $groups = [] ) {
139  return TestUserRegistry::getImmutableTestUser( $groups );
140  }
141 
150  public static function getMutableTestUser( $groups = [] ) {
151  return TestUserRegistry::getMutableTestUser( __CLASS__, $groups );
152  }
153 
162  public static function getTestSysop() {
163  return self::getTestUser( [ 'sysop', 'bureaucrat' ] );
164  }
165 
184  public static function prepareServices( Config $bootstrapConfig ) {
185  static $servicesPrepared = false;
186 
187  if ( $servicesPrepared ) {
188  return;
189  } else {
190  $servicesPrepared = true;
191  }
192 
193  self::resetGlobalServices( $bootstrapConfig );
194  }
195 
209  protected static function resetGlobalServices( Config $bootstrapConfig = null ) {
210  $oldServices = MediaWikiServices::getInstance();
211  $oldConfigFactory = $oldServices->getConfigFactory();
212 
213  $testConfig = self::makeTestConfig( $bootstrapConfig );
214 
215  MediaWikiServices::resetGlobalInstance( $testConfig );
216 
217  self::$serviceLocator = MediaWikiServices::getInstance();
218  self::installTestServices(
219  $oldConfigFactory,
220  self::$serviceLocator
221  );
222  }
223 
233  private static function makeTestConfig(
234  Config $baseConfig = null,
235  Config $customOverrides = null
236  ) {
237  $defaultOverrides = new HashConfig();
238 
239  if ( !$baseConfig ) {
240  $baseConfig = MediaWikiServices::getInstance()->getBootstrapConfig();
241  }
242 
243  /* Some functions require some kind of caching, and will end up using the db,
244  * which we can't allow, as that would open a new connection for mysql.
245  * Replace with a HashBag. They would not be going to persist anyway.
246  */
247  $hashCache = [ 'class' => 'HashBagOStuff' ];
248  $objectCaches = [
249  CACHE_DB => $hashCache,
250  CACHE_ACCEL => $hashCache,
251  CACHE_MEMCACHED => $hashCache,
252  'apc' => $hashCache,
253  'xcache' => $hashCache,
254  'wincache' => $hashCache,
255  ] + $baseConfig->get( 'ObjectCaches' );
256 
257  $defaultOverrides->set( 'ObjectCaches', $objectCaches );
258  $defaultOverrides->set( 'MainCacheType', CACHE_NONE );
259 
260  // Use a fast hash algorithm to hash passwords.
261  $defaultOverrides->set( 'PasswordDefault', 'A' );
262 
263  $testConfig = $customOverrides
264  ? new MultiConfig( [ $customOverrides, $defaultOverrides, $baseConfig ] )
265  : new MultiConfig( [ $defaultOverrides, $baseConfig ] );
266 
267  return $testConfig;
268  }
269 
276  private static function installTestServices(
277  ConfigFactory $oldConfigFactory,
278  MediaWikiServices $newServices
279  ) {
280  // Use bootstrap config for all configuration.
281  // This allows config overrides via global variables to take effect.
282  $bootstrapConfig = $newServices->getBootstrapConfig();
283  $newServices->resetServiceForTesting( 'ConfigFactory' );
284  $newServices->redefineService(
285  'ConfigFactory',
286  self::makeTestConfigFactoryInstantiator(
287  $oldConfigFactory,
288  [ 'main' => $bootstrapConfig ]
289  )
290  );
291  }
292 
299  private static function makeTestConfigFactoryInstantiator(
300  ConfigFactory $oldFactory,
301  array $configurations
302  ) {
303  return function( MediaWikiServices $services ) use ( $oldFactory, $configurations ) {
304  $factory = new ConfigFactory();
305 
306  // clone configurations from $oldFactory that are not overwritten by $configurations
307  $namesToClone = array_diff(
308  $oldFactory->getConfigNames(),
309  array_keys( $configurations )
310  );
311 
312  foreach ( $namesToClone as $name ) {
313  $factory->register( $name, $oldFactory->makeConfig( $name ) );
314  }
315 
316  foreach ( $configurations as $name => $config ) {
317  $factory->register( $name, $config );
318  }
319 
320  return $factory;
321  };
322  }
323 
335  private function doLightweightServiceReset() {
337 
341 
342  // TODO: move global state into MediaWikiServices
345  if ( session_id() !== '' ) {
346  session_write_close();
347  session_id( '' );
348  }
349 
350  $wgRequest = new FauxRequest();
352  }
353 
354  public function run( PHPUnit_Framework_TestResult $result = null ) {
355  // Reset all caches between tests.
356  $this->doLightweightServiceReset();
357 
358  $needsResetDB = false;
359 
360  if ( !self::$dbSetup || $this->needsDB() ) {
361  // set up a DB connection for this test to use
362 
363  self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
364  self::$reuseDB = $this->getCliArg( 'reuse-db' );
365 
366  $this->db = wfGetDB( DB_MASTER );
367 
368  $this->checkDbIsSupported();
369 
370  if ( !self::$dbSetup ) {
371  $this->setupAllTestDBs();
372  $this->addCoreDBData();
373 
374  if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
375  $this->resetDB( $this->db, $this->tablesUsed );
376  }
377  }
378 
379  // TODO: the DB setup should be done in setUpBeforeClass(), so the test DB
380  // is available in subclass's setUpBeforeClass() and setUp() methods.
381  // This would also remove the need for the HACK that is oncePerClass().
382  if ( $this->oncePerClass() ) {
383  $this->addDBDataOnce();
384  }
385 
386  $this->addDBData();
387  $needsResetDB = true;
388  }
389 
390  parent::run( $result );
391 
392  if ( $needsResetDB ) {
393  $this->resetDB( $this->db, $this->tablesUsed );
394  }
395  }
396 
400  private function oncePerClass() {
401  // Remember current test class in the database connection,
402  // so we know when we need to run addData.
403 
404  $class = static::class;
405 
406  $first = !isset( $this->db->_hasDataForTestClass )
407  || $this->db->_hasDataForTestClass !== $class;
408 
409  $this->db->_hasDataForTestClass = $class;
410  return $first;
411  }
412 
418  public function usesTemporaryTables() {
419  return self::$useTemporaryTables;
420  }
421 
431  protected function getNewTempFile() {
432  $fileName = tempnam( wfTempDir(), 'MW_PHPUnit_' . get_class( $this ) . '_' );
433  $this->tmpFiles[] = $fileName;
434 
435  return $fileName;
436  }
437 
448  protected function getNewTempDirectory() {
449  // Starting of with a temporary /file/.
450  $fileName = $this->getNewTempFile();
451 
452  // Converting the temporary /file/ to a /directory/
453  // The following is not atomic, but at least we now have a single place,
454  // where temporary directory creation is bundled and can be improved
455  unlink( $fileName );
456  $this->assertTrue( wfMkdirParents( $fileName ) );
457 
458  return $fileName;
459  }
460 
461  protected function setUp() {
462  parent::setUp();
463  $this->called['setUp'] = true;
464 
465  $this->phpErrorLevel = intval( ini_get( 'error_reporting' ) );
466 
467  // Cleaning up temporary files
468  foreach ( $this->tmpFiles as $fileName ) {
469  if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
470  unlink( $fileName );
471  } elseif ( is_dir( $fileName ) ) {
472  wfRecursiveRemoveDir( $fileName );
473  }
474  }
475 
476  if ( $this->needsDB() && $this->db ) {
477  // Clean up open transactions
478  while ( $this->db->trxLevel() > 0 ) {
479  $this->db->rollback( __METHOD__, 'flush' );
480  }
481  }
482 
484  ObjectCache::getMainWANInstance()->clearProcessCache();
485 
486  ob_start( 'MediaWikiTestCase::wfResetOutputBuffersBarrier' );
487  }
488 
489  protected function addTmpFiles( $files ) {
490  $this->tmpFiles = array_merge( $this->tmpFiles, (array)$files );
491  }
492 
493  protected function tearDown() {
495 
496  $status = ob_get_status();
497  if ( isset( $status['name'] ) &&
498  $status['name'] === 'MediaWikiTestCase::wfResetOutputBuffersBarrier'
499  ) {
500  ob_end_flush();
501  }
502 
503  $this->called['tearDown'] = true;
504  // Cleaning up temporary files
505  foreach ( $this->tmpFiles as $fileName ) {
506  if ( is_file( $fileName ) || ( is_link( $fileName ) ) ) {
507  unlink( $fileName );
508  } elseif ( is_dir( $fileName ) ) {
509  wfRecursiveRemoveDir( $fileName );
510  }
511  }
512 
513  if ( $this->needsDB() && $this->db ) {
514  // Clean up open transactions
515  while ( $this->db->trxLevel() > 0 ) {
516  $this->db->rollback( __METHOD__, 'flush' );
517  }
518  }
519 
520  // Restore mw globals
521  foreach ( $this->mwGlobals as $key => $value ) {
522  $GLOBALS[$key] = $value;
523  }
524  $this->mwGlobals = [];
525  $this->restoreLoggers();
526 
527  if ( self::$serviceLocator && MediaWikiServices::getInstance() !== self::$serviceLocator ) {
528  MediaWikiServices::forceGlobalInstance( self::$serviceLocator );
529  }
530 
531  // TODO: move global state into MediaWikiServices
534  if ( session_id() !== '' ) {
535  session_write_close();
536  session_id( '' );
537  }
538  $wgRequest = new FauxRequest();
541 
542  $phpErrorLevel = intval( ini_get( 'error_reporting' ) );
543 
544  if ( $phpErrorLevel !== $this->phpErrorLevel ) {
545  ini_set( 'error_reporting', $this->phpErrorLevel );
546 
547  $oldHex = strtoupper( dechex( $this->phpErrorLevel ) );
548  $newHex = strtoupper( dechex( $phpErrorLevel ) );
549  $message = "PHP error_reporting setting was left dirty: "
550  . "was 0x$oldHex before test, 0x$newHex after test!";
551 
552  $this->fail( $message );
553  }
554 
555  parent::tearDown();
556  }
557 
562  final public function testMediaWikiTestCaseParentSetupCalled() {
563  $this->assertArrayHasKey( 'setUp', $this->called,
564  static::class . '::setUp() must call parent::setUp()'
565  );
566  }
567 
577  protected function setService( $name, $object ) {
578  // If we did not yet override the service locator, so so now.
579  if ( MediaWikiServices::getInstance() === self::$serviceLocator ) {
580  $this->overrideMwServices();
581  }
582 
583  MediaWikiServices::getInstance()->disableService( $name );
584  MediaWikiServices::getInstance()->redefineService(
585  $name,
586  function () use ( $object ) {
587  return $object;
588  }
589  );
590  }
591 
627  protected function setMwGlobals( $pairs, $value = null ) {
628  if ( is_string( $pairs ) ) {
629  $pairs = [ $pairs => $value ];
630  }
631 
632  $this->stashMwGlobals( array_keys( $pairs ) );
633 
634  foreach ( $pairs as $key => $value ) {
635  $GLOBALS[$key] = $value;
636  }
637  }
638 
647  private static function canShallowCopy( $value ) {
648  if ( is_scalar( $value ) || $value === null ) {
649  return true;
650  }
651  if ( is_array( $value ) ) {
652  foreach ( $value as $subValue ) {
653  if ( !is_scalar( $subValue ) && $subValue !== null ) {
654  return false;
655  }
656  }
657  return true;
658  }
659  return false;
660  }
661 
681  protected function stashMwGlobals( $globalKeys ) {
682  if ( is_string( $globalKeys ) ) {
683  $globalKeys = [ $globalKeys ];
684  }
685 
686  foreach ( $globalKeys as $globalKey ) {
687  // NOTE: make sure we only save the global once or a second call to
688  // setMwGlobals() on the same global would override the original
689  // value.
690  if ( !array_key_exists( $globalKey, $this->mwGlobals ) ) {
691  if ( !array_key_exists( $globalKey, $GLOBALS ) ) {
692  throw new Exception( "Global with key {$globalKey} doesn't exist and cant be stashed" );
693  }
694  // NOTE: we serialize then unserialize the value in case it is an object
695  // this stops any objects being passed by reference. We could use clone
696  // and if is_object but this does account for objects within objects!
697  if ( self::canShallowCopy( $GLOBALS[$globalKey] ) ) {
698  $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
699  } elseif (
700  // Many MediaWiki types are safe to clone. These are the
701  // ones that are most commonly stashed.
702  $GLOBALS[$globalKey] instanceof Language ||
703  $GLOBALS[$globalKey] instanceof User ||
704  $GLOBALS[$globalKey] instanceof FauxRequest
705  ) {
706  $this->mwGlobals[$globalKey] = clone $GLOBALS[$globalKey];
707  } else {
708  try {
709  $this->mwGlobals[$globalKey] = unserialize( serialize( $GLOBALS[$globalKey] ) );
710  } catch ( Exception $e ) {
711  $this->mwGlobals[$globalKey] = $GLOBALS[$globalKey];
712  }
713  }
714  }
715  }
716  }
717 
733  protected function mergeMwGlobalArrayValue( $name, $values ) {
734  if ( !isset( $GLOBALS[$name] ) ) {
735  $merged = $values;
736  } else {
737  if ( !is_array( $GLOBALS[$name] ) ) {
738  throw new MWException( "MW global $name is not an array." );
739  }
740 
741  // NOTE: do not use array_merge, it screws up for numeric keys.
742  $merged = $GLOBALS[$name];
743  foreach ( $values as $k => $v ) {
744  $merged[$k] = $v;
745  }
746  }
747 
748  $this->setMwGlobals( $name, $merged );
749  }
750 
765  protected function overrideMwServices( Config $configOverrides = null, array $services = [] ) {
766  if ( !$configOverrides ) {
767  $configOverrides = new HashConfig();
768  }
769 
770  $oldInstance = MediaWikiServices::getInstance();
771  $oldConfigFactory = $oldInstance->getConfigFactory();
772 
773  $testConfig = self::makeTestConfig( null, $configOverrides );
774  $newInstance = new MediaWikiServices( $testConfig );
775 
776  // Load the default wiring from the specified files.
777  // NOTE: this logic mirrors the logic in MediaWikiServices::newInstance.
778  $wiringFiles = $testConfig->get( 'ServiceWiringFiles' );
779  $newInstance->loadWiringFiles( $wiringFiles );
780 
781  // Provide a traditional hook point to allow extensions to configure services.
782  Hooks::run( 'MediaWikiServices', [ $newInstance ] );
783 
784  foreach ( $services as $name => $callback ) {
785  $newInstance->redefineService( $name, $callback );
786  }
787 
788  self::installTestServices(
789  $oldConfigFactory,
790  $newInstance
791  );
792  MediaWikiServices::forceGlobalInstance( $newInstance );
793 
794  return $newInstance;
795  }
796 
801  public function setUserLang( $lang ) {
802  RequestContext::getMain()->setLanguage( $lang );
803  $this->setMwGlobals( 'wgLang', RequestContext::getMain()->getLanguage() );
804  }
805 
810  public function setContentLang( $lang ) {
811  if ( $lang instanceof Language ) {
812  $langCode = $lang->getCode();
813  $langObj = $lang;
814  } else {
815  $langCode = $lang;
816  $langObj = Language::factory( $langCode );
817  }
818  $this->setMwGlobals( [
819  'wgLanguageCode' => $langCode,
820  'wgContLang' => $langObj,
821  ] );
822  }
823 
830  protected function setLogger( $channel, LoggerInterface $logger ) {
831  // TODO: Once loggers are managed by MediaWikiServices, use
832  // overrideMwServices() to set loggers.
833 
834  $provider = LoggerFactory::getProvider();
835  $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
836  $singletons = $wrappedProvider->singletons;
837  if ( $provider instanceof MonologSpi ) {
838  if ( !isset( $this->loggers[$channel] ) ) {
839  $this->loggers[$channel] = isset( $singletons['loggers'][$channel] )
840  ? $singletons['loggers'][$channel] : null;
841  }
842  $singletons['loggers'][$channel] = $logger;
843  } elseif ( $provider instanceof LegacySpi ) {
844  if ( !isset( $this->loggers[$channel] ) ) {
845  $this->loggers[$channel] = isset( $singletons[$channel] ) ? $singletons[$channel] : null;
846  }
847  $singletons[$channel] = $logger;
848  } else {
849  throw new LogicException( __METHOD__ . ': setting a logger for ' . get_class( $provider )
850  . ' is not implemented' );
851  }
852  $wrappedProvider->singletons = $singletons;
853  }
854 
859  private function restoreLoggers() {
860  $provider = LoggerFactory::getProvider();
861  $wrappedProvider = TestingAccessWrapper::newFromObject( $provider );
862  $singletons = $wrappedProvider->singletons;
863  foreach ( $this->loggers as $channel => $logger ) {
864  if ( $provider instanceof MonologSpi ) {
865  if ( $logger === null ) {
866  unset( $singletons['loggers'][$channel] );
867  } else {
868  $singletons['loggers'][$channel] = $logger;
869  }
870  } elseif ( $provider instanceof LegacySpi ) {
871  if ( $logger === null ) {
872  unset( $singletons[$channel] );
873  } else {
874  $singletons[$channel] = $logger;
875  }
876  }
877  }
878  $wrappedProvider->singletons = $singletons;
879  $this->loggers = [];
880  }
881 
886  public function dbPrefix() {
887  return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
888  }
889 
894  public function needsDB() {
895  # if the test says it uses database tables, it needs the database
896  if ( $this->tablesUsed ) {
897  return true;
898  }
899 
900  # if the test says it belongs to the Database group, it needs the database
901  $rc = new ReflectionClass( $this );
902  if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) {
903  return true;
904  }
905 
906  return false;
907  }
908 
919  protected function insertPage( $pageName, $text = 'Sample page for unit test.' ) {
920  $title = Title::newFromText( $pageName, 0 );
921 
922  $user = static::getTestSysop()->getUser();
923  $comment = __METHOD__ . ': Sample page for unit test.';
924 
925  // Avoid memory leak...?
926  // LinkCache::singleton()->clear();
927  // Maybe. But doing this absolutely breaks $title->isRedirect() when called during unit tests....
928 
930  $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user );
931 
932  return [
933  'title' => $title,
934  'id' => $page->getId(),
935  ];
936  }
937 
953  public function addDBDataOnce() {
954  }
955 
965  public function addDBData() {
966  }
967 
968  private function addCoreDBData() {
969  if ( $this->db->getType() == 'oracle' ) {
970 
971  # Insert 0 user to prevent FK violations
972  # Anonymous user
973  if ( !$this->db->selectField( 'user', '1', [ 'user_id' => 0 ] ) ) {
974  $this->db->insert( 'user', [
975  'user_id' => 0,
976  'user_name' => 'Anonymous' ], __METHOD__, [ 'IGNORE' ] );
977  }
978 
979  # Insert 0 page to prevent FK violations
980  # Blank page
981  if ( !$this->db->selectField( 'page', '1', [ 'page_id' => 0 ] ) ) {
982  $this->db->insert( 'page', [
983  'page_id' => 0,
984  'page_namespace' => 0,
985  'page_title' => ' ',
986  'page_restrictions' => null,
987  'page_is_redirect' => 0,
988  'page_is_new' => 0,
989  'page_random' => 0,
990  'page_touched' => $this->db->timestamp(),
991  'page_latest' => 0,
992  'page_len' => 0 ], __METHOD__, [ 'IGNORE' ] );
993  }
994  }
995 
997 
998  // Make sysop user
999  $user = static::getTestSysop()->getUser();
1000 
1001  // Make 1 page with 1 revision
1002  $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
1003  if ( $page->getId() == 0 ) {
1004  $page->doEditContent(
1005  new WikitextContent( 'UTContent' ),
1006  'UTPageSummary',
1007  EDIT_NEW,
1008  false,
1009  $user
1010  );
1011 
1012  // doEditContent() probably started the session via
1013  // User::loadFromSession(). Close it now.
1014  if ( session_id() !== '' ) {
1015  session_write_close();
1016  session_id( '' );
1017  }
1018  }
1019  }
1020 
1028  public static function teardownTestDB() {
1030 
1031  if ( !self::$dbSetup ) {
1032  return;
1033  }
1034 
1035  foreach ( $wgJobClasses as $type => $class ) {
1036  // Delete any jobs under the clone DB (or old prefix in other stores)
1037  JobQueueGroup::singleton()->get( $type )->delete();
1038  }
1039 
1040  CloneDatabase::changePrefix( self::$oldTablePrefix );
1041 
1042  self::$oldTablePrefix = false;
1043  self::$dbSetup = false;
1044  }
1045 
1059  protected static function setupDatabaseWithTestPrefix( DatabaseBase $db, $prefix ) {
1060  $tablesCloned = self::listTables( $db );
1061  $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
1062  $dbClone->useTemporaryTables( self::$useTemporaryTables );
1063 
1064  if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
1065  CloneDatabase::changePrefix( $prefix );
1066 
1067  return false;
1068  } else {
1069  $dbClone->cloneTableStructure();
1070  return true;
1071  }
1072  }
1073 
1077  public function setupAllTestDBs() {
1079 
1080  self::$oldTablePrefix = $wgDBprefix;
1081 
1082  $testPrefix = $this->dbPrefix();
1083 
1084  // switch to a temporary clone of the database
1085  self::setupTestDB( $this->db, $testPrefix );
1086 
1087  if ( self::isUsingExternalStoreDB() ) {
1088  self::setupExternalStoreTestDBs( $testPrefix );
1089  }
1090  }
1091 
1113  public static function setupTestDB( DatabaseBase $db, $prefix ) {
1114  if ( $db->tablePrefix() === $prefix ) {
1115  throw new MWException(
1116  'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
1117  }
1118 
1119  if ( self::$dbSetup ) {
1120  return;
1121  }
1122 
1123  // TODO: the below should be re-written as soon as LBFactory, LoadBalancer,
1124  // and DatabaseBase no longer use global state.
1125 
1126  self::$dbSetup = true;
1127 
1128  if ( !self::setupDatabaseWithTestPrefix( $db, $prefix ) ) {
1129  return;
1130  }
1131 
1132  // Assuming this isn't needed for External Store database, and not sure if the procedure
1133  // would be available there.
1134  if ( $db->getType() == 'oracle' ) {
1135  $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
1136  }
1137  }
1138 
1144  protected static function setupExternalStoreTestDBs( $testPrefix ) {
1145  $connections = self::getExternalStoreDatabaseConnections();
1146  foreach ( $connections as $dbw ) {
1147  // Hack: cloneTableStructure sets $wgDBprefix to the unit test
1148  // prefix,. Even though listTables now uses tablePrefix, that
1149  // itself is populated from $wgDBprefix by default.
1150 
1151  // We have to set it back, or we won't find the original 'blobs'
1152  // table to copy.
1153 
1154  $dbw->tablePrefix( self::$oldTablePrefix );
1155  self::setupDatabaseWithTestPrefix( $dbw, $testPrefix );
1156  }
1157  }
1158 
1166  protected static function getExternalStoreDatabaseConnections() {
1168 
1169  $externalStoreDB = ExternalStore::getStoreObject( 'DB' );
1170  $defaultArray = (array) $wgDefaultExternalStore;
1171  $dbws = [];
1172  foreach ( $defaultArray as $url ) {
1173  if ( strpos( $url, 'DB://' ) === 0 ) {
1174  list( $proto, $cluster ) = explode( '://', $url, 2 );
1175  $dbw = $externalStoreDB->getMaster( $cluster );
1176  $dbws[] = $dbw;
1177  }
1178  }
1179 
1180  return $dbws;
1181  }
1182 
1188  protected static function isUsingExternalStoreDB() {
1190  if ( !$wgDefaultExternalStore ) {
1191  return false;
1192  }
1193 
1194  $defaultArray = (array) $wgDefaultExternalStore;
1195  foreach ( $defaultArray as $url ) {
1196  if ( strpos( $url, 'DB://' ) === 0 ) {
1197  return true;
1198  }
1199  }
1200 
1201  return false;
1202  }
1203 
1210  private function resetDB( $db, $tablesUsed ) {
1211  if ( $db ) {
1212  $userTables = [ 'user', 'user_groups', 'user_properties' ];
1213  $coreDBDataTables = array_merge( $userTables, [ 'page', 'revision' ] );
1214 
1215  // If any of the user tables were marked as used, we should clear all of them.
1216  if ( array_intersect( $tablesUsed, $userTables ) ) {
1217  $tablesUsed = array_unique( array_merge( $tablesUsed, $userTables ) );
1219  }
1220 
1221  $truncate = in_array( $db->getType(), [ 'oracle', 'mysql' ] );
1222  foreach ( $tablesUsed as $tbl ) {
1223  // TODO: reset interwiki table to its original content.
1224  if ( $tbl == 'interwiki' ) {
1225  continue;
1226  }
1227 
1228  if ( $truncate ) {
1229  $db->query( 'TRUNCATE TABLE ' . $db->tableName( $tbl ), __METHOD__ );
1230  } else {
1231  $db->delete( $tbl, '*', __METHOD__ );
1232  }
1233 
1234  if ( $tbl === 'page' ) {
1235  // Forget about the pages since they don't
1236  // exist in the DB.
1237  LinkCache::singleton()->clear();
1238  }
1239  }
1240 
1241  if ( array_intersect( $tablesUsed, $coreDBDataTables ) ) {
1242  // Re-add core DB data that was deleted
1243  $this->addCoreDBData();
1244  }
1245  }
1246  }
1247 
1257  public function __call( $func, $args ) {
1258  static $compatibility = [
1259  'assertEmpty' => 'assertEmpty2', // assertEmpty was added in phpunit 3.7.32
1260  ];
1261 
1262  if ( isset( $compatibility[$func] ) ) {
1263  return call_user_func_array( [ $this, $compatibility[$func] ], $args );
1264  } else {
1265  throw new MWException( "Called non-existent $func method on "
1266  . get_class( $this ) );
1267  }
1268  }
1269 
1275  private function assertEmpty2( $value, $msg ) {
1276  $this->assertTrue( $value == '', $msg );
1277  }
1278 
1279  private static function unprefixTable( &$tableName, $ind, $prefix ) {
1280  $tableName = substr( $tableName, strlen( $prefix ) );
1281  }
1282 
1283  private static function isNotUnittest( $table ) {
1284  return strpos( $table, 'unittest_' ) !== 0;
1285  }
1286 
1294  public static function listTables( $db ) {
1295  $prefix = $db->tablePrefix();
1296  $tables = $db->listTables( $prefix, __METHOD__ );
1297 
1298  if ( $db->getType() === 'mysql' ) {
1299  # bug 43571: cannot clone VIEWs under MySQL
1300  $views = $db->listViews( $prefix, __METHOD__ );
1301  $tables = array_diff( $tables, $views );
1302  }
1303  array_walk( $tables, [ __CLASS__, 'unprefixTable' ], $prefix );
1304 
1305  // Don't duplicate test tables from the previous fataled run
1306  $tables = array_filter( $tables, [ __CLASS__, 'isNotUnittest' ] );
1307 
1308  if ( $db->getType() == 'sqlite' ) {
1309  $tables = array_flip( $tables );
1310  // these are subtables of searchindex and don't need to be duped/dropped separately
1311  unset( $tables['searchindex_content'] );
1312  unset( $tables['searchindex_segdir'] );
1313  unset( $tables['searchindex_segments'] );
1314  $tables = array_flip( $tables );
1315  }
1316 
1317  return $tables;
1318  }
1319 
1324  protected function checkDbIsSupported() {
1325  if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) {
1326  throw new MWException( $this->db->getType() . " is not currently supported for unit testing." );
1327  }
1328  }
1329 
1335  public function getCliArg( $offset ) {
1336  if ( isset( PHPUnitMaintClass::$additionalOptions[$offset] ) ) {
1337  return PHPUnitMaintClass::$additionalOptions[$offset];
1338  }
1339  }
1340 
1346  public function setCliArg( $offset, $value ) {
1348  }
1349 
1357  public function hideDeprecated( $function ) {
1358  MediaWiki\suppressWarnings();
1359  wfDeprecated( $function );
1360  MediaWiki\restoreWarnings();
1361  }
1362 
1381  protected function assertSelect( $table, $fields, $condition, array $expectedRows ) {
1382  if ( !$this->needsDB() ) {
1383  throw new MWException( 'When testing database state, the test cases\'s needDB()' .
1384  ' method should return true. Use @group Database or $this->tablesUsed.' );
1385  }
1386 
1387  $db = wfGetDB( DB_SLAVE );
1388 
1389  $res = $db->select( $table, $fields, $condition, wfGetCaller(), [ 'ORDER BY' => $fields ] );
1390  $this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
1391 
1392  $i = 0;
1393 
1394  foreach ( $expectedRows as $expected ) {
1395  $r = $res->fetchRow();
1396  self::stripStringKeys( $r );
1397 
1398  $i += 1;
1399  $this->assertNotEmpty( $r, "row #$i missing" );
1400 
1401  $this->assertEquals( $expected, $r, "row #$i mismatches" );
1402  }
1403 
1404  $r = $res->fetchRow();
1405  self::stripStringKeys( $r );
1406 
1407  $this->assertFalse( $r, "found extra row (after #$i)" );
1408  }
1409 
1421  protected function arrayWrap( array $elements ) {
1422  return array_map(
1423  function ( $element ) {
1424  return [ $element ];
1425  },
1426  $elements
1427  );
1428  }
1429 
1442  protected function assertArrayEquals( array $expected, array $actual,
1443  $ordered = false, $named = false
1444  ) {
1445  if ( !$ordered ) {
1446  $this->objectAssociativeSort( $expected );
1447  $this->objectAssociativeSort( $actual );
1448  }
1449 
1450  if ( !$named ) {
1451  $expected = array_values( $expected );
1452  $actual = array_values( $actual );
1453  }
1454 
1455  call_user_func_array(
1456  [ $this, 'assertEquals' ],
1457  array_merge( [ $expected, $actual ], array_slice( func_get_args(), 4 ) )
1458  );
1459  }
1460 
1473  protected function assertHTMLEquals( $expected, $actual, $msg = '' ) {
1474  $expected = str_replace( '>', ">\n", $expected );
1475  $actual = str_replace( '>', ">\n", $actual );
1476 
1477  $this->assertEquals( $expected, $actual, $msg );
1478  }
1479 
1487  protected function objectAssociativeSort( array &$array ) {
1488  uasort(
1489  $array,
1490  function ( $a, $b ) {
1491  return serialize( $a ) > serialize( $b ) ? 1 : -1;
1492  }
1493  );
1494  }
1495 
1505  protected static function stripStringKeys( &$r ) {
1506  if ( !is_array( $r ) ) {
1507  return;
1508  }
1509 
1510  foreach ( $r as $k => $v ) {
1511  if ( is_string( $k ) ) {
1512  unset( $r[$k] );
1513  }
1514  }
1515  }
1516 
1530  protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) {
1531  if ( $actual === $value ) {
1532  $this->assertTrue( true, $message );
1533  } else {
1534  $this->assertType( $type, $actual, $message );
1535  }
1536  }
1537 
1549  protected function assertType( $type, $actual, $message = '' ) {
1550  if ( class_exists( $type ) || interface_exists( $type ) ) {
1551  $this->assertInstanceOf( $type, $actual, $message );
1552  } else {
1553  $this->assertInternalType( $type, $actual, $message );
1554  }
1555  }
1556 
1566  protected function isWikitextNS( $ns ) {
1568 
1569  if ( isset( $wgNamespaceContentModels[$ns] ) ) {
1570  return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT;
1571  }
1572 
1573  return true;
1574  }
1575 
1583  protected function getDefaultWikitextNS() {
1585 
1586  static $wikitextNS = null; // this is not going to change
1587  if ( $wikitextNS !== null ) {
1588  return $wikitextNS;
1589  }
1590 
1591  // quickly short out on most common case:
1592  if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
1593  return NS_MAIN;
1594  }
1595 
1596  // NOTE: prefer content namespaces
1597  $namespaces = array_unique( array_merge(
1599  [ NS_MAIN, NS_HELP, NS_PROJECT ], // prefer these
1601  ) );
1602 
1603  $namespaces = array_diff( $namespaces, [
1604  NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
1605  ] );
1606 
1607  $talk = array_filter( $namespaces, function ( $ns ) {
1608  return MWNamespace::isTalk( $ns );
1609  } );
1610 
1611  // prefer non-talk pages
1612  $namespaces = array_diff( $namespaces, $talk );
1613  $namespaces = array_merge( $namespaces, $talk );
1614 
1615  // check default content model of each namespace
1616  foreach ( $namespaces as $ns ) {
1617  if ( !isset( $wgNamespaceContentModels[$ns] ) ||
1618  $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT
1619  ) {
1620 
1621  $wikitextNS = $ns;
1622 
1623  return $wikitextNS;
1624  }
1625  }
1626 
1627  // give up
1628  // @todo Inside a test, we could skip the test as incomplete.
1629  // But frequently, this is used in fixture setup.
1630  throw new MWException( "No namespace defaults to wikitext!" );
1631  }
1632 
1639  protected function markTestSkippedIfNoDiff3() {
1640  global $wgDiff3;
1641 
1642  # This check may also protect against code injection in
1643  # case of broken installations.
1644  MediaWiki\suppressWarnings();
1645  $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
1646  MediaWiki\restoreWarnings();
1647 
1648  if ( !$haveDiff3 ) {
1649  $this->markTestSkipped( "Skip test, since diff3 is not configured" );
1650  }
1651  }
1652 
1661  protected function checkPHPExtension( $extName ) {
1662  $loaded = extension_loaded( $extName );
1663  if ( !$loaded ) {
1664  $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." );
1665  }
1666 
1667  return $loaded;
1668  }
1669 
1684  protected function assertValidHtmlSnippet( $html ) {
1685  $html = '<!DOCTYPE html><html><head><title>test</title></head><body>' . $html . '</body></html>';
1686  $this->assertValidHtmlDocument( $html );
1687  }
1688 
1700  protected function assertValidHtmlDocument( $html ) {
1701  // Note: we only validate if the tidy PHP extension is available.
1702  // In case wgTidyInternal is false, MWTidy would fall back to the command line version
1703  // of tidy. In that case however, we can not reliably detect whether a failing validation
1704  // is due to malformed HTML, or caused by tidy not being installed as a command line tool.
1705  // That would cause all HTML assertions to fail on a system that has no tidy installed.
1706  if ( !$GLOBALS['wgTidyInternal'] || !MWTidy::isEnabled() ) {
1707  $this->markTestSkipped( 'Tidy extension not installed' );
1708  }
1709 
1710  $errorBuffer = '';
1711  MWTidy::checkErrors( $html, $errorBuffer );
1712  $allErrors = preg_split( '/[\r\n]+/', $errorBuffer );
1713 
1714  // Filter Tidy warnings which aren't useful for us.
1715  // Tidy eg. often cries about parameters missing which have actually
1716  // been deprecated since HTML4, thus we should not care about them.
1717  $errors = preg_grep(
1718  '/^(.*Warning: (trimming empty|.* lacks ".*?" attribute).*|\s*)$/m',
1719  $allErrors, PREG_GREP_INVERT
1720  );
1721 
1722  $this->assertEmpty( $errors, implode( "\n", $errors ) );
1723  }
1724 
1732  private static function tagMatch( $matcher, $actual, $isHtml = true ) {
1733  $dom = PHPUnit_Util_XML::load( $actual, $isHtml );
1734  $tags = PHPUnit_Util_XML::findNodes( $dom, $matcher, $isHtml );
1735  return count( $tags ) > 0 && $tags[0] instanceof DOMNode;
1736  }
1737 
1749  public static function assertTag( $matcher, $actual, $message = '', $isHtml = true ) {
1750  // trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
1751 
1752  self::assertTrue( self::tagMatch( $matcher, $actual, $isHtml ), $message );
1753  }
1754 
1764  public static function assertNotTag( $matcher, $actual, $message = '', $isHtml = true ) {
1765  // trigger_error(__METHOD__ . ' is deprecated', E_USER_DEPRECATED);
1766 
1767  self::assertFalse( self::tagMatch( $matcher, $actual, $isHtml ), $message );
1768  }
1769 
1774  public static function wfResetOutputBuffersBarrier( $buffer ) {
1775  return $buffer;
1776  }
1777 
1778 }
select($table, $vars, $conds= '', $fname=__METHOD__, $options=[], $join_conds=[])
Execute a SELECT query constructed using the various parameters provided.
Definition: Database.php:1233
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:101
mergeMwGlobalArrayValue($name, $values)
Merges the given values into a MW global array variable.
const DB_PREFIX
Table name prefixes.
checkPHPExtension($extName)
Check if $extName is a loaded PHP extension, will skip the test whenever it is not loaded...
static $additionalOptions
Definition: phpunit.php:20
static getMainWANInstance()
Get the main WAN cache object.
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1816
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
LoggerInterface[] $loggers
Holds original loggers which have been replaced by setLogger()
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static clearPendingUpdates()
Clear all pending updates without performing them.
static wfResetOutputBuffersBarrier($buffer)
Used as a marker to prevent wfResetOutputBuffers from breaking PHPUnit.
static installTestServices(ConfigFactory $oldConfigFactory, MediaWikiServices $newServices)
the array() calling protocol came about after MediaWiki 1.4rc1.
static assertNotTag($matcher, $actual, $message= '', $isHtml=true)
const CONTENT_MODEL_WIKITEXT
Definition: Defines.php:278
array $wgDefaultExternalStore
The place to put new revisions, false to put them in the local text table.
const NS_MAIN
Definition: Defines.php:69
$called
$called tracks whether the setUp and tearDown method has been called.
const CACHE_ACCEL
Definition: Defines.php:105
Factory class to create Config objects.
tableName($name, $format= 'quoted')
Format a table name ready for use in constructing an SQL query.
Definition: Database.php:1684
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wfMkdirParents($dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:1980
static isEnabled()
Definition: MWTidy.php:79
assertValidHtmlDocument($html)
Asserts that the given string is valid HTML document.
if(!isset($args[0])) $lang
static isTalk($index)
Is the given namespace a talk namespace?
Definition: MWNamespace.php:97
$comment
listViews($prefix=null, $fname=__METHOD__)
Lists all the VIEWs in the database.
Definition: Database.php:2839
MediaWiki has optional support for a high distributed memory object caching system For general information on but for a larger site with heavy load
Definition: memcached.txt:1
assertArrayEquals(array $expected, array $actual, $ordered=false, $named=false)
Assert that two arrays are equal.
run(PHPUnit_Framework_TestResult $result=null)
insertPage($pageName, $text= 'Sample page for unit test.')
Insert a new page.
static checkErrors($text, &$errorStr=null)
Check HTML for errors, used if $wgValidateAllHtml = true.
Definition: MWTidy.php:63
$value
static changePrefix($prefix)
Change the table prefix on all open DB connections/.
$files
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
assertType($type, $actual, $message= '')
Asserts the type of the provided value.
lastError()
Get a description of the last error.
__construct($name=null, array $data=[], $dataName= '')
static resetIdByNameCache()
Reset the cache used in idFromName().
Definition: User.php:804
static newFromText($text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:256
static resetCache()
Resets all static caches.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static destroySingleton()
Destroy the singleton instance.
$wgJobClasses
Maps jobs to their handling classes; extensions can add to this to provide custom jobs...
LoggerFactory service provider that creates loggers implemented by Monolog.
Definition: MonologSpi.php:116
getNewTempFile()
Obtains a new temporary file name.
listTables($prefix=null, $fname=__METHOD__)
List all tables on the database.
Definition: Database.php:2815
getType()
Get the type of the DBMS, as it appears in $wgDBtype.
arrayWrap(array $elements)
Utility method taking an array of elements and wrapping each element in its own array.
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist & $tables
Definition: hooks.txt:981
setService($name, $object)
Sets a service, maintaining a stashed version of the previous service to be restored in tearDown...
const CACHE_MEMCACHED
Definition: Defines.php:104
tablePrefix($prefix=null)
Get/set the table prefix.
Definition: Database.php:239
static tagMatch($matcher, $actual, $isHtml=true)
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED!Use HtmlPageLinkRendererBegin instead.Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1814
if($line===false) $args
Definition: cdb.php:64
restoreLoggers()
Restores loggers replaced by setLogger().
$factory
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
getDefaultWikitextNS()
Returns the ID of a namespace that defaults to Wikitext.
static clear()
Clear the registry.
Accesses configuration settings from $GLOBALS.
resetServiceForTesting($name, $destroy=true)
Resets the given service for testing purposes.
setupAllTestDBs()
Set up all test DBs.
static getTestSysop()
Convenience method for getting an immutable admin test user.
const NS_PROJECT
Definition: Defines.php:73
static resetCache()
Reset the internal caching for unit testing.
wfTempDir()
Tries to get the system directory for temporary files.
static resetCache()
Reset the internal caching for unit testing.
setCliArg($offset, $value)
static getMain()
Static methods.
static prepareServices(Config $bootstrapConfig)
Prepare service configuration for unit testing.
Interface for configuration instances.
Definition: Config.php:28
static singleton()
Get an instance of this class.
Definition: LinkCache.php:65
unserialize($serialized)
Definition: ApiMessage.php:102
$GLOBALS['IP']
static assertTag($matcher, $actual, $message= '', $isHtml=true)
Note: we are overriding this method to remove the deprecated error.
static isNotUnittest($table)
static getStoreObject($proto, array $params=[])
Get an external store object of the given type, with the given parameters.
static teardownTestDB()
Restores MediaWiki to using the table set (table prefix) it was using before setupTestDB() was called...
static setupExternalStoreTestDBs($testPrefix)
Clones the External Store database(s) for testing.
$res
Definition: database.txt:21
makeConfig($name)
Create a given Config using the registered callback for $name.
objectAssociativeSort(array &$array)
Does an associative sort that works for objects.
MediaWiki exception.
Definition: MWException.php:26
static getExternalStoreDatabaseConnections()
Gets master database connections for all of the ExternalStoreDB stores configured in $wgDefaultExtern...
static setupTestDB(DatabaseBase $db, $prefix)
Creates an empty skeleton of the wiki database by cloning its structure to equivalent tables using th...
MediaWiki Logger MonologSpi
Definition: logger.txt:58
Internationalisation code.
Definition: Language.php:39
assertSelect($table, $fields, $condition, array $expectedRows)
Asserts that the given database query yields the rows given by $expectedRows.
const NS_CATEGORY
Definition: Defines.php:83
static resetMain()
Resets singleton returned by getMain().
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
static getContentNamespaces()
Get a list of all namespace indices which are considered to contain content.
static getMutableTestUser($testName, $groups=[])
Get a TestUser object that the caller may modify.
const DB_SLAVE
Definition: Defines.php:46
LoggerFactory service provider that creates LegacyLogger instances.
Definition: LegacySpi.php:38
static makeTestConfig(Config $baseConfig=null, Config $customOverrides=null)
Create a config suitable for testing, based on a base config, default overrides, and custom overrides...
$buffer
Allows to change the fields on the form that will be generated are created Can be used to omit specific feeds from being outputted You must not use this hook to add use OutputPage::addFeedLink() instead.&$feedLinks conditions will AND in the final query as a Content object as a Content object $title
Definition: hooks.txt:312
hideDeprecated($function)
Don't throw a warning if $function is deprecated and called later.
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
const NS_FILE
Definition: Defines.php:75
Content object for wiki text pages.
overrideMwServices(Config $configOverrides=null, array $services=[])
Stashes the global instance of MediaWikiServices, and installs a new one, allowing test cases to over...
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2044
Provides a fallback sequence for Config objects.
Definition: MultiConfig.php:28
namespace and then decline to actually register it & $namespaces
Definition: hooks.txt:927
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static isUsingExternalStoreDB()
Check whether ExternalStoreDB is being used.
const NS_MEDIAWIKI
Definition: Defines.php:77
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
array $mwGlobals
Holds original values of MediaWiki configuration settings to be restored in tearDown().
static getTestUser($groups=[])
Convenience method for getting an immutable test user.
static TestUser[] $users
redefineService($name, callable $instantiator)
Replace an already defined service.
assertTypeOrValue($type, $actual, $value=false, $message= '')
Asserts that the provided variable is of the specified internal type or equals the $value argument...
static singleton($wiki=false)
Database abstraction object.
Definition: Database.php:32
doLightweightServiceReset()
Resets some well known services that typically have state that may interfere with unit tests...
$wgDiff3
Path to the GNU diff3 utility.
int $phpErrorLevel
Original value of PHP's error_reporting setting.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
or there are no hooks to run
Definition: hooks.txt:223
MediaWiki Logger LegacySpi
Definition: logger.txt:53
const NS_HELP
Definition: Defines.php:81
you have access to all of the normal MediaWiki so you can get a DB use the etc For full docs on the Maintenance class
Definition: maintenance.txt:52
const EDIT_NEW
Definition: Defines.php:179
WebRequest clone which takes values from a provided array.
Definition: FauxRequest.php:33
static resetGlobalServices(Config $bootstrapConfig=null)
Reset global services, and install testing environment.
markTestSkippedIfNoDiff3()
Check, if $wgDiff3 is set and ready to merge Will mark the calling test as skipped, if not ready.
static MediaWikiServices null $serviceLocator
The service locator created by prepareServices().
DatabaseBase $db
Primary database.
$wgDBprefix
Table name prefix.
isWikitextNS($ns)
Returns true if the given namespace defaults to Wikitext according to $wgNamespaceContentModels.
query($sql, $fname=__METHOD__, $tempIgnore=false)
Run an SQL query and return the result.
Definition: Database.php:783
wfRecursiveRemoveDir($dir)
Remove a directory and all its content.
$wgNamespaceContentModels
Associative array mapping namespace IDs to the name of the content model pages in that namespace shou...
static getImmutableTestUser($groups=[])
Get a TestUser object that the caller may not modify.
static unprefixTable(&$tableName, $ind, $prefix)
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1020
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method.MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances.The"Spi"in MediaWiki\Logger\Spi stands for"service provider interface".An SPI is an API intended to be implemented or extended by a third party.This software design pattern is intended to enable framework extension and replaceable components.It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki.The service provider interface allows the backend logging library to be implemented in multiple ways.The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime.This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance.Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
static getMutableTestUser($groups=[])
Convenience method for getting a mutable test user.
getNewTempDirectory()
obtains a new temporary directory
const DB_MASTER
Definition: Defines.php:47
static makeTestConfigFactoryInstantiator(ConfigFactory $oldFactory, array $configurations)
array $tmpFiles
Holds the paths of temporary files/directories created through getNewTempFile, and getNewTempDirector...
getBootstrapConfig()
Returns the Config object containing the bootstrap configuration.
static clear()
Clear all the cached instances.
serialize()
Definition: ApiMessage.php:94
static newFromObject($object)
Return the same object, without access restrictions.
const CACHE_NONE
Definition: Defines.php:102
static factory($code)
Get a cached or new language object for a given language code.
Definition: Language.php:179
setMwGlobals($pairs, $value=null)
A Config instance which stores all settings as a member variable.
Definition: HashConfig.php:28
static getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
delete($table, $conds, $fname=__METHOD__)
DELETE query wrapper.
Definition: Database.php:2244
setLogger($channel, LoggerInterface $logger)
Sets the logger for a specified channel, for the duration of the test.
static setupDatabaseWithTestPrefix(DatabaseBase $db, $prefix)
Setups a database with the given prefix.
MediaWikiServices is the service locator for the application scope of MediaWiki.
assertEmpty2($value, $msg)
Used as a compatibility method for phpunit < 3.7.32.
assertHTMLEquals($expected, $actual, $msg= '')
Put each HTML element on its own line and then equals() the results.
if(is_null($wgLocalTZoffset)) if(!$wgDBerrorLogTZ) $wgRequest
Definition: Setup.php:663
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2376
static stripStringKeys(&$r)
Utility function for eliminating all string keys from an array.
stashMwGlobals($globalKeys)
Stashes the global, will be restored in tearDown()
wfGetCaller($level=2)
Get the name of the function which called this function wfGetCaller( 1 ) is the function with the wfG...
resetDB($db, $tablesUsed)
Empty all tables so they can be repopulated for tests.
const CACHE_DB
Definition: Defines.php:103
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached $page
Definition: hooks.txt:2376
testMediaWikiTestCaseParentSetupCalled()
Make sure MediaWikiTestCase extending classes have called their parent setUp method.
static destroySingletons()
Destroy the singleton instances.
static canShallowCopy($value)
Check if we can back up a value by performing a shallow copy.
assertValidHtmlSnippet($html)
Asserts that the given string is a valid HTML snippet.
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310