diff options
Diffstat (limited to 'tests/phpunit')
269 files changed, 20725 insertions, 6591 deletions
diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile index 8a55dae0..c3e2a303 100644 --- a/tests/phpunit/Makefile +++ b/tests/phpunit/Makefile @@ -2,7 +2,7 @@ .DEFAULT: warning SHELL = /bin/sh -CONFIG_FILE = $(shell pwd)/suite.xml +CONFIG_FILE = ${PWD}/suite.xml PHP = php PU = ${PHP} phpunit.php --configuration ${CONFIG_FILE} ${FLAGS} diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php index 6dd8ea35..1131385f 100644 --- a/tests/phpunit/MediaWikiLangTestCase.php +++ b/tests/phpunit/MediaWikiLangTestCase.php @@ -4,39 +4,30 @@ * Base class that store and restore the Language objects */ abstract class MediaWikiLangTestCase extends MediaWikiTestCase { - private static $oldLang; - private static $oldContLang; - - public function setUp() { - global $wgLanguageCode, $wgLang, $wgContLang; + protected function setUp() { + global $wgLanguageCode, $wgContLang; parent::setUp(); - self::$oldLang = $wgLang; - self::$oldContLang = $wgContLang; - - if( $wgLanguageCode != $wgContLang->getCode() ) { - throw new MWException("Error in MediaWikiLangTestCase::setUp(): " . + if ( $wgLanguageCode != $wgContLang->getCode() ) { + throw new MWException( "Error in MediaWikiLangTestCase::setUp(): " . "\$wgLanguageCode ('$wgLanguageCode') is different from " . "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" ); } - $wgLanguageCode = 'en'; # For mainpage to be 'Main Page' - - $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); - MessageCache::singleton()->disable(); - - } + // HACK: Call getLanguage() so the real $wgContLang is cached as the user language + // rather than our fake one. This is to avoid breaking other, unrelated tests. + RequestContext::getMain()->getLanguage(); - public function tearDown() { - global $wgContLang, $wgLang, $wgLanguageCode; - $wgLang = self::$oldLang; + $langCode = 'en'; # For mainpage to be 'Main Page' + $langObj = Language::factory( $langCode ); - $wgContLang = self::$oldContLang; - $wgLanguageCode = $wgContLang->getCode(); - self::$oldContLang = self::$oldLang = null; + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgLang' => $langObj, + 'wgContLang' => $langObj, + ) ); - parent::tearDown(); + MessageCache::singleton()->disable(); } - } diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php index fca32515..042956a9 100644 --- a/tests/phpunit/MediaWikiPHPUnitCommand.php +++ b/tests/phpunit/MediaWikiPHPUnitCommand.php @@ -2,26 +2,45 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { - static $additionalOptions = array( + public static $additionalOptions = array( 'regex=' => false, 'file=' => false, 'use-filebackend=' => false, + 'use-bagostuff=' => false, + 'use-jobqueue=' => false, 'keep-uploads' => false, 'use-normal-tables' => false, 'reuse-db' => false, + 'wiki=' => false, + 'debug-tests' => false, ); public function __construct() { - foreach( self::$additionalOptions as $option => $default ) { + foreach ( self::$additionalOptions as $option => $default ) { $this->longOptions[$option] = $option . 'Handler'; } + } + + protected function handleArguments( array $argv ) { + parent::handleArguments( $argv ); + + if ( !isset( $this->arguments['listeners'] ) ) { + $this->arguments['listeners'] = array(); + } + foreach ( $this->options[0] as $option ) { + switch ( $option[0] ) { + case '--debug-tests': + $this->arguments['listeners'][] = new MediaWikiPHPUnitTestListener( 'PHPUnitCommand' ); + break; + } + } } public static function main( $exit = true ) { $command = new self; - if( wfIsWindows() ) { + if ( wfIsWindows() ) { # Windows does not come anymore with ANSI.SYS loaded by default # PHPUnit uses the suite.xml parameters to enable/disable colors # which can be then forced to be enabled with --colors. @@ -38,18 +57,40 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { # See bug 32022 set_include_path( __DIR__ - .PATH_SEPARATOR - . get_include_path() + . PATH_SEPARATOR + . get_include_path() ); - $command->run($_SERVER['argv'], $exit); + $command->run( $_SERVER['argv'], $exit ); } public function __call( $func, $args ) { - if( substr( $func, -7 ) == 'Handler' ) { - if( is_null( $args[0] ) ) $args[0] = true; //Booleans - self::$additionalOptions[substr( $func, 0, -7 ) ] = $args[0]; + if ( substr( $func, -7 ) == 'Handler' ) { + if ( is_null( $args[0] ) ) { + $args[0] = true; + } //Booleans + self::$additionalOptions[substr( $func, 0, -7 )] = $args[0]; + } + } + + public function run( array $argv, $exit = true ) { + wfProfileIn( __METHOD__ ); + + $ret = parent::run( $argv, false ); + + wfProfileOut( __METHOD__ ); + + // Return to real wiki db, so profiling data is preserved + MediaWikiTestCase::teardownTestDB(); + + // Log profiling data, e.g. in the database or UDP + wfLogProfilingData(); + + if ( $exit ) { + exit( $ret ); + } else { + return $ret; } } @@ -61,7 +102,7 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command { ParserTest-specific options: --regex="<regex>" Only run parser tests that match the given regex - --file="<filename>" Prints the version and exits. + --file="<filename>" File describing parser tests --keep-uploads Re-use the same upload directory for each test, don't delete it @@ -70,7 +111,9 @@ Database options: --reuse-db Init DB only if tables are missing and keep after finish. +Debugging options: + --debug-tests Log testing activity to the PHPUnitCommand log channel. + EOT; } - } diff --git a/tests/phpunit/MediaWikiPHPUnitTestListener.php b/tests/phpunit/MediaWikiPHPUnitTestListener.php new file mode 100644 index 00000000..7237ef32 --- /dev/null +++ b/tests/phpunit/MediaWikiPHPUnitTestListener.php @@ -0,0 +1,114 @@ +<?php +class MediaWikiPHPUnitTestListener implements PHPUnit_Framework_TestListener { + + /** + * @var string + */ + protected $logChannel; + + public function __construct( $logChannel ) { + $this->logChannel = $logChannel; + } + + protected function getTestName( PHPUnit_Framework_Test $test ) { + $name = get_class( $test ); + + if ( $test instanceof PHPUnit_Framework_TestCase ) { + $name .= '::' . $test->getName( true ); + } + + return $name; + } + + protected function getErrorName( Exception $exception ) { + $name = get_class( $exception ); + $name = "[$name] " . $exception->getMessage(); + + return $name; + } + + /** + * An error occurred. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'ERROR in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * A failure occurred. + * + * @param PHPUnit_Framework_Test $test + * @param PHPUnit_Framework_AssertionFailedError $e + * @param float $time + */ + public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) { + wfDebugLog( $this->logChannel, 'FAILURE in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * Incomplete test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + */ + public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'Incomplete test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * Skipped test. + * + * @param PHPUnit_Framework_Test $test + * @param Exception $e + * @param float $time + * + * @since Method available since Release 3.0.0 + */ + public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) { + wfDebugLog( $this->logChannel, 'Skipped test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) ); + } + + /** + * A test suite started. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { + wfDebugLog( $this->logChannel, 'START suite ' . $suite->getName() ); + } + + /** + * A test suite ended. + * + * @param PHPUnit_Framework_TestSuite $suite + * @since Method available since Release 2.2.0 + */ + public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) { + wfDebugLog( $this->logChannel, 'END suite ' . $suite->getName() ); + } + + /** + * A test started. + * + * @param PHPUnit_Framework_Test $test + */ + public function startTest( PHPUnit_Framework_Test $test ) { + wfDebugLog( $this->logChannel, 'Start test ' . $this->getTestName( $test ) ); + } + + /** + * A test ended. + * + * @param PHPUnit_Framework_Test $test + * @param float $time + */ + public function endTest( PHPUnit_Framework_Test $test, $time ) { + wfDebugLog( $this->logChannel, 'End test ' . $this->getTestName( $test ) ); + } +} diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php index 1cc45e08..6ce78b56 100644 --- a/tests/phpunit/MediaWikiTestCase.php +++ b/tests/phpunit/MediaWikiTestCase.php @@ -6,6 +6,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { public $runDisabled = false; /** + * $called tracks whether the setUp and tearDown method has been called. + * class extending MediaWikiTestCase usually override setUp and tearDown + * but forget to call the parent. + * + * The array format takes a method name as key and anything as a value. + * By asserting the key exist, we know the child class has called the + * parent. + * + * This property must be private, we do not want child to override it, + * they should call the appropriate parent method instead. + */ + private $called = array(); + + /** * @var Array of TestUser */ public static $users; @@ -14,12 +28,12 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @var DatabaseBase */ protected $db; - protected $oldTablePrefix; - protected $useTemporaryTables = true; - protected $reuseDB = false; protected $tablesUsed = array(); // tables with data + private static $useTemporaryTables = true; + private static $reuseDB = false; private static $dbSetup = false; + private static $oldTablePrefix = false; /** * Holds the paths of temporary files/directories created through getNewTempFile, @@ -29,6 +43,13 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { */ private $tmpfiles = array(); + /** + * Holds original values of MediaWiki configuration settings + * to be restored in tearDown(). + * See also setMwGlobal(). + * @var array + */ + private $mwGlobals = array(); /** * Table name prefixes. Oracle likes it shorter. @@ -43,58 +64,80 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'oracle' ); - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); $this->backupGlobals = false; $this->backupStaticAttributes = false; } - function run( PHPUnit_Framework_TestResult $result = NULL ) { + function run( PHPUnit_Framework_TestResult $result = null ) { /* Some functions require some kind of caching, and will end up using the db, * which we can't allow, as that would open a new connection for mysql. * Replace with a HashBag. They would not be going to persist anyway. */ ObjectCache::$instances[CACHE_DB] = new HashBagOStuff; - if( $this->needsDB() ) { - global $wgDBprefix; - - $this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' ); - $this->reuseDB = $this->getCliArg('reuse-db'); + $needsResetDB = false; + $logName = get_class( $this ) . '::' . $this->getName( false ); + + if ( $this->needsDB() ) { + // set up a DB connection for this test to use + + self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' ); + self::$reuseDB = $this->getCliArg( 'reuse-db' ); $this->db = wfGetDB( DB_MASTER ); $this->checkDbIsSupported(); - $this->oldTablePrefix = $wgDBprefix; + if ( !self::$dbSetup ) { + wfProfileIn( $logName . ' (clone-db)' ); + + // switch to a temporary clone of the database + self::setupTestDB( $this->db, $this->dbPrefix() ); - if( !self::$dbSetup ) { - $this->initDB(); - self::$dbSetup = true; + if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { + $this->resetDB(); + } + + wfProfileOut( $logName . ' (clone-db)' ); } + wfProfileIn( $logName . ' (prepare-db)' ); $this->addCoreDBData(); $this->addDBData(); + wfProfileOut( $logName . ' (prepare-db)' ); + + $needsResetDB = true; + } - parent::run( $result ); + wfProfileIn( $logName ); + parent::run( $result ); + wfProfileOut( $logName ); + if ( $needsResetDB ) { + wfProfileIn( $logName . ' (reset-db)' ); $this->resetDB(); - } else { - parent::run( $result ); + wfProfileOut( $logName . ' (reset-db)' ); } } + function usesTemporaryTables() { + return self::$useTemporaryTables; + } + /** * obtains a new temporary file name * * The obtained filename is enlisted to be removed upon tearDown * - * @returns string: absolute name of the temporary file + * @return string: absolute name of the temporary file */ protected function getNewTempFile() { $fname = tempnam( wfTempDir(), 'MW_PHPUnit_' . get_class( $this ) . '_' ); $this->tmpfiles[] = $fname; + return $fname; } @@ -104,7 +147,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * The obtained directory is enlisted to be removed (recursively with all its contained * files) upon tearDown. * - * @returns string: absolute name of the temporary directory + * @return string: absolute name of the temporary directory */ protected function getNewTempDirectory() { // Starting of with a temporary /file/. @@ -116,10 +159,55 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { // where temporary directory creation is bundled and can be improved unlink( $fname ); $this->assertTrue( wfMkdirParents( $fname ) ); + return $fname; } + /** + * setUp and tearDown should (where significant) + * happen in reverse order. + */ + protected function setUp() { + wfProfileIn( __METHOD__ ); + parent::setUp(); + $this->called['setUp'] = 1; + + /* + // @todo global variables to restore for *every* test + array( + 'wgLang', + 'wgContLang', + 'wgLanguageCode', + 'wgUser', + 'wgTitle', + ); + */ + + // Cleaning up temporary files + foreach ( $this->tmpfiles as $fname ) { + if ( is_file( $fname ) || ( is_link( $fname ) ) ) { + unlink( $fname ); + } elseif ( is_dir( $fname ) ) { + wfRecursiveRemoveDir( $fname ); + } + } + + if ( $this->needsDB() && $this->db ) { + // Clean up open transactions + while ( $this->db->trxLevel() > 0 ) { + $this->db->rollback(); + } + + // don't ignore DB errors + $this->db->ignoreErrors( false ); + } + + wfProfileOut( __METHOD__ ); + } + protected function tearDown() { + wfProfileIn( __METHOD__ ); + // Cleaning up temporary files foreach ( $this->tmpfiles as $fname ) { if ( is_file( $fname ) || ( is_link( $fname ) ) ) { @@ -129,14 +217,114 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { } } - // clean up open transactions - if( $this->needsDB() && $this->db ) { - while( $this->db->trxLevel() > 0 ) { + if ( $this->needsDB() && $this->db ) { + // Clean up open transactions + while ( $this->db->trxLevel() > 0 ) { $this->db->rollback(); } + + // don't ignore DB errors + $this->db->ignoreErrors( false ); + } + + // Restore mw globals + foreach ( $this->mwGlobals as $key => $value ) { + $GLOBALS[$key] = $value; } + $this->mwGlobals = array(); parent::tearDown(); + wfProfileOut( __METHOD__ ); + } + + /** + * Make sure MediaWikiTestCase extending classes have called their + * parent setUp method + */ + final public function testMediaWikiTestCaseParentSetupCalled() { + $this->assertArrayHasKey( 'setUp', $this->called, + get_called_class() . "::setUp() must call parent::setUp()" + ); + } + + /** + * Individual test functions may override globals (either directly or through this + * setMwGlobals() function), however one must call this method at least once for + * each key within the setUp(). + * That way the key is added to the array of globals that will be reset afterwards + * in the tearDown(). And, equally important, that way all other tests are executed + * with the same settings (instead of using the unreliable local settings for most + * tests and fix it only for some tests). + * + * @example + * <code> + * protected function setUp() { + * $this->setMwGlobals( 'wgRestrictStuff', true ); + * } + * + * function testFoo() {} + * + * function testBar() {} + * $this->assertTrue( self::getX()->doStuff() ); + * + * $this->setMwGlobals( 'wgRestrictStuff', false ); + * $this->assertTrue( self::getX()->doStuff() ); + * } + * + * function testQuux() {} + * </code> + * + * @param array|string $pairs Key to the global variable, or an array + * of key/value pairs. + * @param mixed $value Value to set the global to (ignored + * if an array is given as first argument). + */ + protected function setMwGlobals( $pairs, $value = null ) { + + // Normalize (string, value) to an array + if ( is_string( $pairs ) ) { + $pairs = array( $pairs => $value ); + } + + foreach ( $pairs as $key => $value ) { + // NOTE: make sure we only save the global once or a second call to + // setMwGlobals() on the same global would override the original + // value. + if ( !array_key_exists( $key, $this->mwGlobals ) ) { + $this->mwGlobals[$key] = $GLOBALS[$key]; + } + + // Override the global + $GLOBALS[$key] = $value; + } + } + + /** + * Merges the given values into a MW global array variable. + * Useful for setting some entries in a configuration array, instead of + * setting the entire array. + * + * @param String $name The name of the global, as in wgFooBar + * @param Array $values The array containing the entries to set in that global + * + * @throws MWException if the designated global is not an array. + */ + protected function mergeMwGlobalArrayValue( $name, $values ) { + if ( !isset( $GLOBALS[$name] ) ) { + $merged = $values; + } else { + if ( !is_array( $GLOBALS[$name] ) ) { + throw new MWException( "MW global $name is not an array." ); + } + + // NOTE: do not use array_merge, it screws up for numeric keys. + $merged = $GLOBALS[$name]; + foreach ( $values as $k => $v ) { + $merged[$k] = $v; + } + } + + $this->setMwGlobals( $name, $merged ); } function dbPrefix() { @@ -162,7 +350,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * Stub. If a test needs to add additional data to the database, it should * implement this method and do so */ - function addDBData() {} + function addDBData() { + } private function addCoreDBData() { # disabled for performance @@ -174,8 +363,8 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { # Insert 0 user to prevent FK violations # Anonymous user $this->db->insert( 'user', array( - 'user_id' => 0, - 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) ); + 'user_id' => 0, + 'user_name' => 'Anonymous' ), __METHOD__, array( 'IGNORE' ) ); # Insert 0 page to prevent FK violations # Blank page @@ -183,7 +372,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'page_id' => 0, 'page_namespace' => 0, 'page_title' => ' ', - 'page_restrictions' => NULL, + 'page_restrictions' => null, 'page_counter' => 0, 'page_is_redirect' => 0, 'page_is_new' => 0, @@ -191,7 +380,6 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { 'page_touched' => $this->db->timestamp(), 'page_latest' => 0, 'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) ); - } User::resetIdByNameCache(); @@ -208,38 +396,80 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { $user->saveSettings(); } - //Make 1 page with 1 revision $page = WikiPage::factory( Title::newFromText( 'UTPage' ) ); if ( !$page->getId() == 0 ) { - $page->doEdit( 'UTContent', - 'UTPageSummary', - EDIT_NEW, - false, - User::newFromName( 'UTSysop' ) ); + $page->doEditContent( + new WikitextContent( 'UTContent' ), + 'UTPageSummary', + EDIT_NEW, + false, + User::newFromName( 'UTSysop' ) ); } } - private function initDB() { + /** + * Restores MediaWiki to using the table set (table prefix) it was using before + * setupTestDB() was called. Useful if we need to perform database operations + * after the test run has finished (such as saving logs or profiling info). + */ + public static function teardownTestDB() { + if ( !self::$dbSetup ) { + return; + } + + CloneDatabase::changePrefix( self::$oldTablePrefix ); + + self::$oldTablePrefix = false; + self::$dbSetup = false; + } + + /** + * Creates an empty skeleton of the wiki database by cloning its structure + * to equivalent tables using the given $prefix. Then sets MediaWiki to + * use the new set of tables (aka schema) instead of the original set. + * + * This is used to generate a dummy table set, typically consisting of temporary + * tables, that will be used by tests instead of the original wiki database tables. + * + * @note: the original table prefix is stored in self::$oldTablePrefix. This is used + * by teardownTestDB() to return the wiki to using the original table set. + * + * @note: this method only works when first called. Subsequent calls have no effect, + * even if using different parameters. + * + * @param DatabaseBase $db The database connection + * @param String $prefix The prefix to use for the new table set (aka schema). + * + * @throws MWException if the database table prefix is already $prefix + */ + public static function setupTestDB( DatabaseBase $db, $prefix ) { global $wgDBprefix; - if ( $wgDBprefix === $this->dbPrefix() ) { - throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' ); + if ( $wgDBprefix === $prefix ) { + throw new MWException( 'Cannot run unit tests, the database prefix is already "' . $prefix . '"' ); + } + + if ( self::$dbSetup ) { + return; } - $tablesCloned = $this->listTables(); - $dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() ); - $dbClone->useTemporaryTables( $this->useTemporaryTables ); + $tablesCloned = self::listTables( $db ); + $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix ); + $dbClone->useTemporaryTables( self::$useTemporaryTables ); + + self::$dbSetup = true; + self::$oldTablePrefix = $wgDBprefix; + + if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) { + CloneDatabase::changePrefix( $prefix ); - if ( ( $this->db->getType() == 'oracle' || !$this->useTemporaryTables ) && $this->reuseDB ) { - CloneDatabase::changePrefix( $this->dbPrefix() ); - $this->resetDB(); return; } else { $dbClone->cloneTableStructure(); } - if ( $this->db->getType() == 'oracle' ) { - $this->db->query( 'BEGIN FILL_WIKI_INFO; END;' ); + if ( $db->getType() == 'oracle' ) { + $db->query( 'BEGIN FILL_WIKI_INFO; END;' ); } } @@ -247,20 +477,24 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * Empty all tables so they can be repopulated for tests */ private function resetDB() { - if( $this->db ) { - if ( $this->db->getType() == 'oracle' ) { - if ( $this->useTemporaryTables ) { + if ( $this->db ) { + if ( $this->db->getType() == 'oracle' ) { + if ( self::$useTemporaryTables ) { wfGetLB()->closeAll(); $this->db = wfGetDB( DB_MASTER ); } else { - foreach( $this->tablesUsed as $tbl ) { - if( $tbl == 'interwiki') continue; - $this->db->query( 'TRUNCATE TABLE '.$this->db->tableName($tbl), __METHOD__ ); + foreach ( $this->tablesUsed as $tbl ) { + if ( $tbl == 'interwiki' ) { + continue; + } + $this->db->query( 'TRUNCATE TABLE ' . $this->db->tableName( $tbl ), __METHOD__ ); } } } else { - foreach( $this->tablesUsed as $tbl ) { - if( $tbl == 'interwiki' || $tbl == 'user' ) continue; + foreach ( $this->tablesUsed as $tbl ) { + if ( $tbl == 'interwiki' || $tbl == 'user' ) { + continue; + } $this->db->delete( $tbl, '*', __METHOD__ ); } } @@ -276,9 +510,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { ); if ( method_exists( $this->suite, $func ) ) { - return call_user_func_array( array( $this->suite, $func ), $args); + return call_user_func_array( array( $this->suite, $func ), $args ); } elseif ( isset( $compatibility[$func] ) ) { - return call_user_func_array( array( $this, $compatibility[$func] ), $args); + return call_user_func_array( array( $this, $compatibility[$func] ), $args ); } else { throw new MWException( "Called non-existant $func method on " . get_class( $this ) ); @@ -289,25 +523,32 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { return $this->assertTrue( $value == '', $msg ); } - static private function unprefixTable( $tableName ) { + private static function unprefixTable( $tableName ) { global $wgDBprefix; + return substr( $tableName, strlen( $wgDBprefix ) ); } - static private function isNotUnittest( $table ) { + private static function isNotUnittest( $table ) { return strpos( $table, 'unittest_' ) !== 0; } - protected function listTables() { + public static function listTables( $db ) { global $wgDBprefix; - $tables = $this->db->listTables( $wgDBprefix, __METHOD__ ); + $tables = $db->listTables( $wgDBprefix, __METHOD__ ); + + if ( $db->getType() === 'mysql' ) { + # bug 43571: cannot clone VIEWs under MySQL + $views = $db->listViews( $wgDBprefix, __METHOD__ ); + $tables = array_diff( $tables, $views ); + } $tables = array_map( array( __CLASS__, 'unprefixTable' ), $tables ); // Don't duplicate test tables from the previous fataled run $tables = array_filter( $tables, array( __CLASS__, 'isNotUnittest' ) ); - if ( $this->db->getType() == 'sqlite' ) { + if ( $db->getType() == 'sqlite' ) { $tables = array_flip( $tables ); // these are subtables of searchindex and don't need to be duped/dropped separately unset( $tables['searchindex_content'] ); @@ -315,27 +556,26 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { unset( $tables['searchindex_segments'] ); $tables = array_flip( $tables ); } + return $tables; } protected function checkDbIsSupported() { - if( !in_array( $this->db->getType(), $this->supportedDBs ) ) { + if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) { throw new MWException( $this->db->getType() . " is not currently supported for unit testing." ); } } public function getCliArg( $offset ) { - if( isset( MediaWikiPHPUnitCommand::$additionalOptions[$offset] ) ) { + if ( isset( MediaWikiPHPUnitCommand::$additionalOptions[$offset] ) ) { return MediaWikiPHPUnitCommand::$additionalOptions[$offset]; } - } public function setCliArg( $offset, $value ) { MediaWikiPHPUnitCommand::$additionalOptions[$offset] = $value; - } /** @@ -368,10 +608,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * or list the tables under testing in $this->tablesUsed, or override the * needsDB() method. */ - protected function assertSelect( $table, $fields, $condition, Array $expectedRows ) { + protected function assertSelect( $table, $fields, $condition, array $expectedRows ) { if ( !$this->needsDB() ) { throw new MWException( 'When testing database state, the test cases\'s needDB()' . - ' method should return true. Use @group Database or $this->tablesUsed.'); + ' method should return true. Use @group Database or $this->tablesUsed.' ); } $db = wfGetDB( DB_SLAVE ); @@ -410,7 +650,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { */ protected function arrayWrap( array $elements ) { return array_map( - function( $element ) { + function ( $element ) { return array( $element ); }, $elements @@ -458,9 +698,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param String $actual HTML on oneline * @param String $msg Optional message */ - protected function assertHTMLEquals( $expected, $actual, $msg='' ) { + protected function assertHTMLEquals( $expected, $actual, $msg = '' ) { $expected = str_replace( '>', ">\n", $expected ); - $actual = str_replace( '>', ">\n", $actual ); + $actual = str_replace( '>', ">\n", $actual ); $this->assertEquals( $expected, $actual, $msg ); } @@ -475,7 +715,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected function objectAssociativeSort( array &$array ) { uasort( $array, - function( $a, $b ) { + function ( $a, $b ) { return serialize( $a ) > serialize( $b ) ? 1 : -1; } ); @@ -518,8 +758,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) { if ( $actual === $value ) { $this->assertTrue( true, $message ); - } - else { + } else { $this->assertType( $type, $actual, $message ); } } @@ -536,12 +775,174 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase { * @param string $message */ protected function assertType( $type, $actual, $message = '' ) { - if ( is_object( $actual ) ) { + if ( class_exists( $type ) || interface_exists( $type ) ) { $this->assertInstanceOf( $type, $actual, $message ); - } - else { + } else { $this->assertInternalType( $type, $actual, $message ); } } + /** + * Returns true if the given namespace defaults to Wikitext + * according to $wgNamespaceContentModels + * + * @param int $ns The namespace ID to check + * + * @return bool + * @since 1.21 + */ + protected function isWikitextNS( $ns ) { + global $wgNamespaceContentModels; + + if ( isset( $wgNamespaceContentModels[$ns] ) ) { + return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT; + } + + return true; + } + + /** + * Returns the ID of a namespace that defaults to Wikitext. + * Throws an MWException if there is none. + * + * @return int the ID of the wikitext Namespace + * @since 1.21 + */ + protected function getDefaultWikitextNS() { + global $wgNamespaceContentModels; + + static $wikitextNS = null; // this is not going to change + if ( $wikitextNS !== null ) { + return $wikitextNS; + } + + // quickly short out on most common case: + if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) { + return NS_MAIN; + } + + // NOTE: prefer content namespaces + $namespaces = array_unique( array_merge( + MWNamespace::getContentNamespaces(), + array( NS_MAIN, NS_HELP, NS_PROJECT ), // prefer these + MWNamespace::getValidNamespaces() + ) ); + + $namespaces = array_diff( $namespaces, array( + NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces + ) ); + + $talk = array_filter( $namespaces, function ( $ns ) { + return MWNamespace::isTalk( $ns ); + } ); + + // prefer non-talk pages + $namespaces = array_diff( $namespaces, $talk ); + $namespaces = array_merge( $namespaces, $talk ); + + // check default content model of each namespace + foreach ( $namespaces as $ns ) { + if ( !isset( $wgNamespaceContentModels[$ns] ) || + $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT + ) { + + $wikitextNS = $ns; + + return $wikitextNS; + } + } + + // give up + // @todo Inside a test, we could skip the test as incomplete. + // But frequently, this is used in fixture setup. + throw new MWException( "No namespace defaults to wikitext!" ); + } + + /** + * Check, if $wgDiff3 is set and ready to merge + * Will mark the calling test as skipped, if not ready + * + * @since 1.21 + */ + protected function checkHasDiff3() { + global $wgDiff3; + + # This check may also protect against code injection in + # case of broken installations. + wfSuppressWarnings(); + $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); + wfRestoreWarnings(); + + if ( !$haveDiff3 ) { + $this->markTestSkipped( "Skip test, since diff3 is not configured" ); + } + } + + /** + * Check whether we have the 'gzip' commandline utility, will skip + * the test whenever "gzip -V" fails. + * + * Result is cached at the process level. + * + * @return bool + * + * @since 1.21 + */ + protected function checkHasGzip() { + static $haveGzip; + + if ( $haveGzip === null ) { + $retval = null; + wfShellExec( 'gzip -V', $retval ); + $haveGzip = ( $retval === 0 ); + } + + if ( !$haveGzip ) { + $this->markTestSkipped( "Skip test, requires the gzip utility in PATH" ); + } + + return $haveGzip; + } + + /** + * Check if $extName is a loaded PHP extension, will skip the + * test whenever it is not loaded. + * + * @since 1.21 + */ + protected function checkPHPExtension( $extName ) { + $loaded = extension_loaded( $extName ); + if ( !$loaded ) { + $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." ); + } + + return $loaded; + } + + /** + * Asserts that an exception of the specified type occurs when running + * the provided code. + * + * @since 1.21 + * @deprecated since 1.22 Use setExpectedException + * + * @param callable $code + * @param string $expected + * @param string $message + */ + protected function assertException( $code, $expected = 'Exception', $message = '' ) { + $pokemons = null; + + try { + call_user_func( $code ); + } catch ( Exception $pokemons ) { + // Gotta Catch 'Em All! + } + + if ( $message === '' ) { + $message = 'An exception of type "' . $expected . '" should have been thrown'; + } + + $this->assertInstanceOf( $expected, $pokemons, $message ); + } } diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php index 933767e7..d929b79d 100644 --- a/tests/phpunit/bootstrap.php +++ b/tests/phpunit/bootstrap.php @@ -11,22 +11,5 @@ if ( !defined( 'MW_PHPUNIT_TEST' ) ) { You are running these tests directly from phpunit. You may not have all globals correctly set. Running phpunit.php instead is recommended. EOF; - require_once ( __DIR__ . "/phpunit.php" ); + require_once __DIR__ . "/phpunit.php"; } - -// Output a notice when running with older versions of PHPUnit -if ( version_compare( PHPUnit_Runner_Version::id(), "3.6.7", "<" ) ) { - echo <<<EOF -******************************************************************************** - -These tests run best with version PHPUnit 3.6.7 or better. Earlier versions may -show failures because earlier versions of PHPUnit do not properly implement -dependencies. - -******************************************************************************** - -EOF; -} - -/** @todo Check if this is really needed */ -MessageCache::destroyInstance(); diff --git a/tests/phpunit/data/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql index 6e56add2..7e8f30ec 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.16.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql @@ -146,11 +146,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', diff --git a/tests/phpunit/data/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql index 69ae3764..e02e3e14 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.17.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql @@ -151,11 +151,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql index bedf6c33..8bfc28e2 100644 --- a/tests/phpunit/data/db/sqlite/tables-1.18.sql +++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql @@ -157,11 +157,6 @@ CREATE TABLE /*_*/externallinks ( CREATE INDEX /*i*/el_from ON /*_*/externallinks (el_from, el_to(40)); CREATE INDEX /*i*/el_to ON /*_*/externallinks (el_to(60), el_from); CREATE INDEX /*i*/el_index ON /*_*/externallinks (el_index(60)); -CREATE TABLE /*_*/external_user ( - eu_local_id int unsigned NOT NULL PRIMARY KEY, - eu_external_id varchar(255) binary NOT NULL -) /*$wgDBTableOptions*/; -CREATE UNIQUE INDEX /*i*/eu_external_id ON /*_*/external_user (eu_external_id); CREATE TABLE /*_*/langlinks ( ll_from int unsigned NOT NULL default 0, ll_lang varbinary(20) NOT NULL default '', @@ -296,7 +291,7 @@ CREATE TABLE /*_*/uploadstash ( us_size int unsigned NOT NULL, us_sha1 varchar(31) NOT NULL, us_mime varchar(255), - us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, + us_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL, us_image_width int unsigned, us_image_height int unsigned, us_image_bits smallint unsigned diff --git a/tests/phpunit/data/less/common/test.common.mixins.less b/tests/phpunit/data/less/common/test.common.mixins.less new file mode 100644 index 00000000..2fbe9b79 --- /dev/null +++ b/tests/phpunit/data/less/common/test.common.mixins.less @@ -0,0 +1,5 @@ +.test-mixin (@value) { + color: @value; + border: @foo solid @Foo; + line-height: test-sum(@bar, 10, 20); +} diff --git a/tests/phpunit/data/less/module/dependency.less b/tests/phpunit/data/less/module/dependency.less new file mode 100644 index 00000000..c7725a25 --- /dev/null +++ b/tests/phpunit/data/less/module/dependency.less @@ -0,0 +1,3 @@ +@import "test.common.mixins"; + +@unitTestColor: green; diff --git a/tests/phpunit/data/less/module/styles.css b/tests/phpunit/data/less/module/styles.css new file mode 100644 index 00000000..b78780a9 --- /dev/null +++ b/tests/phpunit/data/less/module/styles.css @@ -0,0 +1,6 @@ +/* @noflip */ +.unit-tests { + color: green; + border: 2px solid #eeeeee; + line-height: 35; +} diff --git a/tests/phpunit/data/less/module/styles.less b/tests/phpunit/data/less/module/styles.less new file mode 100644 index 00000000..ecac8392 --- /dev/null +++ b/tests/phpunit/data/less/module/styles.less @@ -0,0 +1,6 @@ +@import "dependency"; + +/* @noflip */ +.unit-tests { + .test-mixin(@unitTestColor); +} diff --git a/tests/phpunit/data/xmp/7.result.php b/tests/phpunit/data/xmp/7.result.php index 0efcfa36..115cdc92 100644 --- a/tests/phpunit/data/xmp/7.result.php +++ b/tests/phpunit/data/xmp/7.result.php @@ -1,52 +1,52 @@ <?php -$result = array ( - 'xmp-exif' => - array ( - 'CameraOwnerName' => 'Me!', - ), - 'xmp-general' => - array ( - 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9', - 'ImageDescription' => - array ( - 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp', - '_type' => 'lang', - ), - 'ObjectName' => - array ( - 'x-default' => 'xmp core/xmp rights/cc ns test', - '_type' => 'lang', - ), - 'DateTimeDigitized' => '2005:04:03', - 'Software' => 'The one true editor: Vi (ok i used gimp)', - 'Identifier' => - array ( - 0 => 'http://example.com/identifierurl', - 1 => 'urn:sha1:342524abcdef', - '_type' => 'ul', - ), - 'Label' => 'Test image', - 'DateTimeMetadata' => '2011:05:12', - 'DateTime' => '2007:03:04 06:34:10', - 'Nickname' => 'My little xmp test image', - 'Rating' => '5', - 'RightsCertificate' => 'http://example.com/rights-certificate/', - 'Copyrighted' => 'True', - 'CopyrightOwner' => - array ( - 0 => 'Bawolff is copyright owner', - '_type' => 'ul', - ), - 'UsageTerms' => - array ( - 'x-default' => 'do whatever you want', - 'en-gb' => 'Do whatever you want in british english', - '_type' => 'lang', - ), - 'WebStatement' => 'http://example.com/web_statement', - ), - 'xmp-deprecated' => - array ( - 'Identifier' => 'http://example.com/identifierurl/wrong', - ), +$result = array( + 'xmp-exif' => + array( + 'CameraOwnerName' => 'Me!', + ), + 'xmp-general' => + array( + 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9', + 'ImageDescription' => + array( + 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp', + '_type' => 'lang', + ), + 'ObjectName' => + array( + 'x-default' => 'xmp core/xmp rights/cc ns test', + '_type' => 'lang', + ), + 'DateTimeDigitized' => '2005:04:03', + 'Software' => 'The one true editor: Vi (ok i used gimp)', + 'Identifier' => + array( + 0 => 'http://example.com/identifierurl', + 1 => 'urn:sha1:342524abcdef', + '_type' => 'ul', + ), + 'Label' => 'Test image', + 'DateTimeMetadata' => '2011:05:12', + 'DateTime' => '2007:03:04 06:34:10', + 'Nickname' => 'My little xmp test image', + 'Rating' => '5', + 'RightsCertificate' => 'http://example.com/rights-certificate/', + 'Copyrighted' => 'True', + 'CopyrightOwner' => + array( + 0 => 'Bawolff is copyright owner', + '_type' => 'ul', + ), + 'UsageTerms' => + array( + 'x-default' => 'do whatever you want', + 'en-gb' => 'Do whatever you want in british english', + '_type' => 'lang', + ), + 'WebStatement' => 'http://example.com/web_statement', + ), + 'xmp-deprecated' => + array( + 'Identifier' => 'http://example.com/identifierurl/wrong', + ), ); diff --git a/tests/phpunit/data/xmp/gps.result.php b/tests/phpunit/data/xmp/gps.result.php index 2d1243d5..8ea9c68c 100644 --- a/tests/phpunit/data/xmp/gps.result.php +++ b/tests/phpunit/data/xmp/gps.result.php @@ -9,4 +9,3 @@ $result = array( 'xmp-exif' => 'GPSVersionID' => '2.2.0.0' ) ); - diff --git a/tests/phpunit/docs/ExportDemoTest.php b/tests/phpunit/docs/ExportDemoTest.php index ce65d494..b09487a6 100644 --- a/tests/phpunit/docs/ExportDemoTest.php +++ b/tests/phpunit/docs/ExportDemoTest.php @@ -26,10 +26,13 @@ class ExportDemoTest extends DumpTestCase { $dom = new DomDocument(); $dom->load( $fname ); + // Ensure, the demo is for the current version + $this->assertEquals( $dom->documentElement->getAttribute( 'version' ), $version, 'export-demo.xml should have the current version' ); + try { $this->assertTrue( $dom->schemaValidate( "../../docs/export-" . $version . ".xsd" ), "schemaValidate has found an error" ); - } catch( Exception $e ) { + } catch ( Exception $e ) { $this->fail( "xml not valid against xsd: " . $e->getMessage() ); } } diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php index 17cee6e8..469d1d19 100644 --- a/tests/phpunit/includes/ArticleTablesTest.php +++ b/tests/phpunit/includes/ArticleTablesTest.php @@ -5,7 +5,7 @@ */ class ArticleTablesTest extends MediaWikiLangTestCase { - function testbug14404() { + public function testbug14404() { global $wgContLang, $wgLanguageCode, $wgLang; $title = Title::newFromText( 'Bug 14404' ); @@ -16,18 +16,17 @@ class ArticleTablesTest extends MediaWikiLangTestCase { $wgContLang = Language::factory( 'es' ); $wgLang = Language::factory( 'fr' ); - $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user ); + $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', 0, false, $user ); $templates1 = $title->getTemplateLinksFrom(); $wgLang = Language::factory( 'de' ); - $page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext + $page = WikiPage::factory( $title ); // In order to force the rerendering of the same wikitext // We need an edit, a purge is not enough to regenerate the tables - $status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user ); + $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', EDIT_UPDATE, false, $user ); $templates2 = $title->getTemplateLinksFrom(); $this->assertEquals( $templates1, $templates2 ); $this->assertEquals( $templates1[0]->getFullText(), 'Historial' ); } - } diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php index 846d2b86..b4d6dca6 100644 --- a/tests/phpunit/includes/ArticleTest.php +++ b/tests/phpunit/includes/ArticleTest.php @@ -2,31 +2,37 @@ class ArticleTest extends MediaWikiTestCase { - private $title; // holds a Title object - private $article; // holds an article + /** + * @var Title + */ + private $title; + /** + * @var Article + */ + private $article; /** creates a title object and its article object */ - function setUp() { - $this->title = Title::makeTitle( NS_MAIN, 'SomePage' ); + protected function setUp() { + parent::setUp(); + $this->title = Title::makeTitle( NS_MAIN, 'SomePage' ); $this->article = new Article( $this->title ); - } /** cleanup title object and its article object */ - function tearDown() { - $this->title = null; + protected function tearDown() { + parent::tearDown(); + $this->title = null; $this->article = null; - } - function testImplementsGetMagic() { + public function testImplementsGetMagic() { $this->assertEquals( false, $this->article->mLatest, "Article __get magic" ); } /** * @depends testImplementsGetMagic */ - function testImplementsSetMagic() { + public function testImplementsSetMagic() { $this->article->mLatest = 2; $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" ); } @@ -34,17 +40,17 @@ class ArticleTest extends MediaWikiTestCase { /** * @depends testImplementsSetMagic */ - function testImplementsCallMagic() { + public function testImplementsCallMagic() { $this->article->mLatest = 33; $this->article->mDataLoaded = true; $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" ); } - function testGetOrSetOnNewProperty() { + public function testGetOrSetOnNewProperty() { $this->article->ext_someNewProperty = 12; $this->assertEquals( 12, $this->article->ext_someNewProperty, "Article get/set magic on new field" ); - + $this->article->ext_someNewProperty = -8; $this->assertEquals( -8, $this->article->ext_someNewProperty, "Article get/set magic on update to new field" ); @@ -53,7 +59,11 @@ class ArticleTest extends MediaWikiTestCase { /** * Checks for the existence of the backwards compatibility static functions (forwarders to WikiPage class) */ - function testStaticFunctions() { + public function testStaticFunctions() { + $this->hideDeprecated( 'Article::getAutosummary' ); + $this->hideDeprecated( 'WikiPage::getAutosummary' ); + $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article + $this->assertEquals( WikiPage::selectFields(), Article::selectFields(), "Article static functions" ); $this->assertEquals( true, is_callable( "Article::onArticleCreate" ), @@ -66,15 +76,15 @@ class ArticleTest extends MediaWikiTestCase { "Article static functions" ); } - function testWikiPageFactory() { + public function testWikiPageFactory() { $title = Title::makeTitle( NS_FILE, 'Someimage.png' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiFilePage', get_class( $page ) ); - + $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiCategoryPage', get_class( $page ) ); - + $title = Title::makeTitle( NS_MAIN, 'SomePage' ); $page = WikiPage::factory( $title ); $this->assertEquals( 'WikiPage', get_class( $page ) ); diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php index 0c95b8d1..21de0985 100644 --- a/tests/phpunit/includes/BlockTest.php +++ b/tests/phpunit/includes/BlockTest.php @@ -11,17 +11,18 @@ class BlockTest extends MediaWikiLangTestCase { /* variable used to save up the blockID we insert in this test suite */ private $blockId; - function setUp() { - global $wgContLang; + protected function setUp() { parent::setUp(); - $wgContLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ) + ) ); } function addDBData() { - //$this->dumpBlocks(); $user = User::newFromName( 'UTBlockee' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTBlockeePassword' ); @@ -45,41 +46,41 @@ class BlockTest extends MediaWikiLangTestCase { // its value might change depending on the order the tests are run. // ApiBlockTest insert its own blocks! $newBlockId = $this->block->getId(); - if ($newBlockId) { + if ( $newBlockId ) { $this->blockId = $newBlockId; } else { throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" ); } + + $this->addXffBlocks(); } /** * debug function : dump the ipblocks table */ function dumpBlocks() { - $v = $this->db->query( 'SELECT * FROM unittest_ipblocks' ); + $v = $this->db->select( 'ipblocks', '*' ); print "Got " . $v->numRows() . " rows. Full dump follow:\n"; - foreach( $v as $row ) { + foreach ( $v as $row ) { print_r( $row ); } } - function testInitializerFunctionsReturnCorrectBlock() { + public function testInitializerFunctionsReturnCorrectBlock() { // $this->dumpBlocks(); - $this->assertTrue( $this->block->equals( Block::newFromTarget('UTBlockee') ), "newFromTarget() returns the same block as the one that was made"); - - $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made"); + $this->assertTrue( $this->block->equals( Block::newFromTarget( 'UTBlockee' ) ), "newFromTarget() returns the same block as the one that was made" ); + $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made" ); } /** * per bug 26425 */ - function testBug26425BlockTimestampDefaultsToTime() { + public function testBug26425BlockTimestampDefaultsToTime() { // delta to stop one-off errors when things happen to go over a second mark. $delta = abs( $this->madeAt - $this->block->mTimestamp ); - $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()"); - + $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()" ); } /** @@ -88,13 +89,13 @@ class BlockTest extends MediaWikiLangTestCase { * * This stopped working with r84475 and friends: regression being fixed for bug 29116. * - * @dataProvider dataBug29116 + * @dataProvider provideBug29116Data */ - function testBug29116LoadWithEmptyIp( $vagueTarget ) { + public function testBug29116LoadWithEmptyIp( $vagueTarget ) { $this->hideDeprecated( 'Block::load' ); $uid = User::idFromName( 'UTBlockee' ); - $this->assertTrue( ($uid > 0), 'Must be able to look up the target user during tests' ); + $this->assertTrue( ( $uid > 0 ), 'Must be able to look up the target user during tests' ); $block = new Block(); $ok = $block->load( $vagueTarget, $uid ); @@ -108,14 +109,14 @@ class BlockTest extends MediaWikiLangTestCase { * because the new function didn't accept empty strings like Block::load() * had. Regression bug 29116. * - * @dataProvider dataBug29116 + * @dataProvider provideBug29116Data */ - function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) { - $block = Block::newFromTarget('UTBlockee', $vagueTarget); + public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) { + $block = Block::newFromTarget( 'UTBlockee', $vagueTarget ); $this->assertTrue( $this->block->equals( $block ), "newFromTarget() returns the same block as the one that was made when given empty vagueTarget param " . var_export( $vagueTarget, true ) ); } - function dataBug29116() { + public static function provideBug29116Data() { return array( array( null ), array( '' ), @@ -123,14 +124,13 @@ class BlockTest extends MediaWikiLangTestCase { ); } - function testBlockedUserCanNotCreateAccount() { + public function testBlockedUserCanNotCreateAccount() { $username = 'BlockedUserToCreateAccountWith'; $u = User::newFromName( $username ); $u->setPassword( 'NotRandomPass' ); $u->addToDatabase(); unset( $u ); - // Sanity check $this->assertNull( Block::newFromTarget( $username ), @@ -166,7 +166,7 @@ class BlockTest extends MediaWikiLangTestCase { // Reload block from DB $userBlock = Block::newFromTarget( $username ); $this->assertTrue( - (bool) $block->prevents( 'createaccount' ), + (bool)$block->prevents( 'createaccount' ), "Block object in DB should prevents 'createaccount'" ); @@ -179,12 +179,12 @@ class BlockTest extends MediaWikiLangTestCase { // Reload user $u = User::newFromName( $username ); $this->assertTrue( - (bool) $u->isBlockedFromCreateAccount(), + (bool)$u->isBlockedFromCreateAccount(), "Our sandbox user '$username' should NOT be able to create account" ); } - function testCrappyCrossWikiBlocks() { + public function testCrappyCrossWikiBlocks() { // Delete the last round's block if it's still there $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' ); if ( $oldBlock ) { @@ -222,9 +222,133 @@ class BlockTest extends MediaWikiLangTestCase { $block = Block::newFromID( $res['id'] ); $this->assertEquals( 'UserOnForeignWiki', $block->getTarget()->getName(), 'Correct blockee name' ); - $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' ); + $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' ); $this->assertEquals( 'MetaWikiUser', $block->getBlocker(), 'Correct blocker name' ); $this->assertEquals( 'MetaWikiUser', $block->getByName(), 'Correct blocker name' ); $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' ); } + + protected function addXffBlocks() { + static $inited = false; + + if ( $inited ) { + return; + } + + $inited = true; + + $blockList = array( + array( 'target' => '70.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Hardblock', + 'ACDisable' => false, + 'isHardblock' => true, + 'isAutoBlocking' => false, + ), + array( 'target' => '2001:4860:4001::/48', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range6 Hardblock', + 'ACDisable' => false, + 'isHardblock' => true, + 'isAutoBlocking' => false, + ), + array( 'target' => '60.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Softblock with AC Disabled', + 'ACDisable' => true, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + array( 'target' => '50.2.0.0/16', + 'type' => Block::TYPE_RANGE, + 'desc' => 'Range Softblock', + 'ACDisable' => false, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + array( 'target' => '50.1.1.1', + 'type' => Block::TYPE_IP, + 'desc' => 'Exact Softblock', + 'ACDisable' => false, + 'isHardblock' => false, + 'isAutoBlocking' => false, + ), + ); + + foreach ( $blockList as $insBlock ) { + $target = $insBlock['target']; + + if ( $insBlock['type'] === Block::TYPE_IP ) { + $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName(); + } elseif ( $insBlock['type'] === Block::TYPE_RANGE ) { + $target = IP::sanitizeRange( $target ); + } + + $block = new Block(); + $block->setTarget( $target ); + $block->setBlocker( 'testblocker@global' ); + $block->mReason = $insBlock['desc']; + $block->mExpiry = 'infinity'; + $block->prevents( 'createaccount', $insBlock['ACDisable'] ); + $block->isHardblock( $insBlock['isHardblock'] ); + $block->isAutoblocking( $insBlock['isAutoBlocking'] ); + $block->insert(); + } + } + + public static function providerXff() { + return array( + array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5', + 'count' => 3, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Hardblock' + ), + array( 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5', + 'count' => 2, + 'result' => 'Exact Softblock' + ), + array( 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5', + 'count' => 1, + 'result' => 'Range Softblock with AC Disabled' + ), + array( 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5', + 'count' => 2, + 'result' => 'Range6 Hardblock' + ), + ); + } + + /** + * @dataProvider providerXff + */ + public function testBlocksOnXff( $xff, $exCount, $exResult ) { + $list = array_map( 'trim', explode( ',', $xff ) ); + $xffblocks = Block::getBlocksForIPList( $list, true ); + $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff ); + $block = Block::chooseBlock( $xffblocks, $list ); + $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff ); + } } diff --git a/tests/phpunit/includes/CdbTest.php b/tests/phpunit/includes/CdbTest.php index b5418dd7..e3d9da7c 100644 --- a/tests/phpunit/includes/CdbTest.php +++ b/tests/phpunit/includes/CdbTest.php @@ -3,23 +3,29 @@ /** * Test the CDB reader/writer */ - class CdbTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); if ( !CdbReader::haveExtension() ) { $this->markTestSkipped( 'Native CDB support is not available' ); } } + /** + * @group medium + */ public function testCdb() { $dir = wfTempDir(); if ( !is_writable( $dir ) ) { $this->markTestSkipped( "Temp dir isn't writable" ); } - $w1 = new CdbWriter_PHP( "$dir/php.cdb" ); - $w2 = new CdbWriter_DBA( "$dir/dba.cdb" ); + $phpcdbfile = $this->getNewTempFile(); + $dbacdbfile = $this->getNewTempFile(); + + $w1 = new CdbWriter_PHP( $phpcdbfile ); + $w2 = new CdbWriter_DBA( $dbacdbfile ); $data = array(); for ( $i = 0; $i < 1000; $i++ ) { @@ -37,13 +43,13 @@ class CdbTest extends MediaWikiTestCase { $w2->close(); $this->assertEquals( - md5_file( "$dir/dba.cdb" ), - md5_file( "$dir/php.cdb" ), + md5_file( $phpcdbfile ), + md5_file( $dbacdbfile ), 'same hash' ); - $r1 = new CdbReader_PHP( "$dir/php.cdb" ); - $r2 = new CdbReader_DBA( "$dir/dba.cdb" ); + $r1 = new CdbReader_PHP( $phpcdbfile ); + $r2 = new CdbReader_DBA( $dbacdbfile ); foreach ( $data as $key => $value ) { if ( $key === '' ) { @@ -60,9 +66,6 @@ class CdbTest extends MediaWikiTestCase { $this->cdbAssert( "PHP error", $key, $v1, $value ); $this->cdbAssert( "DBA error", $key, $v2, $value ); } - - unlink( "$dir/dba.cdb" ); - unlink( "$dir/php.cdb" ); } private function randomString() { @@ -71,6 +74,7 @@ class CdbTest extends MediaWikiTestCase { for ( $j = 0; $j < $len; $j++ ) { $s .= chr( mt_rand( 0, 255 ) ); } + return $s; } diff --git a/tests/phpunit/includes/CollationTest.php b/tests/phpunit/includes/CollationTest.php new file mode 100644 index 00000000..43bb3941 --- /dev/null +++ b/tests/phpunit/includes/CollationTest.php @@ -0,0 +1,111 @@ +<?php +class CollationTest extends MediaWikiLangTestCase { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'intl' ) ) { + $this->markTestSkipped( 'These tests require intl extension' ); + } + } + + /** + * Test to make sure, that if you + * have "X" and "XY", the binary + * sortkey also has "X" being a + * prefix of "XY". Our collation + * code makes this assumption. + * + * @param $lang String Language code for collator + * @param $base String Base string + * @param $extended String String containing base as a prefix. + * + * @dataProvider prefixDataProvider + */ + public function testIsPrefix( $lang, $base, $extended ) { + $cp = Collator::create( $lang ); + $cp->setStrength( Collator::PRIMARY ); + $baseBin = $cp->getSortKey( $base ); + // Remove sortkey terminator + $baseBin = rtrim( $baseBin, "\0" ); + $extendedBin = $cp->getSortKey( $extended ); + $this->assertStringStartsWith( $baseBin, $extendedBin, "$base is not a prefix of $extended" ); + } + + function prefixDataProvider() { + return array( + array( 'en', 'A', 'AA' ), + array( 'en', 'A', 'AAA' ), + array( 'en', 'Д', 'ДЂ' ), + array( 'en', 'Д', 'ДA' ), + // 'Ʒ' should expand to 'Z ' (note space). + array( 'fi', 'Z', 'Ʒ' ), + // 'Þ' should expand to 'th' + array( 'sv', 't', 'Þ' ), + // Javanese is a limited use alphabet, so should have 3 bytes + // per character, so do some tests with it. + array( 'en', 'ꦲ', 'ꦲꦤ' ), + array( 'en', 'ꦲ', 'ꦲД' ), + array( 'en', 'A', 'Aꦲ' ), + ); + } + + /** + * Opposite of testIsPrefix + * + * @dataProvider notPrefixDataProvider + */ + public function testNotIsPrefix( $lang, $base, $extended ) { + $cp = Collator::create( $lang ); + $cp->setStrength( Collator::PRIMARY ); + $baseBin = $cp->getSortKey( $base ); + // Remove sortkey terminator + $baseBin = rtrim( $baseBin, "\0" ); + $extendedBin = $cp->getSortKey( $extended ); + $this->assertStringStartsNotWith( $baseBin, $extendedBin, "$base is a prefix of $extended" ); + } + + function notPrefixDataProvider() { + return array( + array( 'en', 'A', 'B' ), + array( 'en', 'AC', 'ABC' ), + array( 'en', 'Z', 'Ʒ' ), + array( 'en', 'A', 'ꦲ' ), + ); + } + + /** + * Test correct first letter is fetched. + * + * @param $collation String Collation name (aka uca-en) + * @param $string String String to get first letter of + * @param $firstLetter String Expected first letter. + * + * @dataProvider firstLetterProvider + */ + public function testGetFirstLetter( $collation, $string, $firstLetter ) { + $col = Collation::factory( $collation ); + $this->assertEquals( $firstLetter, $col->getFirstLetter( $string ) ); + } + + function firstLetterProvider() { + return array( + array( 'uppercase', 'Abc', 'A' ), + array( 'uppercase', 'abc', 'A' ), + array( 'identity', 'abc', 'a' ), + array( 'uca-en', 'abc', 'A' ), + array( 'uca-en', ' ', ' ' ), + array( 'uca-en', 'Êveryone', 'E' ), + array( 'uca-vi', 'Êveryone', 'Ê' ), + // Make sure thorn is not a first letter. + array( 'uca-sv', 'The', 'T' ), + array( 'uca-sv', 'Å', 'Å' ), + array( 'uca-hu', 'dzsdo', 'Dzs' ), + array( 'uca-hu', 'dzdso', 'Dz' ), + array( 'uca-hu', 'CSD', 'Cs' ), + array( 'uca-root', 'CSD', 'C' ), + array( 'uca-fi', 'Ǥ', 'G' ), + array( 'uca-fi', 'Ŧ', 'T' ), + array( 'uca-fi', 'Ʒ', 'Z' ), + array( 'uca-fi', 'Ŋ', 'N' ), + ); + } +} diff --git a/tests/phpunit/includes/DiffHistoryBlobTest.php b/tests/phpunit/includes/DiffHistoryBlobTest.php index cdb6ed2f..a4d5b91a 100644 --- a/tests/phpunit/includes/DiffHistoryBlobTest.php +++ b/tests/phpunit/includes/DiffHistoryBlobTest.php @@ -1,34 +1,38 @@ <?php class DiffHistoryBlobTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { if ( !extension_loaded( 'xdiff' ) ) { $this->markTestSkipped( 'The xdiff extension is not available' ); + return; } if ( !function_exists( 'xdiff_string_rabdiff' ) ) { $this->markTestSkipped( 'The version of xdiff extension is lower than 1.5.0' ); + return; } - if ( !extension_loaded( 'hash' ) && !extension_loaded( 'mhash' ) ) { - $this->markTestSkipped( 'Neither the hash nor mhash extension is available' ); + if ( !extension_loaded( 'hash' ) ) { + $this->markTestSkipped( 'The hash extension is not available' ); + return; } + parent::setUp(); } /** * Test for DiffHistoryBlob::xdiffAdler32() * @dataProvider provideXdiffAdler32 */ - function testXdiffAdler32( $input ) { - $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 ); + public function testXdiffAdler32( $input ) { + $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 ); $dhb = new DiffHistoryBlob; $myHash = $dhb->xdiffAdler32( $input ); $this->assertSame( bin2hex( $xdiffHash ), bin2hex( $myHash ), "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) ); } - function provideXdiffAdler32() { + public static function provideXdiffAdler32() { return array( array( '', 'Empty string' ), array( "\0", 'Null' ), diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php index 8ecfd7e5..87272a4c 100644 --- a/tests/phpunit/includes/EditPageTest.php +++ b/tests/phpunit/includes/EditPageTest.php @@ -1,19 +1,25 @@ <?php /** - * @group Editing + * @group Editing + * + * @group Database + * ^--- tell jenkins this test needs the database + * + * @group medium + * ^--- tell phpunit that these test cases may take longer than 2 seconds. */ -class EditPageTest extends MediaWikiTestCase { +class EditPageTest extends MediaWikiLangTestCase { /** - * @dataProvider dataExtractSectionTitle + * @dataProvider provideExtractSectionTitle */ - function testExtractSectionTitle( $section, $title ) { + public function testExtractSectionTitle( $section, $title ) { $extracted = EditPage::extractSectionTitle( $section ); $this->assertEquals( $title, $extracted ); } - function dataExtractSectionTitle() { + public static function provideExtractSectionTitle() { return array( array( "== Test ==\n\nJust a test section.", @@ -37,4 +43,449 @@ class EditPageTest extends MediaWikiTestCase { ), ); } + + protected function forceRevisionDate( WikiPage $page, $timestamp ) { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->update( 'revision', + array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ), + array( 'rev_id' => $page->getLatest() ) ); + + $page->clear(); + } + + /** + * User input text is passed to rtrim() by edit page. This is a simple + * wrapper around assertEquals() which calls rrtrim() to normalize the + * expected and actual texts. + */ + function assertEditedTextEquals( $expected, $actual, $msg = '' ) { + return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg ); + } + + /** + * Performs an edit and checks the result. + * + * @param String|Title $title The title of the page to edit + * @param String|null $baseText Some text to create the page with before attempting the edit. + * @param User|String|null $user The user to perform the edit as. + * @param array $edit An array of request parameters used to define the edit to perform. + * Some well known fields are: + * * wpTextbox1: the text to submit + * * wpSummary: the edit summary + * * wpEditToken: the edit token (will be inserted if not provided) + * * wpEdittime: timestamp of the edit's base revision (will be inserted if not provided) + * * wpStarttime: timestamp when the edit started (will be inserted if not provided) + * * wpSectionTitle: the section to edit + * * wpMinorEdit: mark as minor edit + * * wpWatchthis: whether to watch the page + * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants). + * Set to null to skip the check. Defaults to EditPage::AS_OK. + * @param String|null $expectedText The text expected to be on the page after the edit. + * Set to null to skip the check. + * @param String|null $message An optional message to show along with any error message. + * + * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc. + */ + protected function assertEdit( $title, $baseText, $user = null, array $edit, + $expectedCode = EditPage::AS_OK, $expectedText = null, $message = null + ) { + if ( is_string( $title ) ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + } + + if ( is_string( $user ) ) { + $user = User::newFromName( $user ); + + if ( $user->getId() === 0 ) { + $user->addToDatabase(); + } + } + + $page = WikiPage::factory( $title ); + + if ( $baseText !== null ) { + $content = ContentHandler::makeContent( $baseText, $title ); + $page->doEditContent( $content, "base text for test" ); + $this->forceRevisionDate( $page, '20120101000000' ); + + //sanity check + $page->clear(); + $currentText = ContentHandler::getContentText( $page->getContent() ); + + # EditPage rtrim() the user input, so we alter our expected text + # to reflect that. + $this->assertEditedTextEquals( $baseText, $currentText ); + } + + if ( $user == null ) { + $user = $GLOBALS['wgUser']; + } else { + $this->setMwGlobals( 'wgUser', $user ); + } + + if ( !isset( $edit['wpEditToken'] ) ) { + $edit['wpEditToken'] = $user->getEditToken(); + } + + if ( !isset( $edit['wpEdittime'] ) ) { + $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : ''; + } + + if ( !isset( $edit['wpStarttime'] ) ) { + $edit['wpStarttime'] = wfTimestampNow(); + } + + $req = new FauxRequest( $edit, true ); // session ?? + + $ep = new EditPage( new Article( $title ) ); + $ep->setContextTitle( $title ); + $ep->importFormData( $req ); + + $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false; + + // this is where the edit happens! + // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut + // and throws exceptions like PermissionsError + $status = $ep->internalAttemptSave( $result, $bot ); + + if ( $expectedCode !== null ) { + // check edit code + $this->assertEquals( $expectedCode, $status->value, + "Expected result code mismatch. $message" ); + } + + $page = WikiPage::factory( $title ); + + if ( $expectedText !== null ) { + // check resulting page text + $content = $page->getContent(); + $text = ContentHandler::getContentText( $content ); + + # EditPage rtrim() the user input, so we alter our expected text + # to reflect that. + $this->assertEditedTextEquals( $expectedText, $text, + "Expected article text mismatch. $message" ); + } + + return $page; + } + + public function testCreatePage() { + $this->assertEdit( + 'EditPageTest_testCreatePage', + null, + null, + array( + 'wpTextbox1' => "Hello World!", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "Hello World!", + "expected article being created" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'EditPageTest_testCreatePage', + null, + null, + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected article not being created if empty" + ); + + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "Not January", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "Not January", + "expected MediaWiki: page being created" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'MediaWiki:EditPageTest_testCreatePage', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected not-registered MediaWiki: page not being created if empty" + ); + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_SUCCESS_NEW_ARTICLE, + "", + "expected registered MediaWiki: page being created even if empty" + )->doDeleteArticleReal( 'EditPageTest_testCreatePage' ); + + $this->assertEdit( + 'MediaWiki:Ipb-default-expiry', + null, + 'UTSysop', + array( + 'wpTextbox1' => "", + ), + EditPage::AS_BLANK_ARTICLE, + "", + "expected registered MediaWiki: page whose default content is empty not being created if empty" + ); + + $this->assertEdit( + 'MediaWiki:January', + null, + 'UTSysop', + array( + 'wpTextbox1' => "January", + ), + EditPage::AS_BLANK_ARTICLE, + null, + "expected MediaWiki: page not being created if text equals default message" + ); + } + + public function testUpdatePage() { + $text = "one"; + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => 'first update', + ); + + $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit, + EditPage::AS_SUCCESS_UPDATE, $text, + "expected successfull update with given text" ); + + $this->forceRevisionDate( $page, '20120101000000' ); + + $text = "two"; + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => 'second update', + ); + + $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit, + EditPage::AS_SUCCESS_UPDATE, $text, + "expected successfull update with given text" ); + } + + public static function provideSectionEdit() { + $text = 'Intro + +== one == +first section. + +== two == +second section. +'; + + $sectionOne = '== one == +hello +'; + + $newSection = '== new section == + +hello +'; + + $textWithNewSectionOne = preg_replace( + '/== one ==.*== two ==/ms', + "$sectionOne\n== two ==", $text + ); + + $textWithNewSectionAdded = "$text\n$newSection"; + + return array( + array( #0 + $text, + '', + 'hello', + 'replace all', + 'hello' + ), + + array( #1 + $text, + '1', + $sectionOne, + 'replace first section', + $textWithNewSectionOne, + ), + + array( #2 + $text, + 'new', + 'hello', + 'new section', + $textWithNewSectionAdded, + ), + ); + } + + /** + * @dataProvider provideSectionEdit + */ + public function testSectionEdit( $base, $section, $text, $summary, $expected ) { + $edit = array( + 'wpTextbox1' => $text, + 'wpSummary' => $summary, + 'wpSection' => $section, + ); + + $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit, + EditPage::AS_SUCCESS_UPDATE, $expected, + "expected successfull update of section" ); + } + + public static function provideAutoMerge() { + $tests = array(); + + $tests[] = array( #0: plain conflict + "Elmo", # base edit user + "one\n\ntwo\n\nthree\n", + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n", + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n", + ), + EditPage::AS_CONFLICT_DETECTED, # expected code + "ONE\n\ntwo\n\nthree\n", # expected text + 'expected edit conflict', # message + ); + + $tests[] = array( #1: successful merge + "Elmo", # base edit user + "one\n\ntwo\n\nthree\n", + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n", + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n", + ), + EditPage::AS_SUCCESS_UPDATE, # expected code + "ONE\n\ntwo\n\nTHREE\n", # expected text + 'expected automatic merge', # message + ); + + $text = "Intro\n\n"; + $text .= "== first section ==\n\n"; + $text .= "one\n\ntwo\n\nthree\n\n"; + $text .= "== second section ==\n\n"; + $text .= "four\n\nfive\n\nsix\n\n"; + + // extract the first section. + $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text ); + + // generate expected text after merge + $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) ); + + $tests[] = array( #2: merge in section + "Elmo", # base edit user + $text, + array( #adam's edit + 'wpStarttime' => 1, + 'wpTextbox1' => str_replace( 'one', 'ONE', $section ), + 'wpSection' => '1' + ), + array( #berta's edit + 'wpStarttime' => 2, + 'wpTextbox1' => str_replace( 'three', 'THREE', $section ), + 'wpSection' => '1' + ), + EditPage::AS_SUCCESS_UPDATE, # expected code + $expected, # expected text + 'expected automatic section merge', # message + ); + + // see whether it makes a difference who did the base edit + $testsWithAdam = array_map( function ( $test ) { + $test[0] = 'Adam'; // change base edit user + return $test; + }, $tests ); + + $testsWithBerta = array_map( function ( $test ) { + $test[0] = 'Berta'; // change base edit user + return $test; + }, $tests ); + + return array_merge( $tests, $testsWithAdam, $testsWithBerta ); + } + + /** + * @dataProvider provideAutoMerge + */ + public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit, + $expectedCode, $expectedText, $message = null + ) { + $this->checkHasDiff3(); + + //create page + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns ); + $page = WikiPage::factory( $title ); + + if ( $page->exists() ) { + $page->doDeleteArticle( "clean slate for testing" ); + } + + $baseEdit = array( + 'wpTextbox1' => $text, + ); + + $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null, + $baseUser, $baseEdit, null, null, __METHOD__ ); + + $this->forceRevisionDate( $page, '20120101000000' ); + + $edittime = $page->getTimestamp(); + + // start timestamps for conflict detection + if ( !isset( $adamsEdit['wpStarttime'] ) ) { + $adamsEdit['wpStarttime'] = 1; + } + + if ( !isset( $bertasEdit['wpStarttime'] ) ) { + $bertasEdit['wpStarttime'] = 2; + } + + $starttime = wfTimestampNow(); + $adamsTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime'] ); + $bertasTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime'] ); + + $adamsEdit['wpStarttime'] = $adamsTime; + $bertasEdit['wpStarttime'] = $bertasTime; + + $adamsEdit['wpSummary'] = 'Adam\'s edit'; + $bertasEdit['wpSummary'] = 'Bertas\'s edit'; + + $adamsEdit['wpEdittime'] = $edittime; + $bertasEdit['wpEdittime'] = $edittime; + + // first edit + $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit, + EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" ); + + // second edit + $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit, + $expectedCode, $expectedText, $message ); + } } diff --git a/tests/phpunit/includes/ExternalStoreTest.php b/tests/phpunit/includes/ExternalStoreTest.php index 92ec7344..fcffcbc2 100644 --- a/tests/phpunit/includes/ExternalStoreTest.php +++ b/tests/phpunit/includes/ExternalStoreTest.php @@ -4,29 +4,78 @@ */ class ExternalStoreTest extends MediaWikiTestCase { - private $saved_wgExternalStores; - function setUp() { - global $wgExternalStores; - $this->saved_wgExternalStores = $wgExternalStores ; - } + public function testExternalFetchFromURL() { + $this->setMwGlobals( 'wgExternalStores', false ); - function tearDown() { - global $wgExternalStores; - $wgExternalStores = $this->saved_wgExternalStores ; - } + $this->assertFalse( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Deny if wgExternalStores is not set to a non-empty array' + ); - function testExternalStoreDoesNotFetchIncorrectURL() { - global $wgExternalStores; - $wgExternalStores = true; + $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) ); + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/200' ), + 'Hello', + 'Allow FOO://cluster1/200' + ); + $this->assertEquals( + ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ), + 'Hello', + 'Allow FOO://cluster1/300/0' + ); # Assertions for r68900 $this->assertFalse( - ExternalStore::fetchFromURL( 'http://' ) ); + ExternalStore::fetchFromURL( 'ftp.example.org' ), + 'Deny domain ftp.example.org' + ); $this->assertFalse( - ExternalStore::fetchFromURL( 'ftp.wikimedia.org' ) ); + ExternalStore::fetchFromURL( '/example.txt' ), + 'Deny path /example.txt' + ); $this->assertFalse( - ExternalStore::fetchFromURL( '/super.txt' ) ); + ExternalStore::fetchFromURL( 'http://' ), + 'Deny protocol http://' + ); } } +class ExternalStoreFOO { + + protected $data = array( + 'cluster1' => array( + '200' => 'Hello', + '300' => array( + 'Hello', 'World', + ), + ), + ); + + /** + * Fetch data from given URL + * @param $url String: an url of the form FOO://cluster/id or FOO://cluster/id/itemid. + * @return mixed + */ + function fetchFromURL( $url ) { + // Based on ExternalStoreDB + $path = explode( '/', $url ); + $cluster = $path[2]; + $id = $path[3]; + if ( isset( $path[4] ) ) { + $itemID = $path[4]; + } else { + $itemID = false; + } + + if ( !isset( $this->data[$cluster][$id] ) ) { + return null; + } + + if ( $itemID !== false && is_array( $this->data[$cluster][$id] ) && isset( $this->data[$cluster][$id][$itemID] ) ) { + return $this->data[$cluster][$id][$itemID]; + } + + return $this->data[$cluster][$id]; + } +} diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php index 903a6d25..6c67beb1 100644 --- a/tests/phpunit/includes/ExtraParserTest.php +++ b/tests/phpunit/includes/ExtraParserTest.php @@ -5,20 +5,21 @@ */ class ExtraParserTest extends MediaWikiTestCase { - function setUp() { - global $wgMemc; - global $wgContLang; - global $wgShowDBErrorBacktrace; - global $wgLanguageCode; - global $wgAlwaysUseTidy; - - $wgShowDBErrorBacktrace = true; - $wgLanguageCode = 'en'; - $wgContLang = new Language( 'en' ); - $wgMemc = new EmptyBagOStuff; - $wgAlwaysUseTidy = false; - - $this->options = new ParserOptions; + protected function setUp() { + parent::setUp(); + + $contLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgShowDBErrorBacktrace' => true, + 'wgLanguageCode' => 'en', + 'wgContLang' => $contLang, + 'wgLang' => Language::factory( 'en' ), + 'wgMemc' => new EmptyBagOStuff, + 'wgAlwaysUseTidy' => false, + 'wgCleanSignatures' => true, + ) ); + + $this->options = ParserOptions::newFromUserAndLang( new User, $contLang ); $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) ); $this->parser = new Parser; @@ -26,117 +27,104 @@ class ExtraParserTest extends MediaWikiTestCase { } // Bug 8689 - Long numeric lines kill the parser - function testBug8689() { - global $wgLang; + public function testBug8689() { global $wgUser; $longLine = '1.' . str_repeat( '1234567890', 100000 ) . "\n"; - if ( $wgLang === null ) $wgLang = new Language; - $t = Title::newFromText( 'Unit test' ); $options = ParserOptions::newFromUser( $wgUser ); $this->assertEquals( "<p>$longLine</p>", $this->parser->parse( $longLine, $t, $options )->getText() ); } - + /* Test the parser entry points */ - function testParse() { + public function testParse() { $title = Title::newFromText( __FUNCTION__ ); - $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}" , $title, $this->options ); + $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options ); $this->assertEquals( "<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>", $parserOutput->getText() ); } - - function testPreSaveTransform() { + + public function testPreSaveTransform() { global $wgUser; $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->preSaveTransform( "Test\r\n{{subst:Foo}}\n{{Bar}}", $title, $wgUser, $this->options ); $this->assertEquals( "Test\nContent of ''Template:Foo''\n{{Bar}}", $outputText ); } - - function testPreprocess() { + + public function testPreprocess() { $title = Title::newFromText( __FUNCTION__ ); - $outputText = $this->parser->preprocess( "Test\n{{Foo}}\n{{Bar}}" , $title, $this->options ); - + $outputText = $this->parser->preprocess( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options ); + $this->assertEquals( "Test\nContent of ''Template:Foo''\nContent of ''Template:Bar''", $outputText ); } - + /** * cleanSig() makes all templates substs and removes tildes */ - function testCleanSig() { - global $wgCleanSignatures; - $oldCleanSignature = $wgCleanSignatures; - $wgCleanSignatures = true; - + public function testCleanSig() { $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); - $wgCleanSignatures = $oldCleanSignature; - $this->assertEquals( "{{SUBST:Foo}} ", $outputText ); } /** * cleanSig() should do nothing if disabled */ - function testCleanSigDisabled() { - global $wgCleanSignatures; - $oldCleanSignature = $wgCleanSignatures; - $wgCleanSignatures = false; + public function testCleanSigDisabled() { + $this->setMwGlobals( 'wgCleanSignatures', false ); $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" ); - $wgCleanSignatures = $oldCleanSignature; - $this->assertEquals( "{{Foo}} ~~~~", $outputText ); } - + /** * cleanSigInSig() just removes tildes * @dataProvider provideStringsForCleanSigInSig */ - function testCleanSigInSig( $in, $out ) { - $this->assertEquals( Parser::cleanSigInSig( $in), $out ); + public function testCleanSigInSig( $in, $out ) { + $this->assertEquals( Parser::cleanSigInSig( $in ), $out ); } - - function provideStringsForCleanSigInSig() { + + public static function provideStringsForCleanSigInSig() { return array( array( "{{Foo}} ~~~~", "{{Foo}} " ), array( "~~~", "" ), array( "~~~~~", "" ), ); } - - function testGetSection() { + + public function testGetSection() { $outputText2 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 2 ); $outputText1 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1 ); - + $this->assertEquals( "=== Heading 2 ===\nSection 2", $outputText2 ); $this->assertEquals( "== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2", $outputText1 ); } - - function testReplaceSection() { + + public function testReplaceSection() { $outputText = $this->parser->replaceSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1, "New section 1" ); - + $this->assertEquals( "Section 0\nNew section 1\n\n== Heading 3 ==\nSection 3", $outputText ); } - + /** * Templates and comments are not affected, but noinclude/onlyinclude is. */ - function testGetPreloadText() { + public function testGetPreloadText() { $title = Title::newFromText( __FUNCTION__ ); $outputText = $this->parser->getPreloadText( "{{Foo}}<noinclude> censored</noinclude> information <!-- is very secret -->", $title, $this->options ); - + $this->assertEquals( "{{Foo}} information <!-- is very secret -->", $outputText ); } - - static function statelessFetchTemplate( $title, $parser=false ) { + + static function statelessFetchTemplate( $title, $parser = false ) { $text = "Content of ''" . $title->getFullText() . "''"; $deps = array(); - + return array( 'text' => $text, 'finalTitle' => $title, @@ -146,12 +134,12 @@ class ExtraParserTest extends MediaWikiTestCase { /** * @group Database */ - function testTrackingCategory() { + public function testTrackingCategory() { $title = Title::newFromText( __FUNCTION__ ); - $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text(); + $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text(); $cat = Title::makeTitleSafe( NS_CATEGORY, $catName ); $expected = array( $cat->getDBkey() ); - $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]", $title, $this->options ); $result = $parserOutput->getCategoryLinks(); $this->assertEquals( $expected, $result ); } @@ -159,11 +147,11 @@ class ExtraParserTest extends MediaWikiTestCase { /** * @group Database */ - function testTrackingCategorySpecial() { + public function testTrackingCategorySpecial() { // Special pages shouldn't have tracking cats. $title = SpecialPage::getTitleFor( 'Contributions' ); - $parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options ); + $parserOutput = $this->parser->parse( "[[file:nonexistent]]", $title, $this->options ); $result = $parserOutput->getCategoryLinks(); $this->assertEmpty( $result ); } - } +} diff --git a/tests/phpunit/includes/FallbackTest.php b/tests/phpunit/includes/FallbackTest.php new file mode 100644 index 00000000..f408f471 --- /dev/null +++ b/tests/phpunit/includes/FallbackTest.php @@ -0,0 +1,73 @@ +<?php + +/** + * @covers Fallback + */ +class FallbackTest extends MediaWikiTestCase { + + public function testFallbackMbstringFunctions() { + + if ( !extension_loaded( 'mbstring' ) ) { + $this->markTestSkipped( "The mb_string functions must be installed to test the fallback functions" ); + } + + $sampleUTF = "Östergötland_coat_of_arms.png"; + + //mb_substr + $substr_params = array( + array( 0, 0 ), + array( 5, -4 ), + array( 33 ), + array( 100, -5 ), + array( -8, 10 ), + array( 1, 1 ), + array( 2, -1 ) + ); + + foreach ( $substr_params as $param_set ) { + $old_param_set = $param_set; + array_unshift( $param_set, $sampleUTF ); + + $this->assertEquals( + call_user_func_array( 'mb_substr', $param_set ), + call_user_func_array( 'Fallback::mb_substr', $param_set ), + 'Fallback mb_substr with params ' . implode( ', ', $old_param_set ) + ); + } + + //mb_strlen + $this->assertEquals( + mb_strlen( $sampleUTF ), + Fallback::mb_strlen( $sampleUTF ), + 'Fallback mb_strlen' + ); + + //mb_str(r?)pos + $strpos_params = array( + //array( 'ter' ), + //array( 'Ö' ), + //array( 'Ö', 3 ), + //array( 'oat_', 100 ), + //array( 'c', -10 ), + //Broken for now + ); + + foreach ( $strpos_params as $param_set ) { + $old_param_set = $param_set; + array_unshift( $param_set, $sampleUTF ); + + $this->assertEquals( + call_user_func_array( 'mb_strpos', $param_set ), + call_user_func_array( 'Fallback::mb_strpos', $param_set ), + 'Fallback mb_strpos with params ' . implode( ', ', $old_param_set ) + ); + + $this->assertEquals( + call_user_func_array( 'mb_strrpos', $param_set ), + call_user_func_array( 'Fallback::mb_strrpos', $param_set ), + 'Fallback mb_strrpos with params ' . implode( ', ', $old_param_set ) + ); + } + } + +}
\ No newline at end of file diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php new file mode 100644 index 00000000..9f3aa11d --- /dev/null +++ b/tests/phpunit/includes/FauxRequestTest.php @@ -0,0 +1,15 @@ +<?php + +class FauxRequestTest extends MediaWikiTestCase { + + public function testGetSetHeader() { + $value = 'test/test'; + + $request = new FauxRequest(); + $request->setHeader( 'Content-Type', $value ); + + $this->assertEquals( $request->getHeader( 'Content-Type' ), $value ); + $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value ); + $this->assertEquals( $request->getHeader( 'content-type' ), $value ); + } +} diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php index c0420049..f9ba1b3b 100644 --- a/tests/phpunit/includes/FauxResponseTest.php +++ b/tests/phpunit/includes/FauxResponseTest.php @@ -25,17 +25,18 @@ class FauxResponseTest extends MediaWikiTestCase { var $response; - function setUp() { + protected function setUp() { + parent::setUp(); $this->response = new FauxResponse; } - function testCookie() { + public function testCookie() { $this->assertEquals( null, $this->response->getcookie( 'key' ), 'Non-existing cookie' ); $this->response->setcookie( 'key', 'val' ); $this->assertEquals( 'val', $this->response->getcookie( 'key' ), 'Existing cookie' ); } - function testHeader() { + public function testHeader() { $this->assertEquals( null, $this->response->getheader( 'Location' ), 'Non-existing header' ); $this->response->header( 'Location: http://localhost/' ); @@ -46,9 +47,12 @@ class FauxResponseTest extends MediaWikiTestCase { $this->response->header( 'Location: http://127.0.0.2/', false ); $this->assertEquals( 'http://127.0.0.1/', $this->response->getheader( 'Location' ), 'Same header with override disabled' ); + + $this->response->header( 'Location: http://localhost/' ); + $this->assertEquals( 'http://localhost/', $this->response->getheader( 'LOCATION' ), 'Get header case insensitive' ); } - function testResponseCode() { + public function testResponseCode() { $this->response->header( 'HTTP/1.1 200' ); $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' ); diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php index d86c95d7..fb2304dc 100644 --- a/tests/phpunit/includes/FormOptionsInitializationTest.php +++ b/tests/phpunit/includes/FormOptionsInitializationTest.php @@ -41,45 +41,44 @@ class FormOptionsInitializationTest extends MediaWikiTestCase { * with. */ protected function setUp() { + parent::setUp(); $this->object = new FormOptionsExposed(); - } public function testAddStringOption() { - $this->object->add( 'foo', 'string value' ); + $this->object->add( 'foo', 'string value' ); $this->assertEquals( array( 'foo' => array( - 'default' => 'string value', + 'default' => 'string value', 'consumed' => false, - 'type' => FormOptions::STRING, - 'value' => null, - ) + 'type' => FormOptions::STRING, + 'value' => null, + ) ), $this->object->getOptions() ); } public function testAddIntegers() { - $this->object->add( 'one', 1 ); - $this->object->add( 'negone', -1 ); + $this->object->add( 'one', 1 ); + $this->object->add( 'negone', -1 ); $this->assertEquals( array( 'negone' => array( - 'default' => -1, - 'value' => null, + 'default' => -1, + 'value' => null, 'consumed' => false, - 'type' => FormOptions::INT, - ), + 'type' => FormOptions::INT, + ), 'one' => array( - 'default' => 1, - 'value' => null, + 'default' => 1, + 'value' => null, 'consumed' => false, - 'type' => FormOptions::INT, - ) + 'type' => FormOptions::INT, + ) ), $this->object->getOptions() ); } - } diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php index 749343ec..0a13cfec 100644 --- a/tests/phpunit/includes/FormOptionsTest.php +++ b/tests/phpunit/includes/FormOptionsTest.php @@ -23,17 +23,18 @@ class FormOptionsTest extends MediaWikiTestCase { protected $object; /** - * Instanciates a FormOptions object to play with. + * Instanciates a FormOptions object to play with. * FormOptions::add() is tested by the class FormOptionsInitializationTest * so we assume the function is well tested already an use it to create * the fixture. */ protected function setUp() { + parent::setUp(); $this->object = new FormOptions; $this->object->add( 'string1', 'string one' ); $this->object->add( 'string2', 'string two' ); - $this->object->add( 'integer', 0 ); - $this->object->add( 'intnull', 0, FormOptions::INTNULL ); + $this->object->add( 'integer', 0 ); + $this->object->add( 'intnull', 0, FormOptions::INTNULL ); } /** Helpers for testGuessType() */ @@ -61,30 +62,30 @@ class FormOptionsTest extends MediaWikiTestCase { * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString */ public function testGuessTypeDetection() { - $this->assertGuessBoolean( true ); + $this->assertGuessBoolean( true ); $this->assertGuessBoolean( false ); - $this->assertGuessInt( 0 ); - $this->assertGuessInt( -5 ); - $this->assertGuessInt( 5 ); + $this->assertGuessInt( 0 ); + $this->assertGuessInt( -5 ); + $this->assertGuessInt( 5 ); $this->assertGuessInt( 0x0F ); - $this->assertGuessString( 'true' ); - $this->assertGuessString( 'false' ); - $this->assertGuessString( '5' ); - $this->assertGuessString( '0' ); + $this->assertGuessString( 'true' ); + $this->assertGuessString( 'false' ); + $this->assertGuessString( '5' ); + $this->assertGuessString( '0' ); } /** - * @expectedException MWException + * @expectedException MWException */ public function testGuessTypeOnArrayThrowException() { - $this->object->guessType( array( 'foo' ) ); + $this->object->guessType( array( 'foo' ) ); } /** - * @expectedException MWException + * @expectedException MWException */ public function testGuessTypeOnNullThrowException() { - $this->object->guessType( null ); + $this->object->guessType( null ); } } diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php index 9097d301..6154df1d 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php @@ -1,67 +1,92 @@ <?php class GlobalTest extends MediaWikiTestCase { - function setUp() { - global $wgReadOnlyFile, $wgUrlProtocols; - $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile; - $this->originals['wgUrlProtocols'] = $wgUrlProtocols; - $wgReadOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" ); - $wgUrlProtocols[] = 'file://'; - unlink( $wgReadOnlyFile ); + protected function setUp() { + parent::setUp(); + + $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" ); + unlink( $readOnlyFile ); + + $this->setMwGlobals( array( + 'wgReadOnlyFile' => $readOnlyFile, + 'wgUrlProtocols' => array( + 'http://', + 'https://', + 'mailto:', + '//', + 'file://', # Non-default + ), + ) ); } - function tearDown() { - global $wgReadOnlyFile, $wgUrlProtocols; + protected function tearDown() { + global $wgReadOnlyFile; + if ( file_exists( $wgReadOnlyFile ) ) { unlink( $wgReadOnlyFile ); } - $wgReadOnlyFile = $this->originals['wgReadOnlyFile']; - $wgUrlProtocols = $this->originals['wgUrlProtocols']; + + parent::tearDown(); } - /** @dataProvider provideForWfArrayDiff2 */ + /** + * @dataProvider provideForWfArrayDiff2 + * @covers ::wfArrayDiff2 + */ public function testWfArrayDiff2( $a, $b, $expected ) { $this->assertEquals( - wfArrayDiff2( $a, $b), $expected + wfArrayDiff2( $a, $b ), $expected ); } // @todo Provide more tests - public function provideForWfArrayDiff2() { + public static function provideForWfArrayDiff2() { // $a $b $expected return array( array( - array( 'a', 'b'), - array( 'a', 'b'), + array( 'a', 'b' ), + array( 'a', 'b' ), array(), ), array( - array( array( 'a'), array( 'a', 'b', 'c' )), - array( array( 'a'), array( 'a', 'b' )), + array( array( 'a' ), array( 'a', 'b', 'c' ) ), + array( array( 'a' ), array( 'a', 'b' ) ), array( 1 => array( 'a', 'b', 'c' ) ), ), ); } - function testRandom() { + /** + * @covers ::wfRandom + */ + public function testRandom() { # This could hypothetically fail, but it shouldn't ;) $this->assertFalse( wfRandom() == wfRandom() ); } - function testUrlencode() { + /** + * @covers ::wfUrlencode + */ + public function testUrlencode() { $this->assertEquals( "%E7%89%B9%E5%88%A5:Contributions/Foobar", wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) ); } - function testExpandIRI() { + /** + * @covers ::wfExpandIRI + */ + public function testExpandIRI() { $this->assertEquals( "https://te.wikibooks.org/wiki/ఉబుంటు_వాడుకరి_మార్గదర్శని", wfExpandIRI( "https://te.wikibooks.org/wiki/%E0%B0%89%E0%B0%AC%E0%B1%81%E0%B0%82%E0%B0%9F%E0%B1%81_%E0%B0%B5%E0%B0%BE%E0%B0%A1%E0%B1%81%E0%B0%95%E0%B0%B0%E0%B0%BF_%E0%B0%AE%E0%B0%BE%E0%B0%B0%E0%B1%8D%E0%B0%97%E0%B0%A6%E0%B0%B0%E0%B1%8D%E0%B0%B6%E0%B0%A8%E0%B0%BF" ) ); } - function testReadOnlyEmpty() { + /** + * @covers ::wfReadOnly + */ + public function testReadOnlyEmpty() { global $wgReadOnly; $wgReadOnly = null; @@ -69,7 +94,10 @@ class GlobalTest extends MediaWikiTestCase { $this->assertFalse( wfReadOnly() ); } - function testReadOnlySet() { + /** + * @covers ::wfReadOnly + */ + public function testReadOnlySet() { global $wgReadOnly, $wgReadOnlyFile; $f = fopen( $wgReadOnlyFile, "wt" ); @@ -87,20 +115,7 @@ class GlobalTest extends MediaWikiTestCase { $this->assertFalse( wfReadOnly() ); } - function testQuotedPrintable() { - $this->assertEquals( - "=?UTF-8?Q?=C4=88u=20legebla=3F?=", - UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); - } - - function testTime() { - $start = wfTime(); - $this->assertInternalType( 'float', $start ); - $end = wfTime(); - $this->assertTrue( $end > $start, "Time is running backwards!" ); - } - - function dataArrayToCGI() { + public static function provideArrayToCGI() { return array( array( array(), '' ), // empty array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test @@ -119,22 +134,26 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataArrayToCGI + * @dataProvider provideArrayToCGI + * @covers ::wfArrayToCgi */ - function testArrayToCGI( $array, $result ) { - $this->assertEquals( $result, wfArrayToCGI( $array ) ); + public function testArrayToCGI( $array, $result ) { + $this->assertEquals( $result, wfArrayToCgi( $array ) ); } - function testArrayToCGI2() { + /** + * @covers ::testWfArrayDiff2 + */ + public function testArrayToCGI2() { $this->assertEquals( "baz=bar&foo=bar", - wfArrayToCGI( + wfArrayToCgi( array( 'baz' => 'bar' ), array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) ); } - function dataCgiToArray() { + public static function provideCgiToArray() { return array( array( '', array() ), // empty array( 'foo=bar', array( 'foo' => 'bar' ) ), // string @@ -150,13 +169,14 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataCgiToArray + * @dataProvider provideCgiToArray + * @covers ::wfCgiToArray */ - function testCgiToArray( $cgi, $result ) { + public function testCgiToArray( $cgi, $result ) { $this->assertEquals( $result, wfCgiToArray( $cgi ) ); } - function dataCgiRoundTrip() { + public static function provideCgiRoundTrip() { return array( array( '' ), array( 'foo=bar' ), @@ -170,166 +190,104 @@ class GlobalTest extends MediaWikiTestCase { } /** - * @dataProvider dataCgiRoundTrip + * @dataProvider provideCgiRoundTrip + * @covers ::wfArrayToCgi */ - function testCgiRoundTrip( $cgi ) { - $this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) ); + public function testCgiRoundTrip( $cgi ) { + $this->assertEquals( $cgi, wfArrayToCgi( wfCgiToArray( $cgi ) ) ); } - function testMimeTypeMatch() { + /** + * @covers ::mimeTypeMatch + */ + public function testMimeTypeMatch() { $this->assertEquals( 'text/html', mimeTypeMatch( 'text/html', array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.3 ) ) ); + 'text/html' => 0.7, + 'text/plain' => 0.3 ) ) ); $this->assertEquals( 'text/*', mimeTypeMatch( 'text/html', array( 'image/*' => 1.0, - 'text/*' => 0.5 ) ) ); + 'text/*' => 0.5 ) ) ); $this->assertEquals( '*/*', mimeTypeMatch( 'text/html', array( '*/*' => 1.0 ) ) ); $this->assertNull( mimeTypeMatch( 'text/html', - array( 'image/png' => 1.0, - 'image/svg+xml' => 0.5 ) ) ); + array( 'image/png' => 1.0, + 'image/svg+xml' => 0.5 ) ) ); } - function testNegotiateType() { + /** + * @covers ::wfNegotiateType + */ + public function testNegotiateType() { $this->assertEquals( 'text/html', wfNegotiateType( array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.5, - 'text/*' => 0.2 ), - array( 'text/html' => 1.0 ) ) ); + 'text/html' => 0.7, + 'text/plain' => 0.5, + 'text/*' => 0.2 ), + array( 'text/html' => 1.0 ) ) ); $this->assertEquals( 'application/xhtml+xml', wfNegotiateType( array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.7, - 'text/plain' => 0.5, - 'text/*' => 0.2 ), + 'text/html' => 0.7, + 'text/plain' => 0.5, + 'text/*' => 0.2 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertEquals( 'text/html', wfNegotiateType( - array( 'text/html' => 1.0, - 'text/plain' => 0.5, - 'text/*' => 0.5, - 'application/xhtml+xml' => 0.2 ), + array( 'text/html' => 1.0, + 'text/plain' => 0.5, + 'text/*' => 0.5, + 'application/xhtml+xml' => 0.2 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertEquals( 'text/html', wfNegotiateType( - array( 'text/*' => 1.0, - 'image/*' => 0.7, - '*/*' => 0.3 ), + array( 'text/*' => 1.0, + 'image/*' => 0.7, + '*/*' => 0.3 ), array( 'application/xhtml+xml' => 1.0, - 'text/html' => 0.5 ) ) ); + 'text/html' => 0.5 ) ) ); $this->assertNull( wfNegotiateType( - array( 'text/*' => 1.0 ), + array( 'text/*' => 1.0 ), array( 'application/xhtml+xml' => 1.0 ) ) ); } - - function testFallbackMbstringFunctions() { - - if( !extension_loaded( 'mbstring' ) ) { - $this->markTestSkipped( "The mb_string functions must be installed to test the fallback functions" ); - } - - $sampleUTF = "Östergötland_coat_of_arms.png"; - - - //mb_substr - $substr_params = array( - array( 0, 0 ), - array( 5, -4 ), - array( 33 ), - array( 100, -5 ), - array( -8, 10 ), - array( 1, 1 ), - array( 2, -1 ) - ); - - foreach( $substr_params as $param_set ) { - $old_param_set = $param_set; - array_unshift( $param_set, $sampleUTF ); - - $this->assertEquals( - MWFunction::callArray( 'mb_substr', $param_set ), - MWFunction::callArray( 'Fallback::mb_substr', $param_set ), - 'Fallback mb_substr with params ' . implode( ', ', $old_param_set ) - ); - } - - - //mb_strlen - $this->assertEquals( - mb_strlen( $sampleUTF ), - Fallback::mb_strlen( $sampleUTF ), - 'Fallback mb_strlen' - ); - - - //mb_str(r?)pos - $strpos_params = array( - //array( 'ter' ), - //array( 'Ö' ), - //array( 'Ö', 3 ), - //array( 'oat_', 100 ), - //array( 'c', -10 ), - //Broken for now - ); - - foreach( $strpos_params as $param_set ) { - $old_param_set = $param_set; - array_unshift( $param_set, $sampleUTF ); - - $this->assertEquals( - MWFunction::callArray( 'mb_strpos', $param_set ), - MWFunction::callArray( 'Fallback::mb_strpos', $param_set ), - 'Fallback mb_strpos with params ' . implode( ', ', $old_param_set ) - ); - - $this->assertEquals( - MWFunction::callArray( 'mb_strrpos', $param_set ), - MWFunction::callArray( 'Fallback::mb_strrpos', $param_set ), - 'Fallback mb_strrpos with params ' . implode( ', ', $old_param_set ) - ); - } - - } - - - function testDebugFunctionTest() { - + + /** + * @covers ::wfDebug + * @covers ::wfDebugMem + */ + public function testDebugFunctionTest() { + global $wgDebugLogFile, $wgDebugTimestamps; - + $old_log_file = $wgDebugLogFile; $wgDebugLogFile = tempnam( wfTempDir(), 'mw-' ); - # @todo FIXME: This setting should be tested + # @todo FIXME: $wgDebugTimestamps should be tested + $old_wgDebugTimestamps = $wgDebugTimestamps; $wgDebugTimestamps = false; - - - + wfDebug( "This is a normal string" ); $this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); - - + wfDebug( "This is nöt an ASCII string" ); $this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); - - + wfDebug( "\00305This has böth UTF and control chars\003" ); $this->assertEquals( " 05This has böth UTF and control chars ", file_get_contents( $wgDebugLogFile ) ); unlink( $wgDebugLogFile ); @@ -337,19 +295,20 @@ class GlobalTest extends MediaWikiTestCase { wfDebugMem(); $this->assertGreaterThan( 5000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) ); unlink( $wgDebugLogFile ); - - wfDebugMem(true); + + wfDebugMem( true ); $this->assertGreaterThan( 5000000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) ); unlink( $wgDebugLogFile ); - - - + $wgDebugLogFile = $old_log_file; - + $wgDebugTimestamps = $old_wgDebugTimestamps; } - - function testClientAcceptsGzipTest() { - + + /** + * @covers ::wfClientAcceptsGzip + */ + public function testClientAcceptsGzipTest() { + $settings = array( 'gzip' => true, 'bzip' => false, @@ -362,25 +321,27 @@ class GlobalTest extends MediaWikiTestCase { 'gzip;q=12345678.9' => true, ' gzip' => true, ); - - if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) $old_server_setting = $_SERVER['HTTP_ACCEPT_ENCODING']; - + + if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) { + $old_server_setting = $_SERVER['HTTP_ACCEPT_ENCODING']; + } + foreach ( $settings as $encoding => $expect ) { $_SERVER['HTTP_ACCEPT_ENCODING'] = $encoding; - + $this->assertEquals( $expect, wfClientAcceptsGzip( true ), "'$encoding' => " . wfBoolToStr( $expect ) ); } - - if( isset( $old_server_setting ) ) $_SERVER['HTTP_ACCEPT_ENCODING'] = $old_server_setting; + if ( isset( $old_server_setting ) ) { + $_SERVER['HTTP_ACCEPT_ENCODING'] = $old_server_setting; + } } - - - - function testSwapVarsTest() { - + /** + * @covers ::swap + */ + public function testSwapVarsTest() { $var1 = 1; $var2 = 2; @@ -391,89 +352,85 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $var1, 2, 'var1 is swapped' ); $this->assertEquals( $var2, 1, 'var2 is swapped' ); - } - - function testWfPercentTest() { + /** + * @covers ::wfPercent + */ + public function testWfPercentTest() { $pcts = array( - array( 6/7, '0.86%', 2, false ), - array( 3/3, '1%' ), - array( 22/7, '3.14286%', 5 ), - array( 3/6, '0.5%' ), - array( 1/3, '0%', 0 ), - array( 10/3, '0%', -1 ), - array( 3/4/5, '0.1%', 1 ), - array( 6/7*8, '6.8571428571%', 10 ), + array( 6 / 7, '0.86%', 2, false ), + array( 3 / 3, '1%' ), + array( 22 / 7, '3.14286%', 5 ), + array( 3 / 6, '0.5%' ), + array( 1 / 3, '0%', 0 ), + array( 10 / 3, '0%', -1 ), + array( 3 / 4 / 5, '0.1%', 1 ), + array( 6 / 7 * 8, '6.8571428571%', 10 ), ); - - foreach( $pcts as $pct ) { - if( !isset( $pct[2] ) ) $pct[2] = 2; - if( !isset( $pct[3] ) ) $pct[3] = true; - - $this->assertEquals( wfPercent( $pct[0], $pct[2], $pct[3] ), $pct[1], $pct[1] ); - } - } + foreach ( $pcts as $pct ) { + if ( !isset( $pct[2] ) ) { + $pct[2] = 2; + } + if ( !isset( $pct[3] ) ) { + $pct[3] = true; + } - - function testInStringTest() { - - $this->assertTrue( in_string( 'foo', 'foobar' ), 'foo is in foobar' ); - $this->assertFalse( in_string( 'Bar', 'foobar' ), 'Case-sensitive by default' ); - $this->assertTrue( in_string( 'Foo', 'foobar', true ), 'Case-insensitive when asked' ); - + $this->assertEquals( wfPercent( $pct[0], $pct[2], $pct[3] ), $pct[1], $pct[1] ); + } } /** * test @see wfShorthandToInteger() * @dataProvider provideShorthand + * @covers ::wfShorthandToInteger */ public function testWfShorthandToInteger( $shorthand, $expected ) { $this->assertEquals( $expected, wfShorthandToInteger( $shorthand ) - ); + ); } /** array( shorthand, expected integer ) */ - public function provideShorthand() { + public static function provideShorthand() { return array( - # Null, empty ... - array( '', -1), - array( ' ', -1), - array( null, -1), + # Null, empty ... + array( '', -1 ), + array( ' ', -1 ), + array( null, -1 ), # Failures returns 0 :( array( 'ABCDEFG', 0 ), - array( 'Ak', 0 ), + array( 'Ak', 0 ), # Int, strings with spaces - array( 1, 1 ), - array( ' 1 ', 1 ), - array( 1023, 1023 ), + array( 1, 1 ), + array( ' 1 ', 1 ), + array( 1023, 1023 ), array( ' 1023 ', 1023 ), # kilo, Mega, Giga - array( '1k', 1024 ), - array( '1K', 1024 ), - array( '1m', 1024 * 1024 ), - array( '1M', 1024 * 1024 ), - array( '1g', 1024 * 1024 * 1024 ), - array( '1G', 1024 * 1024 * 1024 ), + array( '1k', 1024 ), + array( '1K', 1024 ), + array( '1m', 1024 * 1024 ), + array( '1M', 1024 * 1024 ), + array( '1g', 1024 * 1024 * 1024 ), + array( '1G', 1024 * 1024 * 1024 ), # Negatives - array( -1, -1 ), - array( -500, -500 ), - array( '-500', -500 ), - array( '-1k', -1024 ), + array( -1, -1 ), + array( -500, -500 ), + array( '-500', -500 ), + array( '-1k', -1024 ), # Zeroes - array( '0', 0 ), - array( '0k', 0 ), - array( '0M', 0 ), - array( '0G', 0 ), - array( '-0', 0 ), + array( '0', 0 ), + array( '0k', 0 ), + array( '0M', 0 ), + array( '0G', 0 ), + array( '-0', 0 ), array( '-0k', 0 ), array( '-0M', 0 ), array( '-0G', 0 ), @@ -481,14 +438,98 @@ class GlobalTest extends MediaWikiTestCase { } /** + * @param String $old: Text as it was in the database + * @param String $mine: Text submitted while user was editing + * @param String $yours: Text submitted by the user + * @param Boolean $expectedMergeResult Whether the merge should be a success + * @param String $expectedText: Text after merge has been completed + * + * @dataProvider provideMerge() + * @group medium + * @covers ::wfMerge + */ + public function testMerge( $old, $mine, $yours, $expectedMergeResult, $expectedText ) { + $this->checkHasDiff3(); + + $mergedText = null; + $isMerged = wfMerge( $old, $mine, $yours, $mergedText ); + + $msg = 'Merge should be a '; + $msg .= $expectedMergeResult ? 'success' : 'failure'; + $this->assertEquals( $expectedMergeResult, $isMerged, $msg ); + + if ( $isMerged ) { + // Verify the merged text + $this->assertEquals( $expectedText, $mergedText, + 'is merged text as expected?' ); + } + } + + public static function provideMerge() { + $EXPECT_MERGE_SUCCESS = true; + $EXPECT_MERGE_FAILURE = false; + + return array( + // #0: clean merge + array( + // old: + "one one one\n" . // trimmed + "\n" . + "two two two", + + // mine: + "one one one ONE ONE\n" . + "\n" . + "two two two\n", // with tailing whitespace + + // yours: + "one one one\n" . + "\n" . + "two two TWO TWO", // trimmed + + // ok: + $EXPECT_MERGE_SUCCESS, + + // result: + "one one one ONE ONE\n" . + "\n" . + "two two TWO TWO\n", // note: will always end in a newline + ), + + // #1: conflict, fail + array( + // old: + "one one one", // trimmed + + // mine: + "one one one ONE ONE\n" . + "\n" . + "bla bla\n" . + "\n", // with tailing whitespace + + // yours: + "one one one\n" . + "\n" . + "two two", // trimmed + + $EXPECT_MERGE_FAILURE, + + // result: + null, + ), + ); + } + + /** * @dataProvider provideMakeUrlIndexes() + * @covers ::wfMakeUrlIndexes */ - function testMakeUrlIndexes( $url, $expected ) { + public function testMakeUrlIndexes( $url, $expected ) { $index = wfMakeUrlIndexes( $url ); $this->assertEquals( $expected, $index, "wfMakeUrlIndexes(\"$url\")" ); } - function provideMakeUrlIndexes() { + public static function provideMakeUrlIndexes() { return array( array( // just a regular :) @@ -536,16 +577,17 @@ class GlobalTest extends MediaWikiTestCase { ), ); } - + /** * @dataProvider provideWfMatchesDomainList + * @covers ::wfMatchesDomainList */ - function testWfMatchesDomainList( $url, $domains, $expected, $description ) { + public function testWfMatchesDomainList( $url, $domains, $expected, $description ) { $actual = wfMatchesDomainList( $url, $domains ); $this->assertEquals( $expected, $actual, $description ); } - - function provideWfMatchesDomainList() { + + public static function provideWfMatchesDomainList() { $a = array(); $protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' ); foreach ( $protocols as $pDesc => $p ) { @@ -556,19 +598,31 @@ class GlobalTest extends MediaWikiTestCase { array( "$p//www.example2.com", array( 'www.example.com', 'www.example2.com', 'www.example3.com' ), true, "Exact match with other domains in array, $pDesc URL" ), array( "$p//www.example2.com", array( 'example.com', 'example2.com', 'example3,com' ), true, "Match without subdomain with other domains in array, $pDesc URL" ), array( "$p//www.example4.com", array( 'example.com', 'example2.com', 'example3,com' ), false, "Domain not in array, $pDesc URL" ), - - // FIXME: This is a bug in wfMatchesDomainList(). If and when this is fixed, update this test case - array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), true, "Substrings of domains match while they shouldn't, $pDesc URL" ), + array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), false, "Non-matching substring of domain, $pDesc URL" ), ) ); } + return $a; } /** + * @covers ::wfMkdirParents + */ + public function testWfMkdirParents() { + // Should not return true if file exists instead of directory + $fname = $this->getNewTempFile(); + wfSuppressWarnings(); + $ok = wfMkdirParents( $fname ); + wfRestoreWarnings(); + $this->assertFalse( $ok ); + } + + /** * @dataProvider provideWfShellMaintenanceCmdList + * @covers ::wfShellMaintenanceCmd */ - function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) { - if( wfIsWindows() ) { + public function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) { + if ( wfIsWindows() ) { // Approximation that's good enough for our purposes just now $expected = str_replace( "'", '"', $expected ); } @@ -576,23 +630,23 @@ class GlobalTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual, $description ); } - function provideWfShellMaintenanceCmdList() { + public static function provideWfShellMaintenanceCmdList() { global $wgPhpCli; + return array( array( 'eval.php', array( '--help', '--test' ), array(), "'$wgPhpCli' 'eval.php' '--help' '--test'", "Called eval.php --help --test" ), - array( 'eval.php', array( '--help', '--test space' ), array('php' => 'php5'), + array( 'eval.php', array( '--help', '--test space' ), array( 'php' => 'php5' ), "'php5' 'eval.php' '--help' '--test space'", "Called eval.php --help --test with php option" ), - array( 'eval.php', array( '--help', '--test', 'X' ), array('wrapper' => 'MWScript.php'), + array( 'eval.php', array( '--help', '--test', 'X' ), array( 'wrapper' => 'MWScript.php' ), "'$wgPhpCli' 'MWScript.php' 'eval.php' '--help' '--test' 'X'", "Called eval.php --help --test with wrapper option" ), - array( 'eval.php', array( '--help', '--test', 'y' ), array('php' => 'php5', 'wrapper' => 'MWScript.php'), + array( 'eval.php', array( '--help', '--test', 'y' ), array( 'php' => 'php5', 'wrapper' => 'MWScript.php' ), "'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'", "Called eval.php --help --test with wrapper and php option" ), ); } - /* TODO: many more! */ + /* @TODO many more! */ } - diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php index 4879a38d..cf891e7b 100644 --- a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php +++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php @@ -6,13 +6,15 @@ class GlobalWithDBTest extends MediaWikiTestCase { /** * @dataProvider provideWfIsBadImageList + * @covers ::wfIsBadImage */ - function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) { + public function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) { $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc ); } - function provideWfIsBadImageList() { + public static function provideWfIsBadImageList() { $blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]'; + return array( array( 'Bad.jpg', false, $blacklist, true, 'Called on a bad image' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php index be6c99e7..9bb74873 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php @@ -1,10 +1,11 @@ <?php /** - * Unit tests for wfAssembleUrl() + * @covers ::wfAssembleUrl */ - -class wfAssembleUrl extends MediaWikiTestCase { - /** @dataProvider provideURLParts */ +class WfAssembleUrlTest extends MediaWikiTestCase { + /** + * @dataProvider provideURLParts + */ public function testWfAssembleUrl( $parts, $output ) { $partsDump = print_r( $parts, true ); $this->assertEquals( @@ -19,7 +20,7 @@ class wfAssembleUrl extends MediaWikiTestCase { * * @return array */ - public function provideURLParts() { + public static function provideURLParts() { $schemes = array( '' => array(), '//' => array( @@ -83,12 +84,11 @@ class wfAssembleUrl extends MediaWikiTestCase { $parts['query'] = $query; $url .= '?' . $query; } - if( $fragment ) { + if ( $fragment ) { $parts['fragment'] = $fragment; $url .= '#' . $fragment; } - $cases[] = array( $parts, $url, diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php index f4ec7a5f..a01c0d49 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php +++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php @@ -1,26 +1,26 @@ <?php /** - * Unit tests for wfBCP47() + * @covers ::wfBCP47 */ -class wfBCP47 extends MediaWikiTestCase { +class WfBCP47Test extends MediaWikiTestCase { /** * test @see wfBCP47(). * Please note the BCP explicitly state that language codes are case * insensitive, there are some exceptions to the rule :) - * This test is used to verify our formatting against all lower and + * This test is used to verify our formatting against all lower and * all upper cases language code. * * @see http://tools.ietf.org/html/bcp47 * @dataProvider provideLanguageCodes() */ - function testBCP47( $code, $expected ) { + public function testBCP47( $code, $expected ) { $code = strtolower( $code ); - $this->assertEquals( $expected, wfBCP47($code), + $this->assertEquals( $expected, wfBCP47( $code ), "Applying BCP47 standard to lower case '$code'" ); $code = strtoupper( $code ); - $this->assertEquals( $expected, wfBCP47($code), + $this->assertEquals( $expected, wfBCP47( $code ), "Applying BCP47 standard to upper case '$code'" ); } @@ -28,19 +28,19 @@ class wfBCP47 extends MediaWikiTestCase { /** * Array format is ($code, $expected) */ - function provideLanguageCodes() { + public static function provideLanguageCodes() { return array( // Extracted from BCP47 (list not exhaustive) # 2.1.1 - array( 'en-ca-x-ca' , 'en-CA-x-ca' ), - array( 'sgn-be-fr' , 'sgn-BE-FR' ), + array( 'en-ca-x-ca', 'en-CA-x-ca' ), + array( 'sgn-be-fr', 'sgn-BE-FR' ), array( 'az-latn-x-latn', 'az-Latn-x-latn' ), # 2.2 array( 'sr-Latn-RS', 'sr-Latn-RS' ), array( 'az-arab-ir', 'az-Arab-IR' ), # 2.2.5 - array( 'sl-nedis' , 'sl-nedis' ), + array( 'sl-nedis', 'sl-nedis' ), array( 'de-ch-1996', 'de-CH-1996' ), # 2.2.6 @@ -56,40 +56,40 @@ class wfBCP47 extends MediaWikiTestCase { array( 'ja', 'ja' ), # Language subtag plus script subtag: - array( 'zh-hans', 'zh-Hans'), - array( 'sr-cyrl', 'sr-Cyrl'), - array( 'sr-latn', 'sr-Latn'), + array( 'zh-hans', 'zh-Hans' ), + array( 'sr-cyrl', 'sr-Cyrl' ), + array( 'sr-latn', 'sr-Latn' ), # Extended language subtags and their primary language subtag # counterparts: array( 'zh-cmn-hans-cn', 'zh-cmn-Hans-CN' ), - array( 'cmn-hans-cn' , 'cmn-Hans-CN' ), - array( 'zh-yue-hk' , 'zh-yue-HK' ), - array( 'yue-hk' , 'yue-HK' ), + array( 'cmn-hans-cn', 'cmn-Hans-CN' ), + array( 'zh-yue-hk', 'zh-yue-HK' ), + array( 'yue-hk', 'yue-HK' ), # Language-Script-Region: array( 'zh-hans-cn', 'zh-Hans-CN' ), array( 'sr-latn-RS', 'sr-Latn-RS' ), # Language-Variant: - array( 'sl-rozaj' , 'sl-rozaj' ), + array( 'sl-rozaj', 'sl-rozaj' ), array( 'sl-rozaj-biske', 'sl-rozaj-biske' ), - array( 'sl-nedis' , 'sl-nedis' ), + array( 'sl-nedis', 'sl-nedis' ), # Language-Region-Variant: - array( 'de-ch-1901' , 'de-CH-1901' ), - array( 'sl-it-nedis' , 'sl-IT-nedis' ), + array( 'de-ch-1901', 'de-CH-1901' ), + array( 'sl-it-nedis', 'sl-IT-nedis' ), # Language-Script-Region-Variant: array( 'hy-latn-it-arevela', 'hy-Latn-IT-arevela' ), # Language-Region: - array( 'de-de' , 'de-DE' ), - array( 'en-us' , 'en-US' ), - array( 'es-419', 'es-419'), + array( 'de-de', 'de-DE' ), + array( 'en-us', 'en-US' ), + array( 'es-419', 'es-419' ), # Private use subtags: - array( 'de-ch-x-phonebk' , 'de-CH-x-phonebk' ), + array( 'de-ch-x-phonebk', 'de-CH-x-phonebk' ), array( 'az-arab-x-aze-derbend', 'az-Arab-x-aze-derbend' ), /** * Previous test does not reflect the BCP which states: @@ -102,7 +102,7 @@ class wfBCP47 extends MediaWikiTestCase { # Private use registry values: array( 'x-whatever', 'x-whatever' ), array( 'qaa-qaaa-qm-x-southern', 'qaa-Qaaa-QM-x-southern' ), - array( 'de-qaaa' , 'de-Qaaa' ), + array( 'de-qaaa', 'de-Qaaa' ), array( 'sr-latn-qm', 'sr-Latn-QM' ), array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ), @@ -115,19 +115,6 @@ class wfBCP47 extends MediaWikiTestCase { // de-419-DE // a-DE // ar-a-aaa-b-bbb-a-ccc - - /* - // ISO 15924 : - array( 'sr-Cyrl', 'sr-Cyrl' ), - # @todo FIXME: Fix our function? - array( 'SR-lATN', 'sr-Latn' ), - array( 'fr-latn', 'fr-Latn' ), - // Use lowercase for single segment - // ISO 3166-1-alpha-2 code - array( 'US', 'us' ), # USA - array( 'uS', 'us' ), # USA - array( 'Fr', 'fr' ), # France - array( 'va', 'va' ), # Holy See (Vatican City State) - */); + ); } } diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php new file mode 100644 index 00000000..7da804e6 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php @@ -0,0 +1,182 @@ +<?php +/** + * @covers ::wfBaseConvert + */ +class WfBaseConvertTest extends MediaWikiTestCase { + public static function provideSingleDigitConversions() { + return array( + // 2 3 5 8 10 16 36 + array( '0', '0', '0', '0', '0', '0', '0' ), + array( '1', '1', '1', '1', '1', '1', '1' ), + array( '10', '2', '2', '2', '2', '2', '2' ), + array( '11', '10', '3', '3', '3', '3', '3' ), + array( '100', '11', '4', '4', '4', '4', '4' ), + array( '101', '12', '10', '5', '5', '5', '5' ), + array( '110', '20', '11', '6', '6', '6', '6' ), + array( '111', '21', '12', '7', '7', '7', '7' ), + array( '1000', '22', '13', '10', '8', '8', '8' ), + array( '1001', '100', '14', '11', '9', '9', '9' ), + array( '1010', '101', '20', '12', '10', 'a', 'a' ), + array( '1011', '102', '21', '13', '11', 'b', 'b' ), + array( '1100', '110', '22', '14', '12', 'c', 'c' ), + array( '1101', '111', '23', '15', '13', 'd', 'd' ), + array( '1110', '112', '24', '16', '14', 'e', 'e' ), + array( '1111', '120', '30', '17', '15', 'f', 'f' ), + array( '10000', '121', '31', '20', '16', '10', 'g' ), + array( '10001', '122', '32', '21', '17', '11', 'h' ), + array( '10010', '200', '33', '22', '18', '12', 'i' ), + array( '10011', '201', '34', '23', '19', '13', 'j' ), + array( '10100', '202', '40', '24', '20', '14', 'k' ), + array( '10101', '210', '41', '25', '21', '15', 'l' ), + array( '10110', '211', '42', '26', '22', '16', 'm' ), + array( '10111', '212', '43', '27', '23', '17', 'n' ), + array( '11000', '220', '44', '30', '24', '18', 'o' ), + array( '11001', '221', '100', '31', '25', '19', 'p' ), + array( '11010', '222', '101', '32', '26', '1a', 'q' ), + array( '11011', '1000', '102', '33', '27', '1b', 'r' ), + array( '11100', '1001', '103', '34', '28', '1c', 's' ), + array( '11101', '1002', '104', '35', '29', '1d', 't' ), + array( '11110', '1010', '110', '36', '30', '1e', 'u' ), + array( '11111', '1011', '111', '37', '31', '1f', 'v' ), + array( '100000', '1012', '112', '40', '32', '20', 'w' ), + array( '100001', '1020', '113', '41', '33', '21', 'x' ), + array( '100010', '1021', '114', '42', '34', '22', 'y' ), + array( '100011', '1022', '120', '43', '35', '23', 'z' ) + ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase2( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base2, wfBaseConvert( $base3, '3', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base5, '5', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base8, '8', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base10, '10', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base16, '16', '2' ) ); + $this->assertSame( $base2, wfBaseConvert( $base36, '36', '2' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase3( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base3, wfBaseConvert( $base2, '2', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base5, '5', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base8, '8', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base10, '10', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base16, '16', '3' ) ); + $this->assertSame( $base3, wfBaseConvert( $base36, '36', '3' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase5( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base5, wfBaseConvert( $base2, '2', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base3, '3', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base8, '8', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base10, '10', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base16, '16', '5' ) ); + $this->assertSame( $base5, wfBaseConvert( $base36, '36', '5' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase8( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base8, wfBaseConvert( $base2, '2', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base3, '3', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base5, '5', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base10, '10', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base16, '16', '8' ) ); + $this->assertSame( $base8, wfBaseConvert( $base36, '36', '8' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase10( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base10, wfBaseConvert( $base2, '2', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base3, '3', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base5, '5', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base8, '8', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base16, '16', '10' ) ); + $this->assertSame( $base10, wfBaseConvert( $base36, '36', '10' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase16( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base16, wfBaseConvert( $base2, '2', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base3, '3', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base5, '5', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base8, '8', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base10, '10', '16' ) ); + $this->assertSame( $base16, wfBaseConvert( $base36, '36', '16' ) ); + } + + /** + * @dataProvider provideSingleDigitConversions + */ + public function testDigitToBase36( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) { + $this->assertSame( $base36, wfBaseConvert( $base2, '2', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base3, '3', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base5, '5', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base8, '8', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base10, '10', '36' ) ); + $this->assertSame( $base36, wfBaseConvert( $base16, '16', '36' ) ); + } + + public function testLargeNumber() { + $this->assertSame( '1100110001111010000000101110100', wfBaseConvert( 'sd89ys', 36, 2 ) ); + $this->assertSame( '11102112120221201101', wfBaseConvert( 'sd89ys', 36, 3 ) ); + $this->assertSame( '12003102232400', wfBaseConvert( 'sd89ys', 36, 5 ) ); + $this->assertSame( '14617200564', wfBaseConvert( 'sd89ys', 36, 8 ) ); + $this->assertSame( '1715274100', wfBaseConvert( 'sd89ys', 36, 10 ) ); + $this->assertSame( '663d0174', wfBaseConvert( 'sd89ys', 36, 16 ) ); + } + + public static function provideNumbers() { + $x = array(); + $chars = '0123456789abcdefghijklmnopqrstuvwxyz'; + for ( $i = 0; $i < 50; $i++ ) { + $base = mt_rand( 2, 36 ); + $len = mt_rand( 10, 100 ); + + $str = ''; + for ( $j = 0; $j < $len; $j++ ) { + $str .= $chars[mt_rand( 0, $base - 1 )]; + } + + $x[] = array( $base, $str ); + } + + return $x; + } + + /** + * @dataProvider provideNumbers + */ + public function testIdentity( $base, $number ) { + $this->assertSame( $number, wfBaseConvert( $number, $base, $base, strlen( $number ) ) ); + } + + public function testInvalid() { + $this->assertFalse( wfBaseConvert( '101', 1, 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 1 ) ); + $this->assertFalse( wfBaseConvert( '101', 37, 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 37 ) ); + $this->assertFalse( wfBaseConvert( 'abcde', 10, 11 ) ); + $this->assertFalse( wfBaseConvert( '12930', 2, 10 ) ); + $this->assertFalse( wfBaseConvert( '101', 'abc', 15 ) ); + $this->assertFalse( wfBaseConvert( '101', 15, 'abc' ) ); + } + + public function testPadding() { + $number = "10101010101"; + $this->assertSame( strlen( $number ) + 5, strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) + 5 ) ) ); + $this->assertSame( strlen( $number ), strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) - 5 ) ) ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php index 59954b23..8c548040 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php @@ -1,17 +1,17 @@ <?php /** - * Tests for wfBaseName() + * @covers ::wfBaseName */ -class wfBaseName extends MediaWikiTestCase { +class WfBaseNameTest extends MediaWikiTestCase { /** * @dataProvider providePaths */ - function testBaseName( $fullpath, $basename ) { + public function testBaseName( $fullpath, $basename ) { $this->assertEquals( $basename, wfBaseName( $fullpath ), - "wfBaseName('$fullpath') => '$basename'" ); + "wfBaseName('$fullpath') => '$basename'" ); } - - function providePaths() { + + public static function providePaths() { return array( array( '', '' ), array( '/', '' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php index 192689f8..41230a1e 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php @@ -1,17 +1,17 @@ <?php /** - * Unit tests for wfExpandUrl() + * @covers ::wfExpandUrl */ - -class wfExpandUrl extends MediaWikiTestCase { - /** @dataProvider provideExpandableUrls */ +class WfExpandUrlTest extends MediaWikiTestCase { + /** + * @dataProvider provideExpandableUrls + */ public function testWfExpandUrl( $fullUrl, $shortUrl, $defaultProto, $server, $canServer, $httpsMode, $message ) { // Fake $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $oldServer = $wgServer; - $oldCanServer = $wgCanonicalServer; - $wgServer = $server; - $wgCanonicalServer = $canServer; + $this->setMwGlobals( array( + 'wgServer' => $server, + 'wgCanonicalServer' => $canServer, + ) ); // Fake $_SERVER['HTTPS'] if needed if ( $httpsMode ) { @@ -21,10 +21,6 @@ class wfExpandUrl extends MediaWikiTestCase { } $this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message ); - - // Restore $wgServer and $wgCanonicalServer - $wgServer = $oldServer; - $wgCanonicalServer = $oldCanServer; } /** @@ -32,25 +28,49 @@ class wfExpandUrl extends MediaWikiTestCase { * * @return array */ - public function provideExpandableUrls() { + public static function provideExpandableUrls() { $modes = array( 'http', 'https' ); - $servers = array( 'http' => 'http://example.com', 'https' => 'https://example.com', 'protocol-relative' => '//example.com' ); - $defaultProtos = array( 'http' => PROTO_HTTP, 'https' => PROTO_HTTPS, 'protocol-relative' => PROTO_RELATIVE, 'current' => PROTO_CURRENT, 'canonical' => PROTO_CANONICAL ); + $servers = array( + 'http' => 'http://example.com', + 'https' => 'https://example.com', + 'protocol-relative' => '//example.com' + ); + $defaultProtos = array( + 'http' => PROTO_HTTP, + 'https' => PROTO_HTTPS, + 'protocol-relative' => PROTO_RELATIVE, + 'current' => PROTO_CURRENT, + 'canonical' => PROTO_CANONICAL + ); $retval = array(); foreach ( $modes as $mode ) { $httpsMode = $mode == 'https'; foreach ( $servers as $serverDesc => $server ) { - foreach ( $modes as $canServerMode ) { + foreach ( $modes as $canServerMode ) { $canServer = "$canServerMode://example2.com"; foreach ( $defaultProtos as $protoDesc => $defaultProto ) { - $retval[] = array( 'http://example.com', 'http://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified http URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); - $retval[] = array( 'https://example.com', 'https://example.com', $defaultProto, $server, $canServer, $httpsMode, "Testing fully qualified https URLs (no need to expand) (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + 'http://example.com', 'http://example.com', + $defaultProto, $server, $canServer, $httpsMode, + "Testing fully qualified http URLs (no need to expand) ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); + $retval[] = array( + 'https://example.com', 'https://example.com', + $defaultProto, $server, $canServer, $httpsMode, + "Testing fully qualified https URLs (no need to expand) ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); # Would be nice to support this, see fixme on wfExpandUrl() - $retval[] = array( "wiki/FooBar", 'wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Test non-expandable relative URLs (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + "wiki/FooBar", 'wiki/FooBar', + $defaultProto, $server, $canServer, $httpsMode, + "Test non-expandable relative URLs ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); // Determine expected protocol - $p = $protoDesc . ':'; // default case if ( $protoDesc == 'protocol-relative' ) { $p = ''; } elseif ( $protoDesc == 'current' ) { @@ -69,12 +89,23 @@ class wfExpandUrl extends MediaWikiTestCase { $srv = $server; } - $retval[] = array( "$p//wikipedia.org", '//wikipedia.org', $defaultProto, $server, $canServer, $httpsMode, "Test protocol-relative URL (defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); - $retval[] = array( "$srv/wiki/FooBar", '/wiki/FooBar', $defaultProto, $server, $canServer, $httpsMode, "Testing expanding URL beginning with / (defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" ); + $retval[] = array( + "$p//wikipedia.org", '//wikipedia.org', + $defaultProto, $server, $canServer, $httpsMode, + "Test protocol-relative URL ' . + '(defaultProto: $protoDesc, wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); + $retval[] = array( + "$srv/wiki/FooBar", '/wiki/FooBar', + $defaultProto, $server, $canServer, $httpsMode, + "Testing expanding URL beginning with / ' . + '(defaultProto: $protoDesc , wgServer: $server, wgCanonicalServer: $canServer, current request protocol: $mode )" + ); } } } } + return $retval; } } diff --git a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php index 4c4c4c04..62296245 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php @@ -1,8 +1,11 @@ <?php -class wfGetCaller extends MediaWikiTestCase { +/** + * @covers ::wfGetCaller + */ +class WfGetCallerTest extends MediaWikiTestCase { - function testZero() { + public function testZero() { $this->assertEquals( __METHOD__, wfGetCaller( 1 ) ); } @@ -10,26 +13,28 @@ class wfGetCaller extends MediaWikiTestCase { return wfGetCaller(); } - function testOne() { - $this->assertEquals( "wfGetCaller::testOne", self::callerOne() ); + public function testOne() { + $this->assertEquals( 'WfGetCallerTest::testOne', self::callerOne() ); } function intermediateFunction( $level = 2, $n = 0 ) { - if ( $n > 0 ) + if ( $n > 0 ) { return self::intermediateFunction( $level, $n - 1 ); + } + return wfGetCaller( $level ); } - function testTwo() { - $this->assertEquals( "wfGetCaller::testTwo", self::intermediateFunction() ); + public function testTwo() { + $this->assertEquals( 'WfGetCallerTest::testTwo', self::intermediateFunction() ); } - function testN() { - $this->assertEquals( "wfGetCaller::testN", self::intermediateFunction( 2, 0 ) ); - $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( 1, 0 ) ); + public function testN() { + $this->assertEquals( 'WfGetCallerTest::testN', self::intermediateFunction( 2, 0 ) ); + $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( 1, 0 ) ); - for ($i=0; $i < 10; $i++) - $this->assertEquals( "wfGetCaller::intermediateFunction", self::intermediateFunction( $i + 1, $i ) ); + for ( $i = 0; $i < 10; $i++ ) { + $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( $i + 1, $i ) ); + } } } - diff --git a/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php new file mode 100644 index 00000000..5032dc11 --- /dev/null +++ b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Copyright © 2013 Alexandre Emsenhuber + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * @covers ::wfParseUrl + */ +class WfParseUrlTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgUrlProtocols', array( + '//', 'http://', 'file://', 'mailto:', + ) ); + } + + /** + * @dataProvider provideURLs + */ + public function testWfParseUrl( $url, $parts ) { + $partsDump = var_export( $parts, true ); + $this->assertEquals( + $parts, + wfParseUrl( $url ), + "Testing $url parses to $partsDump" + ); + } + + /** + * Provider of URLs for testing wfParseUrl() + * + * @return array + */ + public static function provideURLs() { + return array( + array( + '//example.org', + array( + 'scheme' => '', + 'delimiter' => '//', + 'host' => 'example.org', + ) + ), + array( + 'http://example.org', + array( + 'scheme' => 'http', + 'delimiter' => '://', + 'host' => 'example.org', + ) + ), + array( + 'http://id:key@example.org:123/path?foo=bar#baz', + array( + 'scheme' => 'http', + 'delimiter' => '://', + 'user' => 'id', + 'pass' => 'key', + 'host' => 'example.org', + 'port' => 123, + 'path' => '/path', + 'query' => 'foo=bar', + 'fragment' => 'baz', + ) + ), + array( + 'file://example.org/etc/php.ini', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => 'example.org', + 'path' => '/etc/php.ini', + ) + ), + array( + 'file:///etc/php.ini', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => '', + 'path' => '/etc/php.ini', + ) + ), + array( + 'file:///c:/', + array( + 'scheme' => 'file', + 'delimiter' => '://', + 'host' => '', + 'path' => '/c:/', + ) + ), + array( + 'mailto:id@example.org', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => 'id@example.org', + 'path' => '', + ) + ), + array( + 'mailto:id@example.org?subject=Foo', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => 'id@example.org', + 'path' => '', + 'query' => 'subject=Foo', + ) + ), + array( + 'mailto:?subject=Foo', + array( + 'scheme' => 'mailto', + 'delimiter' => ':', + 'host' => '', + 'path' => '', + 'query' => 'subject=Foo', + ) + ), + array( + 'invalid://test/', + false + ), + ); + } +} diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php index 1cf0e0fb..238a2c9c 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php @@ -1,10 +1,11 @@ <?php /** - * Unit tests for wfRemoveDotSegments() + *@covers ::wfRemoveDotSegments */ - -class wfRemoveDotSegments extends MediaWikiTestCase { - /** @dataProvider providePaths */ +class WfRemoveDotSegmentsTest extends MediaWikiTestCase { + /** + * @dataProvider providePaths + */ public function testWfRemoveDotSegments( $inputPath, $outputPath ) { $this->assertEquals( $outputPath, @@ -18,7 +19,7 @@ class wfRemoveDotSegments extends MediaWikiTestCase { * * @return array */ - public function providePaths() { + public static function providePaths() { return array( array( '/a/b/c/./../../g', '/a/g' ), array( 'mid/content=5/../6', 'mid/6' ), diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php index 1df26d2c..aadec87f 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php @@ -1,10 +1,13 @@ <?php -class wfShorthandToIntegerTest extends MediaWikiTestCase { +/** + * @covers ::wfShorthandToInteger + */ +class WfShorthandToIntegerTest extends MediaWikiTestCase { /** * @dataProvider provideABunchOfShorthands */ - function testWfShorthandToInteger( $input, $output, $description ) { + public function testWfShorthandToInteger( $input, $output, $description ) { $this->assertEquals( wfShorthandToInteger( $input ), $output, @@ -12,7 +15,7 @@ class wfShorthandToIntegerTest extends MediaWikiTestCase { ); } - function provideABunchOfShorthands() { + public static function provideABunchOfShorthands() { return array( array( '', -1, 'Empty string' ), array( ' ', -1, 'String of spaces' ), @@ -24,5 +27,4 @@ class wfShorthandToIntegerTest extends MediaWikiTestCase { array( '1k', 1024, 'One kb lowercased' ), ); } - } diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php index 505c28c7..5998f186 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php @@ -1,19 +1,19 @@ <?php - /* - * Tests for wfTimestamp() + * @covers ::wfTimestamp */ -class wfTimestamp extends MediaWikiTestCase { +class WfTimestampTest extends MediaWikiTestCase { /** * @dataProvider provideNormalTimestamps */ - function testNormalTimestamps( $input, $format, $output, $desc ) { + public function testNormalTimestamps( $input, $format, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); } - function provideNormalTimestamps() { + public static function provideNormalTimestamps() { $t = gmmktime( 12, 34, 56, 1, 15, 2001 ); - return array ( + + return array( // TS_UNIX array( $t, TS_MW, '20010115123456', 'TS_UNIX to TS_MW' ), array( -30281104, TS_MW, '19690115123456', 'Negative TS_UNIX to TS_MW' ), @@ -21,13 +21,13 @@ class wfTimestamp extends MediaWikiTestCase { array( $t, TS_DB, '2001-01-15 12:34:56', 'TS_UNIX to TS_DB' ), array( $t, TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_ISO_8601_BASIC to TS_DB' ), - + // TS_MW array( '20010115123456', TS_MW, '20010115123456', 'TS_MW to TS_MW' ), array( '20010115123456', TS_UNIX, 979562096, 'TS_MW to TS_UNIX' ), array( '20010115123456', TS_DB, '2001-01-15 12:34:56', 'TS_MW to TS_DB' ), array( '20010115123456', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_MW to TS_ISO_8601_BASIC' ), - + // TS_DB array( '2001-01-15 12:34:56', TS_MW, '20010115123456', 'TS_DB to TS_MW' ), array( '2001-01-15 12:34:56', TS_UNIX, 979562096, 'TS_DB to TS_UNIX' ), @@ -57,12 +57,12 @@ class wfTimestamp extends MediaWikiTestCase { * See r74778 and bug 25451 * @dataProvider provideOldTimestamps */ - function testOldTimestamps( $input, $format, $output, $desc ) { + public function testOldTimestamps( $input, $format, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc ); } - function provideOldTimestamps() { - return array ( + public static function provideOldTimestamps() { + return array( array( '19011213204554', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:54 GMT', 'Earliest time according to php documentation' ), array( '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ), array( '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ), @@ -96,11 +96,11 @@ class wfTimestamp extends MediaWikiTestCase { * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 * @dataProvider provideHttpDates */ - function testHttpDate( $input, $output, $desc ) { + public function testHttpDate( $input, $output, $desc ) { $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc ); } - function provideHttpDates() { + public static function provideHttpDates() { return array( array( 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ), array( 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ), @@ -114,9 +114,9 @@ class wfTimestamp extends MediaWikiTestCase { * There are a number of assumptions in our codebase where wfTimestamp() * should give the current date but it is not given a 0 there. See r71751 CR */ - function testTimestampParameter() { + public function testTimestampParameter() { $now = wfTimestamp( TS_UNIX ); - // We check that wfTimestamp doesn't return false (error) and use a LessThan assert + // We check that wfTimestamp doesn't return false (error) and use a LessThan assert // for the cases where the test is run in a second boundary. $zero = wfTimestamp( TS_UNIX, 0 ); diff --git a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php index cd1a8dbd..ce6c82c5 100644 --- a/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php +++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php @@ -1,26 +1,27 @@ <?php /** - * Tests for includes/GlobalFunctions.php -> wfUrlencode() - * * The function only need a string parameter and might react to IIS7.0 + * @covers ::wfUrlencode */ - -class wfUrlencodeTest extends MediaWikiTestCase { - +class WfUrlencodeTest extends MediaWikiTestCase { #### TESTS ############################################################## - - /** @dataProvider provideURLS */ + + /** + * @dataProvider provideURLS + */ public function testEncodingUrlWith( $input, $expected ) { $this->verifyEncodingFor( 'Apache', $input, $expected ); } - /** @dataProvider provideURLS */ + /** + * @dataProvider provideURLS + */ public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) { $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected ); } #### HELPERS ############################################################# - + /** * Internal helper that actually run the test. * Called by the public methods testEncodingUrlWith...() @@ -30,10 +31,9 @@ class wfUrlencodeTest extends MediaWikiTestCase { $expected = $this->extractExpect( $server, $expectations ); // save up global - $old = isset($_SERVER['SERVER_SOFTWARE']) + $old = isset( $_SERVER['SERVER_SOFTWARE'] ) ? $_SERVER['SERVER_SOFTWARE'] - : null - ; + : null; $_SERVER['SERVER_SOFTWARE'] = $server; wfUrlencode( null ); @@ -45,7 +45,7 @@ class wfUrlencodeTest extends MediaWikiTestCase { ); // restore global - if( $old === null ) { + if ( $old === null ) { unset( $_SERVER['SERVER_SOFTWARE'] ); } else { $_SERVER['SERVER_SOFTWARE'] = $old; @@ -58,19 +58,18 @@ class wfUrlencodeTest extends MediaWikiTestCase { * the HTTP server name. */ private function extractExpect( $server, $expectations ) { - if( is_string( $expectations ) ) { + if ( is_string( $expectations ) ) { return $expectations; - } elseif( is_array( $expectations ) ) { - if( !array_key_exists( $server, $expectations ) ) { + } elseif ( is_array( $expectations ) ) { + if ( !array_key_exists( $server, $expectations ) ) { throw new MWException( __METHOD__ . " expectation does not have any value for server name $server. Check the provider array.\n" ); } else { return $expectations[$server]; } - } else { + } else { throw new MWException( __METHOD__ . " given invalid expectation for '$server'. Should be a string or an array( <http server name> => <string> ).\n" ); - } - } - + } + } #### PROVIDERS ########################################################### @@ -83,11 +82,11 @@ class wfUrlencodeTest extends MediaWikiTestCase { * array( 'Microsoft-IIS/7', 'expected' ), * ), * If you want to add other HTTP server name, you will have to add a new - * testing method much like the testEncodingUrlWith() method above. + * testing method much like the testEncodingUrlWith() method above. */ - public function provideURLS() { + public static function provideURLS() { return array( - ### RFC 1738 chars + ### RFC 1738 chars // + is not safe array( '+', '%2B' ), // & and = not safe in queries @@ -95,7 +94,7 @@ class wfUrlencodeTest extends MediaWikiTestCase { array( '=', '%3D' ), array( ':', array( - 'Apache' => ':', + 'Apache' => ':', 'Microsoft-IIS/7' => '%3A', ) ), @@ -105,10 +104,10 @@ class wfUrlencodeTest extends MediaWikiTestCase { ';@$-_.!*', ), - ### Other tests + ### Other tests // slash remain unchanged. %2F seems to break things array( '/', '/' ), - + // Other 'funnies' chars array( '[]', '%5B%5D' ), array( '<>', '%3C%3E' ), diff --git a/tests/phpunit/includes/HTMLCheckMatrixTest.php b/tests/phpunit/includes/HTMLCheckMatrixTest.php new file mode 100644 index 00000000..5bbafd37 --- /dev/null +++ b/tests/phpunit/includes/HTMLCheckMatrixTest.php @@ -0,0 +1,102 @@ +<?php + +/** + * Unit tests for the HTMLCheckMatrix form field + */ +class HtmlCheckMatrixTest extends MediaWikiTestCase { + static private $defaultOptions = array( + 'rows' => array( 'r1', 'r2' ), + 'columns' => array( 'c1', 'c2' ), + 'fieldname' => 'test', + ); + + public function testPlainInstantiation() { + try { + $form = new HTMLCheckMatrix( array() ); + } catch ( MWException $e ) { + $this->assertInstanceOf( 'HTMLFormFieldRequiredOptionsException', $e ); + return; + } + + $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' ); + } + + public function testInstantiationWithMinimumRequiredParameters() { + $form = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertTrue( true ); // form instantiation must throw exception on failure + } + + public function testValidateCallsUserDefinedValidationCallback() { + $called = false; + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'validation-callback' => function() use ( &$called ) { + $called = true; + return false; + }, + ) ); + $this->assertEquals( false, $this->validate( $field, array() ) ); + $this->assertTrue( $called ); + } + + public function testValidateRequiresArrayInput() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertEquals( false, $this->validate( $field, null ) ); + $this->assertEquals( false, $this->validate( $field, true ) ); + $this->assertEquals( false, $this->validate( $field, 'abc' ) ); + $this->assertEquals( false, $this->validate( $field, new stdClass ) ); + $this->assertEquals( true, $this->validate( $field, array() ) ); + } + + public function testValidateAllowsOnlyKnownTags() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertInternalType( 'string', $this->validate( $field, array( 'foo' ) ) ); + } + + public function testValidateAcceptsPartialTagList() { + $field = new HTMLCheckMatrix( self::$defaultOptions ); + $this->assertTrue( $this->validate( $field, array() ) ); + $this->assertTrue( $this->validate( $field, array( 'c1-r1' ) ) ); + $this->assertTrue( $this->validate( $field, array( 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ) ) ); + } + + /** + * This form object actually has no visibility into what happens later on, but essentially + * if the data submitted by the user passes validate the following is run: + * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) { + * $user->setOption( $k, $v ); + * } + */ + public function testValuesForcedOnRemainOn() { + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'force-options-on' => array( 'c2-r1' ), + ) ); + $expected = array( + 'c1-r1' => false, + 'c1-r2' => false, + 'c2-r1' => true, + 'c2-r2' => false, + ); + $this->assertEquals( $expected, $field->filterDataForSubmit( array() ) ); + } + + public function testValuesForcedOffRemainOff() { + $field = new HTMLCheckMatrix( self::$defaultOptions + array( + 'force-options-off' => array( 'c1-r2', 'c2-r2' ), + ) ); + $expected = array( + 'c1-r1' => true, + 'c1-r2' => false, + 'c2-r1' => true, + 'c2-r2' => false, + ); + // array_keys on the result simulates submitting all fields checked + $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) ); + } + + protected function validate( HTMLFormField $field, $submitted ) { + return $field->validate( + $submitted, + array( self::$defaultOptions['fieldname'] => $submitted ) + ); + } +} diff --git a/tests/phpunit/includes/HashRingTest.php b/tests/phpunit/includes/HashRingTest.php new file mode 100644 index 00000000..65f13696 --- /dev/null +++ b/tests/phpunit/includes/HashRingTest.php @@ -0,0 +1,53 @@ +<?php + +/** + * @group HashRing + */ +class HashRingTest extends MediaWikiTestCase { + public function testHashRing() { + $ring = new HashRing( array( 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) ); + + $locations = array(); + for ( $i = 0; $i < 20; $i++ ) { + $locations[ "hello$i"] = $ring->getLocation( "hello$i" ); + } + $expectedLocations = array( + "hello0" => "s5", + "hello1" => "s6", + "hello2" => "s2", + "hello3" => "s5", + "hello4" => "s6", + "hello5" => "s4", + "hello6" => "s5", + "hello7" => "s4", + "hello8" => "s5", + "hello9" => "s5", + "hello10" => "s3", + "hello11" => "s6", + "hello12" => "s1", + "hello13" => "s3", + "hello14" => "s3", + "hello15" => "s5", + "hello16" => "s4", + "hello17" => "s6", + "hello18" => "s6", + "hello19" => "s3" + ); + + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + + $locations = array(); + for ( $i = 0; $i < 5; $i++ ) { + $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 ); + } + + $expectedLocations = array( + "hello0" => array( "s5", "s6" ), + "hello1" => array( "s6", "s4" ), + "hello2" => array( "s2", "s1" ), + "hello3" => array( "s5", "s6" ), + "hello4" => array( "s6", "s4" ), + ); + $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' ); + } +} diff --git a/tests/phpunit/includes/HooksTest.php b/tests/phpunit/includes/HooksTest.php index 2f9d9f8d..81dd4870 100644 --- a/tests/phpunit/includes/HooksTest.php +++ b/tests/phpunit/includes/HooksTest.php @@ -2,101 +2,157 @@ class HooksTest extends MediaWikiTestCase { - public function testOldStyleHooks() { - $foo = 'Foo'; - $bar = 'Bar'; - - $i = new NothingClass(); - + function setUp() { global $wgHooks; + parent::setUp(); + Hooks::clear( 'MediaWikiHooksTest001' ); + unset( $wgHooks['MediaWikiHooksTest001'] ); + } - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someNonStatic' ); - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); - - $this->assertEquals( 'fOO', $foo, 'Standard method' ); - $foo = 'Foo'; - - $wgHooks['MediaWikiHooksTest001'][] = $i; - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + public static function provideHooks() { + $i = new NothingClass(); - $this->assertEquals( 'foo', $foo, 'onEventName style' ); - $foo = 'Foo'; + return array( + array( 'Object and method', array( $i, 'someNonStatic' ), 'changed-nonstatic', 'changed-nonstatic' ), + array( 'Object and no method', array( $i ), 'changed-onevent', 'original' ), + array( 'Object and method with data', array( $i, 'someNonStaticWithData', 'data' ), 'data', 'original' ), + array( 'Object and static method', array( $i, 'someStatic' ), 'changed-static', 'original' ), + array( 'Class::method static call', array( 'NothingClass::someStatic' ), 'changed-static', 'original' ), + array( 'Global function', array( 'NothingFunction' ), 'changed-func', 'original' ), + array( 'Global function with data', array( 'NothingFunctionData', 'data' ), 'data', 'original' ), + array( 'Closure', array( function ( &$foo, $bar ) { + $foo = 'changed-closure'; + + return true; + } ), 'changed-closure', 'original' ), + array( 'Closure with data', array( function ( $data, &$foo, $bar ) { + $foo = $data; + + return true; + }, 'data' ), 'data', 'original' ) + ); + } - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someNonStaticWithData', 'baz' ); + /** + * @dataProvider provideHooks + */ + public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) { + global $wgHooks; + $foo = $bar = 'original'; + $wgHooks['MediaWikiHooksTest001'][] = $hook; wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); - $this->assertEquals( 'baz', $foo, 'Data included' ); - $foo = 'Foo'; - - $wgHooks['MediaWikiHooksTest001'][] = array( $i, 'someStatic' ); - - wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertSame( $expectedFoo, $foo, $msg ); + $this->assertSame( $expectedBar, $bar, $msg ); + } - $this->assertEquals( 'bah', $foo, 'Standard static method' ); - //$foo = 'Foo'; + /** + * @dataProvider provideHooks + */ + public function testNewStyleHooks( $msg, $hook, $expectedFoo, $expectedBar ) { + $foo = $bar = 'original'; - unset( $wgHooks['MediaWikiHooksTest001'] ); + Hooks::register( 'MediaWikiHooksTest001', $hook ); + Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertSame( $expectedFoo, $foo, $msg ); + $this->assertSame( $expectedBar, $bar, $msg ); } - public function testNewStyleHooks() { - $foo = 'Foo'; - $bar = 'Bar'; - - $i = new NothingClass(); + public function testNewStyleHookInteraction() { + global $wgHooks; - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someNonStatic' ) ); + $a = new NothingClass(); + $b = new NothingClass(); - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $wgHooks['MediaWikiHooksTest001'][] = $a; + $this->assertTrue( Hooks::isRegistered( 'MediaWikiHooksTest001' ), 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' ); - $this->assertEquals( 'fOO', $foo, 'Standard method' ); - $foo = 'Foo'; + Hooks::register( 'MediaWikiHooksTest001', $b ); + $this->assertEquals( 2, count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' ); - Hooks::register( 'MediaWikiHooksTest001', $i ); + $foo = 'quux'; + $bar = 'qaax'; Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + $this->assertEquals( 1, $a->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' ); + $this->assertEquals( 1, $b->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' ); + } - $this->assertEquals( 'foo', $foo, 'onEventName style' ); - $foo = 'Foo'; + /** + * @expectedException MWException + */ + public function testUncallableFunction() { + Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' ); + Hooks::run( 'MediaWikiHooksTest001', array() ); + } - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someNonStaticWithData', 'baz' ) ); + public function testFalseReturn() { + Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { + return false; + } ); + Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) { + $foo = 'test'; + + return true; + } ); + $foo = 'original'; + Hooks::run( 'MediaWikiHooksTest001', array( &$foo ) ); + $this->assertSame( 'original', $foo, 'Hooks continued processing after a false return.' ); + } - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); + /** + * @expectedException FatalError + */ + public function testFatalError() { + Hooks::register( 'MediaWikiHooksTest001', function () { + return 'test'; + } ); + Hooks::run( 'MediaWikiHooksTest001', array() ); + } +} - $this->assertEquals( 'baz', $foo, 'Data included' ); - $foo = 'Foo'; +function NothingFunction( &$foo, &$bar ) { + $foo = 'changed-func'; - Hooks::register( 'MediaWikiHooksTest001', array( $i, 'someStatic' ) ); + return true; +} - Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) ); +function NothingFunctionData( $data, &$foo, &$bar ) { + $foo = $data; - $this->assertEquals( 'bah', $foo, 'Standard static method' ); - $foo = 'Foo'; - } + return true; } class NothingClass { - static public function someStatic( &$foo, &$bar ) { - $foo = 'bah'; + public $calls = 0; + + public static function someStatic( &$foo, &$bar ) { + $foo = 'changed-static'; + return true; } public function someNonStatic( &$foo, &$bar ) { - $foo = 'fOO'; - $bar = 'bAR'; + $this->calls++; + $foo = 'changed-nonstatic'; + $bar = 'changed-nonstatic'; + return true; } public function onMediaWikiHooksTest001( &$foo, &$bar ) { - $foo = 'foo'; + $this->calls++; + $foo = 'changed-onevent'; + return true; } - public function someNonStaticWithData( $foo, &$bar ) { - $bar = $foo; + public function someNonStaticWithData( $data, &$foo, &$bar ) { + $this->calls++; + $foo = $data; + return true; } } diff --git a/tests/phpunit/includes/HtmlFormatterTest.php b/tests/phpunit/includes/HtmlFormatterTest.php new file mode 100644 index 00000000..a37df74f --- /dev/null +++ b/tests/phpunit/includes/HtmlFormatterTest.php @@ -0,0 +1,81 @@ +<?php + +/** + * @group HtmlFormatter + */ +class HtmlFormatterTest extends MediaWikiTestCase { + /** + * @dataProvider getHtmlData + */ + public function testTransform( $input, $expected, $callback = false ) { + $input = self::normalize( $input ); + $formatter = new HtmlFormatter( HtmlFormatter::wrapHTML( $input ) ); + if ( $callback ) { + $callback( $formatter ); + } + $formatter->filterContent(); + $html = $formatter->getText(); + $this->assertEquals( self::normalize( $expected ), self::normalize( $html ) ); + } + + private static function normalize( $s ) { + return str_replace( "\n", '', + str_replace( "\r", '', $s ) // "yay" to Windows! + ); + } + + public function getHtmlData() { + $removeImages = function( HtmlFormatter $f ) { + $f->setRemoveMedia(); + }; + $removeTags = function( HtmlFormatter $f ) { + $f->remove( array( 'table', '.foo', '#bar', 'div.baz' ) ); + }; + $flattenSomeStuff = function( HtmlFormatter $f ) { + $f->flatten( array( 's', 'div' ) ); + }; + $flattenEverything = function( HtmlFormatter $f ) { + $f->flattenAllTags(); + }; + return array( + // remove images if asked + array( + '<img src="/foo/bar.jpg" alt="Blah"/>', + '', + $removeImages, + ), + // basic tag removal + array( + '<table><tr><td>foo</td></tr></table><div class="foo">foo</div><div class="foo quux">foo</div><span id="bar">bar</span> +<strong class="foo" id="bar">foobar</strong><div class="notfoo">test</div><div class="baz"/> +<span class="baz">baz</span>', + + '<div class="notfoo">test</div> +<span class="baz">baz</span>', + $removeTags, + ), + // don't flatten tags that start like chosen ones + array( + '<div><s>foo</s> <span>bar</span></div>', + 'foo <span>bar</span>', + $flattenSomeStuff, + ), + // total flattening + array( + '<div style="foo">bar<sup>2</sup></div>', + 'bar2', + $flattenEverything, + ), + // UTF-8 preservation and security + array( + '<span title="" \' &"><Тест!></span> &<&&&&', + '<span title="" \' &"><Тест!></span> &<&&&&', + ), + // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086 + array( + 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>', + 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>', + ), + ); + } +} diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php index 135ebc5a..1c62d032 100644 --- a/tests/phpunit/includes/HtmlTest.php +++ b/tests/phpunit/includes/HtmlTest.php @@ -2,119 +2,144 @@ /** tests for includes/Html.php */ class HtmlTest extends MediaWikiTestCase { - private static $oldLang; - private static $oldContLang; - private static $oldLanguageCode; - private static $oldNamespaces; - private static $oldHTML5; - public function setUp() { - global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + protected function setUp() { + parent::setUp(); - // Save globals - self::$oldLang = $wgLang; - self::$oldContLang = $wgContLang; - self::$oldNamespaces = $wgContLang->getNamespaces(); - self::$oldLanguageCode = $wgLanguageCode; - self::$oldHTML5 = $wgHTML5; - - $wgLanguageCode = 'en'; - $wgContLang = $wgLang = Language::factory( $wgLanguageCode ); + $langCode = 'en'; + $langObj = Language::factory( $langCode ); // Hardcode namespaces during test runs, // so that html output based on existing namespaces // can be properly evaluated. - $wgContLang->setNamespaces( array( + $langObj->setNamespaces( array( -2 => 'Media', -1 => 'Special', - 0 => '', - 1 => 'Talk', - 2 => 'User', - 3 => 'User_talk', - 4 => 'MyWiki', - 5 => 'MyWiki_Talk', - 6 => 'File', - 7 => 'File_talk', - 8 => 'MediaWiki', - 9 => 'MediaWiki_talk', - 10 => 'Template', - 11 => 'Template_talk', - 14 => 'Category', - 15 => 'Category_talk', - 100 => 'Custom', - 101 => 'Custom_talk', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 14 => 'Category', + 15 => 'Category_talk', + 100 => 'Custom', + 101 => 'Custom_talk', + ) ); + + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgContLang' => $langObj, + 'wgLang' => $langObj, + 'wgWellFormedXml' => false, ) ); } - public function tearDown() { - global $wgLang, $wgContLang, $wgLanguageCode, $wgHTML5; + public function testElementBasics() { + $this->assertEquals( + '<img>', + Html::element( 'img', null, '' ), + 'No close tag for short-tag elements' + ); - // Restore globals - $wgContLang->setNamespaces( self::$oldNamespaces ); - $wgLang = self::$oldLang; - $wgContLang = self::$oldContLang; - $wgLanguageCode = self::$oldLanguageCode; - $wgHTML5 = self::$oldHTML5; + $this->assertEquals( + '<element></element>', + Html::element( 'element', null, null ), + 'Close tag for empty element (null, null)' + ); + + $this->assertEquals( + '<element></element>', + Html::element( 'element', array(), '' ), + 'Close tag for empty element (array, string)' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + '<img />', + Html::element( 'img', null, '' ), + 'Self-closing tag for short-tag elements (wgWellFormedXml = true)' + ); } - /** - * Wrapper to easily set $wgHTML5 = true. - * Original value will be restored after test completion. - * @todo Move to MediaWikiTestCase - */ - public function enableHTML5() { - global $wgHTML5; - $wgHTML5 = true; + public function dataXmlMimeType() { + return array( + // ( $mimetype, $isXmlMimeType ) + # HTML is not an XML MimeType + array( 'text/html', false ), + # XML is an XML MimeType + array( 'text/xml', true ), + array( 'application/xml', true ), + # XHTML is an XML MimeType + array( 'application/xhtml+xml', true ), + # Make sure other +xml MimeTypes are supported + # SVG is another random MimeType even though we don't use it + array( 'image/svg+xml', true ), + # Complete random other MimeTypes are not XML + array( 'text/plain', false ), + ); } + /** - * Wrapper to easily set $wgHTML5 = false - * Original value will be restored after test completion. - * @todo Move to MediaWikiTestCase + * @dataProvider dataXmlMimeType */ - public function disableHTML5() { - global $wgHTML5; - $wgHTML5 = false; + public function testXmlMimeType( $mimetype, $isXmlMimeType ) { + $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) ); } public function testExpandAttributesSkipsNullAndFalse() { - + ### EMPTY ######## - $this->AssertEmpty( + $this->assertEmpty( Html::expandAttributes( array( 'foo' => null ) ), 'skip keys with null value' ); - $this->AssertEmpty( + $this->assertEmpty( Html::expandAttributes( array( 'foo' => false ) ), 'skip keys with false value' ); - $this->AssertNotEmpty( + $this->assertNotEmpty( Html::expandAttributes( array( 'foo' => '' ) ), 'keep keys with an empty string' ); } public function testExpandAttributesForBooleans() { - global $wgHtml5; - $this->AssertEquals( + $this->assertEquals( '', Html::expandAttributes( array( 'selected' => false ) ), 'Boolean attributes do not generates output when value is false' ); - $this->AssertEquals( + $this->assertEquals( '', Html::expandAttributes( array( 'selected' => null ) ), 'Boolean attributes do not generates output when value is null' ); - $this->AssertEquals( - $wgHtml5 ? ' selected=""' : ' selected="selected"', + $this->assertEquals( + ' selected', Html::expandAttributes( array( 'selected' => true ) ), - 'Boolean attributes skip value output' + 'Boolean attributes have no value when value is true' ); - $this->AssertEquals( - $wgHtml5 ? ' selected=""' : ' selected="selected"', + $this->assertEquals( + ' selected', Html::expandAttributes( array( 'selected' ) ), - 'Boolean attributes (ex: selected) do not need a value' + 'Boolean attributes have no value when value is true (passed as numerical array)' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + ' selected=""', + Html::expandAttributes( array( 'selected' => true ) ), + 'Boolean attributes have empty string value when value is true (wgWellFormedXml)' ); } @@ -124,25 +149,48 @@ class HtmlTest extends MediaWikiTestCase { */ public function testExpandAttributesVariousExpansions() { ### NOT EMPTY #### - $this->AssertEquals( + $this->assertEquals( ' empty_string=""', Html::expandAttributes( array( 'empty_string' => '' ) ), - 'Value with an empty string' + 'Empty string is always quoted' ); - $this->AssertEquals( + $this->assertEquals( + ' key=value', + Html::expandAttributes( array( 'key' => 'value' ) ), + 'Simple string value needs no quotes' + ); + $this->assertEquals( + ' one=1', + Html::expandAttributes( array( 'one' => 1 ) ), + 'Number 1 value needs no quotes' + ); + $this->assertEquals( + ' zero=0', + Html::expandAttributes( array( 'zero' => 0 ) ), + 'Number 0 value needs no quotes' + ); + + $this->setMwGlobals( 'wgWellFormedXml', true ); + + $this->assertEquals( + ' empty_string=""', + Html::expandAttributes( array( 'empty_string' => '' ) ), + 'Attribute values are always quoted (wgWellFormedXml): Empty string' + ); + $this->assertEquals( ' key="value"', Html::expandAttributes( array( 'key' => 'value' ) ), - 'Value is a string' + 'Attribute values are always quoted (wgWellFormedXml): Simple string' ); - $this->AssertEquals( + $this->assertEquals( ' one="1"', Html::expandAttributes( array( 'one' => 1 ) ), - 'Value is a numeric one' + 'Attribute values are always quoted (wgWellFormedXml): Number 1' ); - $this->AssertEquals( + $this->assertEquals( ' zero="0"', Html::expandAttributes( array( 'zero' => 0 ) ), - 'Value is a numeric zero' + 'Attribute values are always quoted (wgWellFormedXml): Number 0' ); } @@ -153,29 +201,29 @@ class HtmlTest extends MediaWikiTestCase { */ public function testExpandAttributesListValueAttributes() { ### STRING VALUES - $this->AssertEquals( + $this->assertEquals( ' class="redundant spaces here"', Html::expandAttributes( array( 'class' => ' redundant spaces here ' ) ), 'Normalization should strip redundant spaces' ); - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => 'foo bar foo bar bar' ) ), 'Normalization should remove duplicates in string-lists' ); ### "EMPTY" ARRAY VALUES - $this->AssertEquals( + $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array() ) ), 'Value with an empty array' ); - $this->AssertEquals( + $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( null, '', ' ', ' ' ) ) ), 'Array with null, empty string and spaces' ); ### NON-EMPTY ARRAY VALUES - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo', @@ -186,7 +234,7 @@ class HtmlTest extends MediaWikiTestCase { ) ) ), 'Normalization should remove duplicates in the array' ); - $this->AssertEquals( + $this->assertEquals( ' class="foo bar"', Html::expandAttributes( array( 'class' => array( 'foo bar', @@ -202,7 +250,7 @@ class HtmlTest extends MediaWikiTestCase { * Test feature added by r96188, let pass attributes values as * a PHP array. Restricted to class,rel, accesskey. */ - function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { + public function testExpandAttributesSpaceSeparatedAttributesWithBoolean() { $this->assertEquals( ' class="booltrue one"', Html::expandAttributes( array( 'class' => array( @@ -210,12 +258,12 @@ class HtmlTest extends MediaWikiTestCase { 'one' => 1, # Method use isset() internally, make sure we do discard - # attributes values which have been assigned well known values + # attributes values which have been assigned well known values 'emptystring' => '', 'boolfalse' => false, 'zero' => 0, 'null' => null, - ))) + ) ) ) ); } @@ -226,62 +274,62 @@ class HtmlTest extends MediaWikiTestCase { * * Feature added by r96188 */ - function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { + public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() { $this->assertEquals( ' class=""', Html::expandAttributes( array( 'class' => array( 'GREEN', 'GREEN' => false, 'GREEN', - ))) + ) ) ) ); } - function testNamespaceSelector() { - $this->assertEquals( - '<select>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + public function testNamespaceSelector() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector(), 'Basic namespace selector without custom options' ); $this->assertEquals( - '<label for="mw-test-namespace">Select a namespace:</label> ' . -'<select id="mw-test-namespace" name="wpNamespace">' . "\n" . -'<option value="all">all</option>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2" selected="">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + '<label for=mw-test-namespace>Select a namespace:</label> ' . + '<select id=mw-test-namespace name=wpNamespace>' . "\n" . + '<option value=all>all</option>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2 selected>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ), array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' ) @@ -290,25 +338,25 @@ class HtmlTest extends MediaWikiTestCase { ); $this->assertEquals( - '<label>Select a namespace:</label> ' . -'<select>' . "\n" . -'<option value="0">(Main)</option>' . "\n" . -'<option value="1">Talk</option>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="3">User talk</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + '<label for=namespace>Select a namespace:</label> ' . + '<select id=namespace name=namespace>' . "\n" . + '<option value=0>(Main)</option>' . "\n" . + '<option value=1>Talk</option>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=3>User talk</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'label' => 'Select a namespace:' ) ), @@ -316,21 +364,21 @@ class HtmlTest extends MediaWikiTestCase { ); } - function testCanFilterOutNamespaces() { - $this->assertEquals( -'<select>' . "\n" . -'<option value="2">User</option>' . "\n" . -'<option value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'</select>', + public function testCanFilterOutNamespaces() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option value=2>User</option>' . "\n" . + '<option value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'exclude' => array( 0, 1, 3, 100, 101 ) ) ), @@ -338,26 +386,26 @@ class HtmlTest extends MediaWikiTestCase { ); } - function testCanDisableANamespaces() { - $this->assertEquals( -'<select>' . "\n" . -'<option disabled="" value="0">(Main)</option>' . "\n" . -'<option disabled="" value="1">Talk</option>' . "\n" . -'<option disabled="" value="2">User</option>' . "\n" . -'<option disabled="" value="3">User talk</option>' . "\n" . -'<option disabled="" value="4">MyWiki</option>' . "\n" . -'<option value="5">MyWiki Talk</option>' . "\n" . -'<option value="6">File</option>' . "\n" . -'<option value="7">File talk</option>' . "\n" . -'<option value="8">MediaWiki</option>' . "\n" . -'<option value="9">MediaWiki talk</option>' . "\n" . -'<option value="10">Template</option>' . "\n" . -'<option value="11">Template talk</option>' . "\n" . -'<option value="14">Category</option>' . "\n" . -'<option value="15">Category talk</option>' . "\n" . -'<option value="100">Custom</option>' . "\n" . -'<option value="101">Custom talk</option>' . "\n" . -'</select>', + public function testCanDisableANamespaces() { + $this->assertEquals( + '<select id=namespace name=namespace>' . "\n" . + '<option disabled value=0>(Main)</option>' . "\n" . + '<option disabled value=1>Talk</option>' . "\n" . + '<option disabled value=2>User</option>' . "\n" . + '<option disabled value=3>User talk</option>' . "\n" . + '<option disabled value=4>MyWiki</option>' . "\n" . + '<option value=5>MyWiki Talk</option>' . "\n" . + '<option value=6>File</option>' . "\n" . + '<option value=7>File talk</option>' . "\n" . + '<option value=8>MediaWiki</option>' . "\n" . + '<option value=9>MediaWiki talk</option>' . "\n" . + '<option value=10>Template</option>' . "\n" . + '<option value=11>Template talk</option>' . "\n" . + '<option value=14>Category</option>' . "\n" . + '<option value=15>Category talk</option>' . "\n" . + '<option value=100>Custom</option>' . "\n" . + '<option value=101>Custom talk</option>' . "\n" . + '</select>', Html::namespaceSelector( array( 'disable' => array( 0, 1, 2, 3, 4 ) ) ), @@ -366,13 +414,12 @@ class HtmlTest extends MediaWikiTestCase { } /** - * @dataProvider providesHtml5InputTypes + * @dataProvider provideHtml5InputTypes */ - function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { - $this->enableHTML5(); + public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) { $this->assertEquals( - '<input type="' . $HTML5InputType . '" />', - HTML::element( 'input', array( 'type' => $HTML5InputType ) ), + '<input type=' . $HTML5InputType . '>', + Html::element( 'input', array( 'type' => $HTML5InputType ) ), 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"' ); } @@ -381,7 +428,7 @@ class HtmlTest extends MediaWikiTestCase { * List of input element types values introduced by HTML5 * Full list at http://www.w3.org/TR/html-markup/input.html */ - function providesHtml5InputTypes() { + public static function provideHtml5InputTypes() { $types = array( 'datetime', 'datetime-local', @@ -398,42 +445,39 @@ class HtmlTest extends MediaWikiTestCase { 'color', ); $cases = array(); - foreach( $types as $type ) { + foreach ( $types as $type ) { $cases[] = array( $type ); } + return $cases; } /** - * Test out Html::element drops default value - * @cover Html::dropDefaults + * Test out Html::element drops or enforces default value + * @covers Html::dropDefaults * @dataProvider provideElementsWithAttributesHavingDefaultValues */ - function testDropDefaults( $expected, $element, $message = '' ) { - $this->enableHTML5(); - $this->assertEquals( $expected, $element, $message ); + public function testDropDefaults( $expected, $element, $attribs, $message = '' ) { + $this->assertEquals( $expected, Html::element( $element, $attribs ), $message ); } - function provideElementsWithAttributesHavingDefaultValues() { + public static function provideElementsWithAttributesHavingDefaultValues() { # Use cases in a concise format: # <expected>, <element name>, <array of attributes> [, <message>] # Will be mapped to Html::element() $cases = array(); ### Generic cases, match $attribDefault static array - $cases[] = array( '<area />', + $cases[] = array( '<area>', 'area', array( 'shape' => 'rect' ) ); - $cases[] = array( '<button></button>', + $cases[] = array( '<button type=submit></button>', 'button', array( 'formaction' => 'GET' ) ); - $cases[] = array( '<button></button>', + $cases[] = array( '<button type=submit></button>', 'button', array( 'formenctype' => 'application/x-www-form-urlencoded' ) ); - $cases[] = array( '<button></button>', - 'button', array( 'type' => 'submit' ) - ); $cases[] = array( '<canvas></canvas>', 'canvas', array( 'height' => '150' ) @@ -449,7 +493,7 @@ class HtmlTest extends MediaWikiTestCase { 'canvas', array( 'width' => 300 ) ); - $cases[] = array( '<command />', + $cases[] = array( '<command>', 'command', array( 'type' => 'command' ) ); @@ -463,18 +507,18 @@ class HtmlTest extends MediaWikiTestCase { 'form', array( 'enctype' => 'application/x-www-form-urlencoded' ) ); - $cases[] = array( '<input />', + $cases[] = array( '<input>', 'input', array( 'formaction' => 'GET' ) ); - $cases[] = array( '<input />', + $cases[] = array( '<input>', 'input', array( 'type' => 'text' ) ); - $cases[] = array( '<keygen />', + $cases[] = array( '<keygen>', 'keygen', array( 'keytype' => 'rsa' ) ); - $cases[] = array( '<link />', + $cases[] = array( '<link>', 'link', array( 'media' => 'all' ) ); @@ -499,37 +543,44 @@ class HtmlTest extends MediaWikiTestCase { ### SPECIFIC CASES - # <link type="text/css" /> - $cases[] = array( '<link />', + # <link type="text/css"> + $cases[] = array( '<link>', 'link', array( 'type' => 'text/css' ) ); - # <input /> specific handling - $cases[] = array( '<input type="checkbox" />', + # <input> specific handling + $cases[] = array( '<input type=checkbox>', 'input', array( 'type' => 'checkbox', 'value' => 'on' ), 'Default value "on" is stripped of checkboxes', ); - $cases[] = array( '<input type="radio" />', + $cases[] = array( '<input type=radio>', 'input', array( 'type' => 'radio', 'value' => 'on' ), 'Default value "on" is stripped of radio buttons', ); - $cases[] = array( '<input type="submit" value="Submit" />', + $cases[] = array( '<input type=submit value=Submit>', 'input', array( 'type' => 'submit', 'value' => 'Submit' ), 'Default value "Submit" is kept on submit buttons (for possible l10n issues)', ); - $cases[] = array( '<input type="color" />', + $cases[] = array( '<input type=color>', 'input', array( 'type' => 'color', 'value' => '' ), ); - $cases[] = array( '<input type="range" />', + $cases[] = array( '<input type=range>', 'input', array( 'type' => 'range', 'value' => '' ), ); - # <select /> specifc handling - $cases[] = array( '<select multiple=""></select>', + # <button> specific handling + # see remarks on http://msdn.microsoft.com/en-us/library/ie/ms535211%28v=vs.85%29.aspx + $cases[] = array( '<button type=submit></button>', + 'button', array( 'type' => 'submit' ), + 'According to standard the default type is "submit". Depending on compatibility mode IE might use "button", instead.', + ); + + # <select> specifc handling + $cases[] = array( '<select multiple></select>', 'select', array( 'size' => '4', 'multiple' => true ), ); # .. with numeric value - $cases[] = array( '<select multiple=""></select>', + $cases[] = array( '<select multiple></select>', 'select', array( 'size' => 4, 'multiple' => true ), ); $cases[] = array( '<select></select>', @@ -553,15 +604,16 @@ class HtmlTest extends MediaWikiTestCase { "dropDefaults accepts values given as an array" ); - # Craft the Html elements $ret = array(); - foreach( $cases as $case ) { + foreach ( $cases as $case ) { $ret[] = array( $case[0], - Html::element( $case[1], $case[2] ) + $case[1], $case[2], + isset( $case[3] ) ? $case[3] : '' ); } + return $ret; } @@ -571,10 +623,9 @@ class HtmlTest extends MediaWikiTestCase { 'Blacklist form validation attributes.' ); $this->assertEquals( - ' step="any"', + ' step=any', Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 'any' ) ), - "Allow special case 'step=\"any\"'." + 'Allow special case "step=any".' ); } - } diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php index 263383f1..11d8ed60 100644 --- a/tests/phpunit/includes/HttpTest.php +++ b/tests/phpunit/includes/HttpTest.php @@ -5,8 +5,9 @@ class HttpTest extends MediaWikiTestCase { /** * @dataProvider cookieDomains + * @covers Cookie::validateCookieDomain */ - function testValidateCookieDomain( $expected, $domain, $origin = null ) { + public function testValidateCookieDomain( $expected, $domain, $origin = null ) { if ( $origin ) { $ok = Cookie::validateCookieDomain( $domain, $origin ); $msg = "$domain against origin $origin"; @@ -17,12 +18,12 @@ class HttpTest extends MediaWikiTestCase { $this->assertEquals( $expected, $ok, $msg ); } - function cookieDomains() { + public static function cookieDomains() { return array( - array( false, "org"), - array( false, ".org"), - array( true, "wikipedia.org"), - array( true, ".wikipedia.org"), + array( false, "org" ), + array( false, ".org" ), + array( true, "wikipedia.org" ), + array( true, ".wikipedia.org" ), array( false, "co.uk" ), array( false, ".co.uk" ), array( false, "gov.uk" ), @@ -50,11 +51,12 @@ class HttpTest extends MediaWikiTestCase { * Test Http::isValidURI() * @bug 27854 : Http::isValidURI is too lax * @dataProvider provideURI + * @covers Http::isValidURI */ - function testIsValidUri( $expect, $URI, $message = '' ) { + public function testIsValidUri( $expect, $URI, $message = '' ) { $this->assertEquals( $expect, - (bool) Http::isValidURI( $URI ), + (bool)Http::isValidURI( $URI ), $message ); } @@ -62,32 +64,32 @@ class HttpTest extends MediaWikiTestCase { /** * Feeds URI to test a long regular expression in Http::isValidURI */ - function provideURI() { + public static function provideURI() { /** Format: 'boolean expectation', 'URI to test', 'Optional message' */ return array( array( false, '¿non sens before!! http://a', 'Allow anything before URI' ), # (http|https) - only two schemes allowed - array( true, 'http://www.example.org/' ), - array( true, 'https://www.example.org/' ), - array( true, 'http://www.example.org', 'URI without directory' ), - array( true, 'http://a', 'Short name' ), - array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star' + array( true, 'http://www.example.org/' ), + array( true, 'https://www.example.org/' ), + array( true, 'http://www.example.org', 'URI without directory' ), + array( true, 'http://a', 'Short name' ), + array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star' array( false, '\\host\directory', 'CIFS share' ), array( false, 'gopher://host/dir', 'Reject gopher scheme' ), array( false, 'telnet://host', 'Reject telnet scheme' ), # :\/\/ - double slashes - array( false, 'http//example.org', 'Reject missing colon in protocol' ), - array( false, 'http:/example.org', 'Reject missing slash in protocol' ), - array( false, 'http:example.org', 'Must have two slashes' ), + array( false, 'http//example.org', 'Reject missing colon in protocol' ), + array( false, 'http:/example.org', 'Reject missing slash in protocol' ), + array( false, 'http:example.org', 'Must have two slashes' ), # Following fail since hostname can be made of anything - array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ), + array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ), # (\w+:{0,1}\w*@)? - optional user:pass - array( true, 'http://user@host', 'Username provided' ), - array( true, 'http://user:@host', 'Username provided, no password' ), - array( true, 'http://user:pass@host', 'Username and password provided' ), + array( true, 'http://user@host', 'Username provided' ), + array( true, 'http://user:@host', 'Username provided, no password' ), + array( true, 'http://user:pass@host', 'Username and password provided' ), # (\S+) - host part is made of anything not whitespaces array( false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ), @@ -115,7 +117,7 @@ class HttpTest extends MediaWikiTestCase { array( true, 'http://example/&' ), # Fragment - array( true, 'http://exam#ple.org', ), # This one is valid, really! + array( true, 'http://exam#ple.org', ), # This one is valid, really! array( true, 'http://example.org:80#anchor' ), array( true, 'http://example.org/?id#anchor' ), array( true, 'http://example.org/?#anchor' ), @@ -126,18 +128,19 @@ class HttpTest extends MediaWikiTestCase { /** * Warning: - * + * * These tests are for code that makes use of an artifact of how CURL * handles header reporting on redirect pages, and will need to be * rewritten when bug 29232 is taken care of (high-level handling of * HTTP redirects). */ - function testRelativeRedirections() { - $h = new MWHttpRequestTester( 'http://oldsite/file.ext' ); + public function testRelativeRedirections() { + $h = MWHttpRequestTester::factory( 'http://oldsite/file.ext' ); + # Forge a Location header $h->setRespHeaders( 'location', array( - 'http://newsite/file.ext', - '/newfile.ext', + 'http://newsite/file.ext', + '/newfile.ext', ) ); # Verify we correctly fix the Location @@ -148,7 +151,7 @@ class HttpTest extends MediaWikiTestCase { ); $h->setRespHeaders( 'location', array( - 'https://oldsite/file.ext' + 'https://oldsite/file.ext' ) ); $this->assertEquals( @@ -158,23 +161,56 @@ class HttpTest extends MediaWikiTestCase { ); $h->setRespHeaders( 'location', array( - '/anotherfile.ext', - 'http://anotherfile/hoster.ext', - 'https://anotherfile/hoster.ext' + '/anotherfile.ext', + 'http://anotherfile/hoster.ext', + 'https://anotherfile/hoster.ext' ) ); $this->assertEquals( 'https://anotherfile/hoster.ext', - $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!") + $h->getFinalUrl( "Relative file path Location: should keep the latest host and scheme!" ) ); } } /** - * Class to let us overwrite MWHttpREquest respHeaders variable + * Class to let us overwrite MWHttpRequest respHeaders variable */ class MWHttpRequestTester extends MWHttpRequest { + + // function derived from the MWHttpRequest factory function but + // returns appropriate tester class here + public static function factory( $url, $options = null ) { + if ( !Http::$httpEngine ) { + Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php'; + } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) { + throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' . + 'Http::$httpEngine is set to "curl"' ); + } + + switch ( Http::$httpEngine ) { + case 'curl': + return new CurlHttpRequestTester( $url, $options ); + case 'php': + if ( !wfIniGetBool( 'allow_url_fopen' ) ) { + throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' . + ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' ); + } + + return new PhpHttpRequestTester( $url, $options ); + default: + } + } +} + +class CurlHttpRequestTester extends CurlHttpRequest { + function setRespHeaders( $name, $value ) { + $this->respHeaders[$name] = $value; + } +} + +class PhpHttpRequestTester extends PhpHttpRequest { function setRespHeaders( $name, $value ) { - $this->respHeaders[$name] = $value ; + $this->respHeaders[$name] = $value; } } diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php index f50b2fe9..c074eea6 100644 --- a/tests/phpunit/includes/IPTest.php +++ b/tests/phpunit/includes/IPTest.php @@ -1,7 +1,12 @@ <?php /** - * Tests for IP validity functions. Ported from /t/inc/IP.t by avar. + * Tests for IP validity functions. + * + * Ported from /t/inc/IP.t by avar. + * * @group IP + * @todo Test methods in this call should be split into a method and a + * dataprovider. */ class IPTest extends MediaWikiTestCase { @@ -11,13 +16,13 @@ class IPTest extends MediaWikiTestCase { */ public function testisIPAddress() { $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); + $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' ); $this->assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' ); $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' ); $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1'), 'IPv6 with a double :: occurrence' ); - $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::'), 'IPv6 with a double :: occurrence, last at end' ); - $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1'), 'IPv6 with a double :: occurrence, firt at beginning' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1' ), 'IPv6 with a double :: occurrence' ); + $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::' ), 'IPv6 with a double :: occurrence, last at end' ); + $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1' ), 'IPv6 with a double :: occurrence, firt at beginning' ); $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' ); $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' ); $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' ); @@ -81,9 +86,9 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' ); $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); - $this->assertTrue( IP::isIPv6( '2001::df'), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); + $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' ); + $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); + $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0' ), 'IPv6 with "::" and 8 words' ); $this->assertFalse( IP::isIPv6( 'fc::100:a:d:1:e:ac:0:1' ), 'IPv6 with 9 words' ); @@ -96,7 +101,7 @@ class IPTest extends MediaWikiTestCase { */ public function testisIPv4() { $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' ); - $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); + $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' ); $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' ); $this->assertFalse( IP::isIPv4( 'abc' ) ); $this->assertFalse( IP::isIPv4( ':' ) ); @@ -119,7 +124,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%01d", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f.$f.$f.$f"; - $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv4 address" ); + $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" ); } } foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) { @@ -128,7 +133,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%02x", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertTrue( IP::isValid( $ip ) , "$ip is a valid IPv6 address" ); + $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" ); } } // test with some abbreviations @@ -143,9 +148,9 @@ class IPTest extends MediaWikiTestCase { $this->assertTrue( IP::isValid( 'fc::100' ), 'IPv6 with "::" and 2 words' ); $this->assertTrue( IP::isValid( 'fc::100:a' ), 'IPv6 with "::" and 3 words' ); - $this->assertTrue( IP::isValid( '2001::df'), 'IPv6 with "::" and 2 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df'), 'IPv6 with "::" and 5 words' ); - $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2'), 'IPv6 with "::" and 6 words' ); + $this->assertTrue( IP::isValid( '2001::df' ), 'IPv6 with "::" and 2 words' ); + $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' ); + $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' ); $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' ); @@ -173,7 +178,7 @@ class IPTest extends MediaWikiTestCase { $c = sprintf( "%02s", $i ); foreach ( array_unique( array( $a, $b, $c ) ) as $f ) { $ip = "$f:$f:$f:$f:$f:$f:$f:$f"; - $this->assertFalse( IP::isValid( $ip ) , "$ip is not a valid IPv6 address" ); + $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" ); } } // Have CIDR @@ -254,19 +259,68 @@ class IPTest extends MediaWikiTestCase { * @todo Most probably incomplete */ public function testSanitizeIP() { - $this->assertNull( IP::sanitizeIP('') ); - $this->assertNull( IP::sanitizeIP(' ') ); + $this->assertNull( IP::sanitizeIP( '' ) ); + $this->assertNull( IP::sanitizeIP( ' ' ) ); } /** - * test wrapper around ip2long which might return -1 or false depending on PHP version * @covers IP::toUnsigned + * @dataProvider provideToUnsigned */ - public function testip2longWrapper() { - // @todo FIXME: Add more tests ? - $this->assertEquals( pow(2,32) - 1, IP::toUnsigned( '255.255.255.255' )); - $i = 'IN.VA.LI.D'; - $this->assertFalse( IP::toUnSigned( $i ) ); + public function testToUnsigned( $expected, $input ) { + $result = IP::toUnsigned( $input ); + $this->assertTrue( $result === false || is_string( $result ) || is_int( $result ) ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testToUnsigned() + */ + public static function provideToUnsigned() { + return array( + array( 1, '0.0.0.1' ), + array( 16909060, '1.2.3.4' ), + array( 2130706433, '127.0.0.1' ), + array( '2147483648', '128.0.0.0' ), + array( '3735931646', '222.173.202.254' ), + array( pow( 2, 32 ) - 1, '255.255.255.255' ), + array( false, 'IN.VA.LI.D' ), + array( 1, '::1' ), + array( '42540766452641154071740215577757643572', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ), + array( '42540766452641154071740215577757643572', '2001:db8:85a3::8a2e:0370:7334' ), + array( false, 'IN:VA::LI:D' ), + array( false, ':::1' ) + ); + } + + /** + * @covers IP::toHex + * @dataProvider provideToHex + */ + public function testToHex( $expected, $input ) { + $result = IP::toHex( $input ); + $this->assertTrue( $result === false || is_string( $result ) ); + $this->assertEquals( $expected, $result ); + } + + /** + * Provider for IP::testToHex() + */ + public static function provideToHex() { + return array( + array( '00000001', '0.0.0.1' ), + array( '01020304', '1.2.3.4' ), + array( '7F000001', '127.0.0.1' ), + array( '80000000', '128.0.0.0' ), + array( 'DEADCAFE', '222.173.202.254' ), + array( 'FFFFFFFF', '255.255.255.255' ), + array( false, 'IN.VA.LI.D' ), + array( 'v6-00000000000000000000000000000001', '::1' ), + array( 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ), + array( 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ), + array( false, 'IN:VA::LI:D' ), + array( false, ':::1' ) + ); } /** @@ -284,7 +338,7 @@ class IPTest extends MediaWikiTestCase { } // Private wrapper used to test CIDR Parsing. - private function assertFalseCIDR( $CIDR, $msg='' ) { + private function assertFalseCIDR( $CIDR, $msg = '' ) { $ff = array( false, false ); $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg ); } @@ -299,15 +353,15 @@ class IPTest extends MediaWikiTestCase { * @covers IP::hexToQuad */ public function testHexToQuad() { - $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '00000001' ) ); - $this->assertEquals( '255.0.0.0' , IP::hexToQuad( 'FF000000' ) ); + $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) ); + $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) ); $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) ); - $this->assertEquals( '10.188.222.255' , IP::hexToQuad( '0ABCDEFF' ) ); + $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) ); // hex not left-padded... - $this->assertEquals( '0.0.0.0' , IP::hexToQuad( '0' ) ); - $this->assertEquals( '0.0.0.1' , IP::hexToQuad( '1' ) ); - $this->assertEquals( '0.0.0.255' , IP::hexToQuad( 'FF' ) ); - $this->assertEquals( '0.0.255.0' , IP::hexToQuad( 'FF00' ) ); + $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) ); + $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) ); + $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) ); + $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) ); } /** @@ -325,11 +379,11 @@ class IPTest extends MediaWikiTestCase { $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) ); // hex not left-padded... - $this->assertEquals( '0:0:0:0:0:0:0:0' , IP::hexToOctet( '0' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:1' , IP::hexToOctet( '1' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FF' , IP::hexToOctet( 'FF' ) ); - $this->assertEquals( '0:0:0:0:0:0:0:FFD0' , IP::hexToOctet( 'FFD0' ) ); - $this->assertEquals( '0:0:0:0:0:0:FA00:0' , IP::hexToOctet( 'FA000000' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) ); + $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) ); + $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) ); $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) ); } @@ -338,43 +392,42 @@ class IPTest extends MediaWikiTestCase { * representing the network mask and the bit mask. * @covers IP::parseCIDR */ - function testCIDRParsing() { - $this->assertFalseCIDR( '192.0.2.0' , "missing mask" ); + public function testCIDRParsing() { + $this->assertFalseCIDR( '192.0.2.0', "missing mask" ); $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" ); // Verify if statement - $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" ); + $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" ); $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" ); - $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" ); - $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" ); + $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" ); + $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" ); // Check internal logic # 0 mask always result in array(0,0) - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('192.0.0.2/0') ); - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('0.0.0.0/0') ); - $this->assertEquals( array( 0, 0 ), IP::parseCIDR('255.255.255.255/0') ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '192.0.0.2/0' ) ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '0.0.0.0/0' ) ); + $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '255.255.255.255/0' ) ); // @todo FIXME: Add more tests. # This part test network shifting - $this->assertNet( '192.0.0.0' , '192.0.0.2/24' ); - $this->assertNet( '192.168.5.0', '192.168.5.13/24'); - $this->assertNet( '10.0.0.160' , '10.0.0.161/28' ); - $this->assertNet( '10.0.0.0' , '10.0.0.3/28' ); - $this->assertNet( '10.0.0.0' , '10.0.0.3/30' ); - $this->assertNet( '10.0.0.4' , '10.0.0.4/30' ); + $this->assertNet( '192.0.0.0', '192.0.0.2/24' ); + $this->assertNet( '192.168.5.0', '192.168.5.13/24' ); + $this->assertNet( '10.0.0.160', '10.0.0.161/28' ); + $this->assertNet( '10.0.0.0', '10.0.0.3/28' ); + $this->assertNet( '10.0.0.0', '10.0.0.3/30' ); + $this->assertNet( '10.0.0.4', '10.0.0.4/30' ); $this->assertNet( '172.17.32.0', '172.17.35.48/21' ); - $this->assertNet( '10.128.0.0' , '10.135.0.0/9' ); - $this->assertNet( '134.0.0.0' , '134.0.5.1/8' ); + $this->assertNet( '10.128.0.0', '10.135.0.0/9' ); + $this->assertNet( '134.0.0.0', '134.0.5.1/8' ); } - /** * @covers IP::canonicalize */ public function testIPCanonicalizeOnValidIp() { $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ), - 'Canonicalization of a valid IP returns it unchanged' ); + 'Canonicalization of a valid IP returns it unchanged' ); } /** @@ -405,27 +458,27 @@ class IPTest extends MediaWikiTestCase { } /** Provider for testIPIsInRange() */ - function provideIPsAndRanges() { - # Format: (expected boolean, address, range, optional message) + public static function provideIPsAndRanges() { + # Format: (expected boolean, address, range, optional message) return array( # IPv4 - array( true , '192.0.2.0' , '192.0.2.0/24', 'Network address' ), - array( true , '192.0.2.77' , '192.0.2.0/24', 'Simple address' ), - array( true , '192.0.2.255' , '192.0.2.0/24', 'Broadcast address' ), + array( true, '192.0.2.0', '192.0.2.0/24', 'Network address' ), + array( true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ), + array( true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ), - array( false, '0.0.0.0' , '192.0.2.0/24' ), - array( false, '255.255.255' , '192.0.2.0/24' ), + array( false, '0.0.0.0', '192.0.2.0/24' ), + array( false, '255.255.255', '192.0.2.0/24' ), # IPv6 - array( false, '::1' , '2001:DB8::/32' ), - array( false, '::' , '2001:DB8::/32' ), + array( false, '::1', '2001:DB8::/32' ), + array( false, '::', '2001:DB8::/32' ), array( false, 'FE80::1', '2001:DB8::/32' ), - array( true , '2001:DB8::' , '2001:DB8::/32' ), - array( true , '2001:0DB8::' , '2001:DB8::/32' ), - array( true , '2001:DB8::1' , '2001:DB8::/32' ), - array( true , '2001:0DB8::1', '2001:DB8::/32' ), - array( true , '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', + array( true, '2001:DB8::', '2001:DB8::/32' ), + array( true, '2001:0DB8::', '2001:DB8::/32' ), + array( true, '2001:DB8::1', '2001:DB8::/32' ), + array( true, '2001:0DB8::1', '2001:DB8::/32' ), + array( true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF', '2001:DB8::/32' ), array( false, '2001:0DB8:F::', '2001:DB8::/96' ), @@ -436,14 +489,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::splitHostAndPort(). * @dataProvider provideSplitHostAndPort */ - function testSplitHostAndPort( $expected, $input, $description ) { + public function testSplitHostAndPort( $expected, $input, $description ) { $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description ); } /** * Provider for IP::splitHostAndPort() */ - function provideSplitHostAndPort() { + public static function provideSplitHostAndPort() { return array( array( false, '[', 'Unclosed square bracket' ), array( false, '[::', 'Unclosed square bracket 2' ), @@ -463,7 +516,7 @@ class IPTest extends MediaWikiTestCase { * Test for IP::combineHostAndPort() * @dataProvider provideCombineHostAndPort */ - function testCombineHostAndPort( $expected, $input, $description ) { + public function testCombineHostAndPort( $expected, $input, $description ) { list( $host, $port, $defaultPort ) = $input; $this->assertEquals( $expected, @@ -474,7 +527,7 @@ class IPTest extends MediaWikiTestCase { /** * Provider for IP::combineHostAndPort() */ - function provideCombineHostAndPort() { + public static function provideCombineHostAndPort() { return array( array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ), array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ), @@ -487,14 +540,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::sanitizeRange() * @dataProvider provideIPCIDRs */ - function testSanitizeRange( $input, $expected, $description ) { + public function testSanitizeRange( $input, $expected, $description ) { $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description ); } /** * Provider for IP::testSanitizeRange() */ - function provideIPCIDRs() { + public static function provideIPCIDRs() { return array( array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ), array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ), @@ -511,14 +564,14 @@ class IPTest extends MediaWikiTestCase { * Test for IP::prettifyIP() * @dataProvider provideIPsToPrettify */ - function testPrettifyIP( $ip, $prettified ) { + public function testPrettifyIP( $ip, $prettified ) { $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" ); } /** * Provider for IP::testPrettifyIP() */ - function provideIPsToPrettify() { + public static function provideIPsToPrettify() { return array( array( '0:0:0:0:0:0:0:0', '::' ), array( '0:0:0::0:0:0', '::' ), diff --git a/tests/phpunit/includes/JsonTest.php b/tests/phpunit/includes/JsonTest.php deleted file mode 100644 index 75dd18d5..00000000 --- a/tests/phpunit/includes/JsonTest.php +++ /dev/null @@ -1,33 +0,0 @@ -<?php - -class JsonTest extends MediaWikiTestCase { - - function testPhpBug46944Test() { - - $this->assertNotEquals( - '\ud840\udc00', - strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), - 'Test encoding an broken json_encode character (U+20000)' - ); - - - } - - function testDecodeVarTypes() { - - $this->assertInternalType( - 'object', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), - 'Default to object' - ); - - $this->assertInternalType( - 'array', - FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), - 'Optional array' - ); - - } - -} - diff --git a/tests/phpunit/includes/LanguageConverterTest.php b/tests/phpunit/includes/LanguageConverterTest.php index baf28b07..7c2134b9 100644 --- a/tests/phpunit/includes/LanguageConverterTest.php +++ b/tests/phpunit/includes/LanguageConverterTest.php @@ -4,60 +4,65 @@ class LanguageConverterTest extends MediaWikiLangTestCase { protected $lang = null; protected $lc = null; - function setUp() { + protected function setUp() { parent::setUp(); - global $wgMemc, $wgRequest, $wgUser, $wgContLang; - $wgUser = new User; - $wgRequest = new FauxRequest( array() ); - $wgMemc = new EmptyBagOStuff; - $wgContLang = Language::factory( 'tg' ); + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'tg' ), + 'wgLanguageCode' => 'tg', + 'wgDefaultLanguageVariant' => false, + 'wgMemc' => new EmptyBagOStuff, + 'wgRequest' => new FauxRequest( array() ), + 'wgUser' => new User, + ) ); + $this->lang = new LanguageToTest(); - $this->lc = new TestConverter( $this->lang, 'tg', - array( 'tg', 'tg-latn' ) ); + $this->lc = new TestConverter( + $this->lang, 'tg', + array( 'tg', 'tg-latn' ) + ); } - function tearDown() { - global $wgMemc; - unset( $wgMemc ); + protected function tearDown() { unset( $this->lc ); unset( $this->lang ); + parent::tearDown(); } - function testGetPreferredVariantDefaults() { + public function testGetPreferredVariantDefaults() { $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaders() { + public function testGetPreferredVariantHeaders() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg-latn' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderWeight() { + public function testGetPreferredVariantHeaderWeight() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg;q=1' ); $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderWeight2() { + public function testGetPreferredVariantHeaderWeight2() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'tg-latn;q=1' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderMulti() { + public function testGetPreferredVariantHeaderMulti() { global $wgRequest; $wgRequest->setHeader( 'Accept-Language', 'en, tg-latn;q=1' ); $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantUserOption() { + public function testGetPreferredVariantUserOption() { global $wgUser; $wgUser = new User; @@ -70,8 +75,22 @@ class LanguageConverterTest extends MediaWikiLangTestCase { $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantHeaderUserVsUrl() { - global $wgRequest, $wgUser, $wgContLang; + public function testGetPreferredVariantUserOptionForForeignLanguage() { + global $wgContLang, $wgUser; + + $wgContLang = Language::factory( 'en' ); + $wgUser = new User; + $wgUser->load(); // from 'defaults' + $wgUser->mId = 1; + $wgUser->mDataLoaded = true; + $wgUser->mOptionsLoaded = true; + $wgUser->setOption( 'variant-tg', 'tg-latn' ); + + $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); + } + + public function testGetPreferredVariantHeaderUserVsUrl() { + global $wgContLang, $wgRequest, $wgUser; $wgContLang = Language::factory( 'tg-latn' ); $wgRequest->setVal( 'variant', 'tg' ); @@ -79,20 +98,20 @@ class LanguageConverterTest extends MediaWikiLangTestCase { $wgUser->setId( 1 ); $wgUser->mFrom = 'defaults'; $wgUser->mOptionsLoaded = true; - $wgUser->setOption( 'variant', 'tg-latn' ); // The user's data is ignored - // because the variant is set in the URL. + // The user's data is ignored because the variant is set in the URL. + $wgUser->setOption( 'variant', 'tg-latn' ); $this->assertEquals( 'tg', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantDefaultLanguageVariant() { + public function testGetPreferredVariantDefaultLanguageVariant() { global $wgDefaultLanguageVariant; $wgDefaultLanguageVariant = 'tg-latn'; $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() ); } - function testGetPreferredVariantDefaultLanguageVsUrlVariant() { + public function testGetPreferredVariantDefaultLanguageVsUrlVariant() { global $wgDefaultLanguageVariant, $wgRequest, $wgContLang; $wgContLang = Language::factory( 'tg-latn' ); @@ -115,10 +134,9 @@ class TestConverter extends LanguageConverter { function loadDefaultTables() { $this->mTables = array( 'tg-latn' => new ReplacementArray( $this->table ), - 'tg' => new ReplacementArray() + 'tg' => new ReplacementArray() ); } - } class LanguageToTest extends Language { diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php index e467f3cd..478a2ffc 100644 --- a/tests/phpunit/includes/LicensesTest.php +++ b/tests/phpunit/includes/LicensesTest.php @@ -2,7 +2,7 @@ class LicensesTest extends MediaWikiTestCase { - function testLicenses() { + public function testLicenses() { $str = " * Free licenses: ** GFDL|Debian disagrees @@ -14,8 +14,8 @@ class LicensesTest extends MediaWikiTestCase { 'section' => 'description', 'id' => 'wpLicense', 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined - 'name' => 'AnotherName', - 'licenses' => $str, + 'name' => 'AnotherName', + 'licenses' => $str, ) ); $this->assertThat( $lc, $this->isInstanceOf( 'Licenses' ) ); } diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php new file mode 100644 index 00000000..b605f08f --- /dev/null +++ b/tests/phpunit/includes/LinkerTest.php @@ -0,0 +1,71 @@ +<?php + +class LinkerTest extends MediaWikiLangTestCase { + + /** + * @dataProvider provideCasesForUserLink + * @covers Linker::userLink + */ + public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) { + $this->setMwGlobals( array( + 'wgArticlePath' => '/wiki/$1', + 'wgWellFormedXml' => true, + ) ); + + $this->assertEquals( $expected, + Linker::userLink( $userId, $userName, $altUserName, $msg ) + ); + } + + public static function provideCasesForUserLink() { + # Format: + # - expected + # - userid + # - username + # - optional altUserName + # - optional message + return array( + + ### ANONYMOUS USER ######################################## + array( + '<a href="/wiki/Special:Contributions/JohnDoe" title="Special:Contributions/JohnDoe" class="mw-userlink">JohnDoe</a>', + 0, 'JohnDoe', false, + ), + array( + '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">::1</a>', + 0, '::1', false, + 'Anonymous with pretty IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" title="Special:Contributions/0:0:0:0:0:0:0:1" class="mw-userlink">::1</a>', + 0, '0:0:0:0:0:0:0:1', false, + 'Anonymous with almost pretty IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" class="mw-userlink">::1</a>', + 0, '0000:0000:0000:0000:0000:0000:0000:0001', false, + 'Anonymous with full IPv6' + ), + array( + '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">AlternativeUsername</a>', + 0, '::1', 'AlternativeUsername', + 'Anonymous with pretty IPv6 and an alternative username' + ), + + # IPV4 + array( + '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">127.0.0.1</a>', + 0, '127.0.0.1', false, + 'Anonymous with IPv4' + ), + array( + '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">AlternativeUsername</a>', + 0, '127.0.0.1', 'AlternativeUsername', + 'Anonymous with IPv4 and an alternative username' + ), + + ### Regular user ########################################## + # TODO! + ); + } +} diff --git a/tests/phpunit/includes/LinksUpdateTest.php b/tests/phpunit/includes/LinksUpdateTest.php index 49462001..5ade250e 100644 --- a/tests/phpunit/includes/LinksUpdateTest.php +++ b/tests/phpunit/includes/LinksUpdateTest.php @@ -7,33 +7,39 @@ */ class LinksUpdateTest extends MediaWikiTestCase { - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); - $this->tablesUsed = array_merge ( $this->tablesUsed, - array( 'interwiki', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + $this->tablesUsed = array_merge( $this->tablesUsed, + array( + 'interwiki', + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' + ) + ); } - function setUp() { + protected function setUp() { + parent::setUp(); $dbw = wfGetDB( DB_MASTER ); - $dbw->replace( 'interwiki', - array('iw_prefix'), - array( 'iw_prefix' => 'linksupdatetest', - 'iw_url' => 'http://testing.com/wiki/$1', - 'iw_api' => 'http://testing.com/w/api.php', - 'iw_local' => 0, - 'iw_trans' => 0, - 'iw_wikiid' => 'linksupdatetest', - ) ); + $dbw->replace( + 'interwiki', + array( 'iw_prefix' ), + array( + 'iw_prefix' => 'linksupdatetest', + 'iw_url' => 'http://testing.com/wiki/$1', + 'iw_api' => 'http://testing.com/w/api.php', + 'iw_local' => 0, + 'iw_trans' => 0, + 'iw_wikiid' => 'linksupdatetest', + ) + ); } protected function makeTitleAndParserOutput( $name, $id ) { @@ -54,18 +60,30 @@ class LinksUpdateTest extends MediaWikiTestCase { $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored - $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( array( NS_MAIN, 'Foo' ), ) ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state.... + ), $update->getAddedLinks() ); $po = new ParserOutput(); $po->setTitleText( $t->getPrefixedText() ); $po->addLink( Title::newFromText( "Bar" ) ); + $po->addLink( Title::newFromText( "Talk:Bar" ) ); - $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( + $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array( array( NS_MAIN, 'Bar' ), + array( NS_TALK, 'Bar' ), ) ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Bar' ), + Title::makeTitle( NS_TALK, 'Bar' ), + ), $update->getAddedLinks() ); + $this->assertArrayEquals( array( + Title::makeTitle( NS_MAIN, 'Foo' ), + ), $update->getRemovedLinks() ); } public function testUpdate_externallinks() { @@ -79,6 +97,8 @@ class LinksUpdateTest extends MediaWikiTestCase { } public function testUpdate_categorylinks() { + $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' ); + list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); $po->addCategory( "Foo", "FOO" ); @@ -114,7 +134,6 @@ class LinksUpdateTest extends MediaWikiTestCase { $po->addImage( "Foo.png" ); - $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array( array( 'Foo.png' ), ) ); @@ -123,8 +142,7 @@ class LinksUpdateTest extends MediaWikiTestCase { public function testUpdate_langlinks() { list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 ); - $po->addLanguageLink( Title::newFromText( "en:Foo" ) ); - + $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() ); $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array( array( 'En', 'Foo' ), @@ -141,14 +159,17 @@ class LinksUpdateTest extends MediaWikiTestCase { ) ); } - #@todo: test recursive, too! + // @todo test recursive, too! - protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, Array $expectedRows ) { + protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) { $update = new LinksUpdate( $title, $parserOutput ); + //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction. + $update->beginTransaction(); $update->doUpdate(); + $update->commitTransaction(); $this->assertSelect( $table, $fields, $condition, $expectedRows ); + return $update; } } - diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php index 5b26b89c..2501c783 100644 --- a/tests/phpunit/includes/LocalFileTest.php +++ b/tests/phpunit/includes/LocalFileTest.php @@ -6,19 +6,20 @@ */ class LocalFileTest extends MediaWikiTestCase { - function setUp() { - global $wgCapitalLinks; - $wgCapitalLinks = true; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgCapitalLinks', true ); $info = array( - 'name' => 'test', - 'directory' => '/testdir', - 'url' => '/testurl', - 'hashLevels' => 2, + 'name' => 'test', + 'directory' => '/testdir', + 'url' => '/testurl', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( 'cont1' => "/testdir/local-backend/tempimages/cont1", @@ -34,75 +35,73 @@ class LocalFileTest extends MediaWikiTestCase { $this->file_lc = $this->repo_lc->newFile( 'test!' ); } - function testGetHashPath() { + public function testGetHashPath() { $this->assertEquals( '', $this->file_hl0->getHashPath() ); $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); } - function testGetRel() { + public function testGetRel() { $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); } - function testGetUrlRel() { + public function testGetUrlRel() { $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); } - function testGetArchivePath() { + public function testGetArchivePath() { $this->assertEquals( 'mwstore://local-backend/test-public/archive', $this->file_hl0->getArchivePath() ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2', $this->file_hl2->getArchivePath() ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/!', $this->file_hl0->getArchivePath( '!' ) ); $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) ); } - function testGetThumbPath() { + public function testGetThumbPath() { $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!', $this->file_hl0->getThumbPath() ); $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!', $this->file_hl2->getThumbPath() ); $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) ); $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) ); } - function testGetArchiveUrl() { + public function testGetArchiveUrl() { $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); } - function testGetThumbUrl() { + public function testGetThumbUrl() { $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); } - function testGetArchiveVirtualUrl() { + public function testGetArchiveVirtualUrl() { $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); $this->assertEquals( 'mwrepo://test/public/archive/a/a2', $this->file_hl2->getArchiveVirtualUrl() ); $this->assertEquals( 'mwrepo://test/public/archive/%21', $this->file_hl0->getArchiveVirtualUrl( '!' ) ); $this->assertEquals( 'mwrepo://test/public/archive/a/a2/%21', $this->file_hl2->getArchiveVirtualUrl( '!' ) ); } - function testGetThumbVirtualUrl() { + public function testGetThumbVirtualUrl() { $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); $this->assertEquals( 'mwrepo://test/thumb/Test%21/%21', $this->file_hl0->getThumbVirtualUrl( '!' ) ); $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21/%21', $this->file_hl2->getThumbVirtualUrl( '!' ) ); } - function testGetUrl() { + public function testGetUrl() { $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); } - function testWfLocalFile() { + public function testWfLocalFile() { $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" ); $this->assertThat( $file, $this->isInstanceOf( 'LocalFile' ), 'wfLocalFile() returns LocalFile for valid Titles' ); } } - - diff --git a/tests/phpunit/includes/LocalisationCacheTest.php b/tests/phpunit/includes/LocalisationCacheTest.php index 356db87c..b34847aa 100644 --- a/tests/phpunit/includes/LocalisationCacheTest.php +++ b/tests/phpunit/includes/LocalisationCacheTest.php @@ -5,15 +5,15 @@ class LocalisationCacheTest extends MediaWikiTestCase { $cache = Language::getLocalisationCache(); $this->assertEquals( - $cache->getItem( 'ru', 'pluralRules' ), - $cache->getItem( 'os', 'pluralRules' ), - 'os plural rules (undefined) fallback to ru (defined)' + $cache->getItem( 'ar', 'pluralRules' ), + $cache->getItem( 'arz', 'pluralRules' ), + 'arz plural rules (undefined) fallback to ar (defined)' ); $this->assertEquals( - $cache->getItem( 'ru', 'compiledPluralRules' ), - $cache->getItem( 'os', 'compiledPluralRules' ), - 'os compiled plural rules (undefined) fallback to ru (defined)' + $cache->getItem( 'ar', 'compiledPluralRules' ), + $cache->getItem( 'arz', 'compiledPluralRules' ), + 'arz compiled plural rules (undefined) fallback to ar (defined)' ); $this->assertNotEquals( diff --git a/tests/phpunit/includes/MWExceptionHandlerTest.php b/tests/phpunit/includes/MWExceptionHandlerTest.php new file mode 100644 index 00000000..987dfa83 --- /dev/null +++ b/tests/phpunit/includes/MWExceptionHandlerTest.php @@ -0,0 +1,73 @@ +<?php +/** + * Tests for includes/Exception.php. + * + * @author Antoine Musso + * @copyright Copyright © 2013, Antoine Musso + * @copyright Copyright © 2013, Wikimedia Foundation Inc. + * @file + */ + +class MWExceptionHandlerTest extends MediaWikiTestCase { + + /** + * @covers MWExceptionHandler::getRedactedTrace + */ + function testGetRedactedTrace() { + try { + $array = array( 'a', 'b' ); + $object = new StdClass(); + self::helperThrowAnException( $array, $object ); + } catch (Exception $e) { + } + + # Make sure our strack trace contains an array and an object passed to + # some function in the stacktrace. Else, we can not assert the trace + # redaction achieved its job. + $trace = $e->getTrace(); + $hasObject = false; + $hasArray = false; + foreach ( $trace as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $hasObject = $hasObject || is_object( $arg ); + $hasArray = $hasArray || is_array( $arg ); + } + + if( $hasObject && $hasArray ) { + break; + } + } + $this->assertTrue( $hasObject, + "The stacktrace must have a function having an object has parameter" ); + $this->assertTrue( $hasArray, + "The stacktrace must have a function having an array has parameter" ); + + # Now we redact the trace.. and make sure no function arguments are + # arrays or objects. + $redacted = MWExceptionHandler::getRedactedTrace( $e ); + + foreach ( $redacted as $frame ) { + if ( ! isset( $frame['args'] ) ) { + continue; + } + foreach ( $frame['args'] as $arg ) { + $this->assertNotInternalType( 'array', $arg); + $this->assertNotInternalType( 'object', $arg); + } + } + } + + /** + * Helper function for testExpandArgumentsInCall + * + * Pass it an object and an array :-) + * + * @throws Exception + */ + protected static function helperThrowAnException( $a, $b ) { + throw new Exception(); + } +} diff --git a/tests/phpunit/includes/MWFunctionTest.php b/tests/phpunit/includes/MWFunctionTest.php index ed5e7602..d86f2c9b 100644 --- a/tests/phpunit/includes/MWFunctionTest.php +++ b/tests/phpunit/includes/MWFunctionTest.php @@ -1,85 +1,29 @@ <?php class MWFunctionTest extends MediaWikiTestCase { - - function testCallUserFuncWorkarounds() { - - $this->assertEquals( - call_user_func( array( 'MWFunctionTest', 'someMethod' ) ), - MWFunction::call( 'MWFunctionTest::someMethod' ) - ); - $this->assertEquals( - call_user_func( array( 'MWFunctionTest', 'someMethod' ), 'foo', 'bar', 'baz' ), - MWFunction::call( 'MWFunctionTest::someMethod', 'foo', 'bar', 'baz' ) - ); - - - - $this->assertEquals( - call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array() ), - MWFunction::callArray( 'MWFunctionTest::someMethod', array() ) - ); - $this->assertEquals( - call_user_func_array( array( 'MWFunctionTest', 'someMethod' ), array( 'foo', 'bar', 'baz' ) ), - MWFunction::callArray( 'MWFunctionTest::someMethod', array( 'foo', 'bar', 'baz' ) ) - ); - - } - - function testNewObjFunction() { - + public function testNewObjFunction() { $arg1 = 'Foo'; $arg2 = 'Bar'; $arg3 = array( 'Baz' ); $arg4 = new ExampleObject; - + $args = array( $arg1, $arg2, $arg3, $arg4 ); - + $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 ); - - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args )->args, + $this->assertEquals( + MWFunction::newObj( 'MWBlankClass', $args )->args, $newObject->args ); - - $this->assertEquals( - MWFunction::newObj( 'MWBlankClass', $args, true )->args, - $newObject->args, - 'Works even with PHP version < 5.1.3' - ); - - } - - /** - * @expectedException MWException - */ - function testCallingParentFails() { - - MWFunction::call( 'parent::foo' ); - } - - /** - * @expectedException MWException - */ - function testCallingSelfFails() { - - MWFunction::call( 'self::foo' ); } - - public static function someMethod() { - return func_get_args(); - } - } class MWBlankClass { - + public $args = array(); - + function __construct( $arg1, $arg2, $arg3, $arg4 ) { $this->args = array( $arg1, $arg2, $arg3, $arg4 ); } - } class ExampleObject { diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php index 3b05d675..10e9db61 100644 --- a/tests/phpunit/includes/MWNamespaceTest.php +++ b/tests/phpunit/includes/MWNamespaceTest.php @@ -11,21 +11,22 @@ * */ class MWNamespaceTest extends MediaWikiTestCase { - /** - * Sets up the fixture, for example, opens a network connection. - * This method is called before a test is executed. - */ protected function setUp() { - } + parent::setUp(); - /** - * Tears down the fixture, for example, closes a network connection. - * This method is called after a test is executed. - */ - protected function tearDown() { + $this->setMwGlobals( array( + 'wgContentNamespaces' => array( NS_MAIN ), + 'wgNamespacesWithSubpages' => array( + NS_TALK => true, + NS_USER => true, + NS_USER_TALK => true, + ), + 'wgCapitalLinks' => true, + 'wgCapitalLinkOverrides' => array(), + 'wgNonincludableNamespaces' => array(), + ) ); } - #### START OF TESTS ######################################################### /** @@ -41,18 +42,18 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsSubject() { // Special namespaces - $this->assertIsSubject( NS_MEDIA ); + $this->assertIsSubject( NS_MEDIA ); $this->assertIsSubject( NS_SPECIAL ); // Subject pages $this->assertIsSubject( NS_MAIN ); $this->assertIsSubject( NS_USER ); - $this->assertIsSubject( 100 ); # user defined + $this->assertIsSubject( 100 ); # user defined // Talk pages - $this->assertIsNotSubject( NS_TALK ); + $this->assertIsNotSubject( NS_TALK ); $this->assertIsNotSubject( NS_USER_TALK ); - $this->assertIsNotSubject( 101 ); # user defined + $this->assertIsNotSubject( 101 ); # user defined } /** @@ -61,18 +62,18 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsTalk() { // Special namespaces - $this->assertIsNotTalk( NS_MEDIA ); + $this->assertIsNotTalk( NS_MEDIA ); $this->assertIsNotTalk( NS_SPECIAL ); // Subject pages - $this->assertIsNotTalk( NS_MAIN ); - $this->assertIsNotTalk( NS_USER ); - $this->assertIsNotTalk( 100 ); # user defined + $this->assertIsNotTalk( NS_MAIN ); + $this->assertIsNotTalk( NS_USER ); + $this->assertIsNotTalk( 100 ); # user defined // Talk pages - $this->assertIsTalk( NS_TALK ); + $this->assertIsTalk( NS_TALK ); $this->assertIsTalk( NS_USER_TALK ); - $this->assertIsTalk( 101 ); # user defined + $this->assertIsTalk( 101 ); # user defined } /** @@ -124,7 +125,6 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testGetAssociated() { $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) ); $this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) ); - } ### Exceptions with getAssociated() @@ -134,7 +134,7 @@ class MWNamespaceTest extends MediaWikiTestCase { * @expectedException MWException */ public function testGetAssociatedExceptionsForNsMedia() { - $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); + $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) ); } /** @@ -147,14 +147,14 @@ class MWNamespaceTest extends MediaWikiTestCase { /** * @todo Implement testExists(). */ -/* + /* public function testExists() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } -*/ + */ /** * Test MWNamespace::equals @@ -188,7 +188,7 @@ class MWNamespaceTest extends MediaWikiTestCase { $this->assertSameSubject( NS_USER, NS_USER_TALK ); $this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE ); - $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); + $this->assertDifferentSubject( NS_SPECIAL, NS_MAIN ); } public function testSpecialAndMediaAreDifferentSubjects() { @@ -200,62 +200,63 @@ class MWNamespaceTest extends MediaWikiTestCase { NS_SPECIAL, NS_MEDIA, "NS_SPECIAL and NS_MEDIA are different subject namespaces" ); - } /** * @todo Implement testGetCanonicalNamespaces(). */ -/* + /* public function testGetCanonicalNamespaces() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } -*/ + */ /** * @todo Implement testGetCanonicalName(). */ -/* - public function testGetCanonicalName() { - // Remove the following lines when you implement this test. - $this->markTestIncomplete( - 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' - ); - } -*/ + /* + public function testGetCanonicalName() { + // Remove the following lines when you implement this test. + $this->markTestIncomplete( + 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' + ); + } + */ /** * @todo Implement testGetCanonicalIndex(). */ -/* + /* public function testGetCanonicalIndex() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } -*/ + */ + /** * @todo Implement testGetValidNamespaces(). */ -/* + /* public function testGetValidNamespaces() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet. Rely on $wgCanonicalNamespaces.' ); } -*/ + */ + /** */ public function testCanTalk() { - $this->assertCanNotTalk( NS_MEDIA ); + $this->assertCanNotTalk( NS_MEDIA ); $this->assertCanNotTalk( NS_SPECIAL ); - $this->assertCanTalk( NS_MAIN ); - $this->assertCanTalk( NS_TALK ); - $this->assertCanTalk( NS_USER ); + $this->assertCanTalk( NS_MAIN ); + $this->assertCanTalk( NS_TALK ); + $this->assertCanTalk( NS_USER ); $this->assertCanTalk( NS_USER_TALK ); // User defined namespaces @@ -268,82 +269,41 @@ class MWNamespaceTest extends MediaWikiTestCase { public function testIsContent() { // NS_MAIN is a content namespace per DefaultSettings.php // and per function definition. - $this->assertIsContent( NS_MAIN ); - - global $wgContentNamespaces; - - $saved = $wgContentNamespaces; - $wgContentNamespaces[] = NS_MAIN; $this->assertIsContent( NS_MAIN ); // Other namespaces which are not expected to be content - if ( isset( $wgContentNamespaces[NS_MEDIA] ) ) { - unset( $wgContentNamespaces[NS_MEDIA] ); - } - $this->assertIsNotContent( NS_MEDIA ); - if ( isset( $wgContentNamespaces[NS_SPECIAL] ) ) { - unset( $wgContentNamespaces[NS_SPECIAL] ); - } + $this->assertIsNotContent( NS_MEDIA ); $this->assertIsNotContent( NS_SPECIAL ); - - if ( isset( $wgContentNamespaces[NS_TALK] ) ) { - unset( $wgContentNamespaces[NS_TALK] ); - } $this->assertIsNotContent( NS_TALK ); - - if ( isset( $wgContentNamespaces[NS_USER] ) ) { - unset( $wgContentNamespaces[NS_USER] ); - } $this->assertIsNotContent( NS_USER ); - - if ( isset( $wgContentNamespaces[NS_CATEGORY] ) ) { - unset( $wgContentNamespaces[NS_CATEGORY] ); - } $this->assertIsNotContent( NS_CATEGORY ); - - if ( isset( $wgContentNamespaces[100] ) ) { - unset( $wgContentNamespaces[100] ); - } $this->assertIsNotContent( 100 ); - - $wgContentNamespaces = $saved; } /** * Similar to testIsContent() but alters the $wgContentNamespaces * global variable. */ - public function testIsContentWithAdditionsInWgContentNamespaces() { - // NS_MAIN is a content namespace per DefaultSettings.php - // and per function definition. - $this->assertIsContent( NS_MAIN ); + public function testIsContentAdvanced() { + global $wgContentNamespaces; - // Tests that user defined namespace #252 is not content: + // Test that user defined namespace #252 is not content $this->assertIsNotContent( 252 ); - # @todo FIXME: Is global saving really required for PHPUnit? // Bless namespace # 252 as a content namespace - global $wgContentNamespaces; - $savedGlobal = $wgContentNamespaces; $wgContentNamespaces[] = 252; + $this->assertIsContent( 252 ); // Makes sure NS_MAIN was not impacted $this->assertIsContent( NS_MAIN ); - - // Restore global - $wgContentNamespaces = $savedGlobal; - - // Verify namespaces after global restauration - $this->assertIsContent( NS_MAIN ); - $this->assertIsNotContent( 252 ); } public function testIsWatchable() { // Specials namespaces are not watchable - $this->assertIsNotWatchable( NS_MEDIA ); + $this->assertIsNotWatchable( NS_MEDIA ); $this->assertIsNotWatchable( NS_SPECIAL ); // Core defined namespaces are watchables @@ -356,68 +316,60 @@ class MWNamespaceTest extends MediaWikiTestCase { } public function testHasSubpages() { + global $wgNamespacesWithSubpages; + // Special namespaces: - $this->assertHasNotSubpages( NS_MEDIA ); + $this->assertHasNotSubpages( NS_MEDIA ); $this->assertHasNotSubpages( NS_SPECIAL ); - // namespaces without subpages - # save up global - global $wgNamespacesWithSubpages; - $saved = null; - if( array_key_exists( NS_MAIN, $wgNamespacesWithSubpages ) ) { - $saved = $wgNamespacesWithSubpages[NS_MAIN]; - unset( $wgNamespacesWithSubpages[NS_MAIN] ); - } - + // Namespaces without subpages $this->assertHasNotSubpages( NS_MAIN ); $wgNamespacesWithSubpages[NS_MAIN] = true; $this->assertHasSubpages( NS_MAIN ); + $wgNamespacesWithSubpages[NS_MAIN] = false; $this->assertHasNotSubpages( NS_MAIN ); - # restore global - if( $saved !== null ) { - $wgNamespacesWithSubpages[NS_MAIN] = $saved; - } - // Some namespaces with subpages - $this->assertHasSubpages( NS_TALK ); - $this->assertHasSubpages( NS_USER ); + $this->assertHasSubpages( NS_TALK ); + $this->assertHasSubpages( NS_USER ); $this->assertHasSubpages( NS_USER_TALK ); } /** */ public function testGetContentNamespaces() { + global $wgContentNamespaces; + $this->assertEquals( array( NS_MAIN ), - MWNamespace::getcontentNamespaces(), + MWNamespace::getContentNamespaces(), '$wgContentNamespaces is an array with only NS_MAIN by default' ); - global $wgContentNamespaces; - - $saved = $wgContentNamespaces; # test !is_array( $wgcontentNamespaces ) $wgContentNamespaces = ''; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = false; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = null; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); + $wgContentNamespaces = 5; - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); # test $wgContentNamespaces === array() $wgContentNamespaces = array(); - $this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() ); + $this->assertEquals( array( NS_MAIN ), MWNamespace::getContentNamespaces() ); # test !in_array( NS_MAIN, $wgContentNamespaces ) $wgContentNamespaces = array( NS_USER, NS_CATEGORY ); $this->assertEquals( array( NS_MAIN, NS_USER, NS_CATEGORY ), - MWNamespace::getcontentNamespaces(), + MWNamespace::getContentNamespaces(), 'NS_MAIN is forced in $wgContentNamespaces even if unwanted' ); @@ -425,23 +377,21 @@ class MWNamespaceTest extends MediaWikiTestCase { $wgContentNamespaces = array( NS_MAIN ); $this->assertEquals( array( NS_MAIN ), - MWNamespace::getcontentNamespaces() + MWNamespace::getContentNamespaces() ); $wgContentNamespaces = array( NS_MAIN, NS_USER, NS_CATEGORY ); $this->assertEquals( array( NS_MAIN, NS_USER, NS_CATEGORY ), - MWNamespace::getcontentNamespaces() + MWNamespace::getContentNamespaces() ); - - $wgContentNamespaces = $saved; } /** */ public function testGetSubjectNamespaces() { $subjectsNS = MWNamespace::getSubjectNamespaces(); - $this->assertContains( NS_MAIN, $subjectsNS, + $this->assertContains( NS_MAIN, $subjectsNS, "Talk namespaces should have NS_MAIN" ); $this->assertNotContains( NS_TALK, $subjectsNS, "Talk namespaces should have NS_TALK" ); @@ -456,7 +406,7 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testGetTalkNamespaces() { $talkNS = MWNamespace::getTalkNamespaces(); - $this->assertContains( NS_TALK, $talkNS, + $this->assertContains( NS_TALK, $talkNS, "Subject namespaces should have NS_TALK" ); $this->assertNotContains( NS_MAIN, $talkNS, "Subject namespaces should not have NS_MAIN" ); @@ -475,18 +425,18 @@ class MWNamespaceTest extends MediaWikiTestCase { // NS_MEDIA and NS_FILE are treated the same $this->assertEquals( MWNamespace::isCapitalized( NS_MEDIA ), - MWNamespace::isCapitalized( NS_FILE ), + MWNamespace::isCapitalized( NS_FILE ), 'NS_MEDIA and NS_FILE have same capitalization rendering' ); // Boths are capitalized by default $this->assertIsCapitalized( NS_MEDIA ); - $this->assertIsCapitalized( NS_FILE ); + $this->assertIsCapitalized( NS_FILE ); // Always capitalized namespaces // @see MWNamespace::$alwaysCapitalizedNamespaces - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); } @@ -498,30 +448,26 @@ class MWNamespaceTest extends MediaWikiTestCase { * $wgCapitalLinkOverrides = array(); by default * $wgCapitalLinks = true; by default * This function test $wgCapitalLinks - * + * * Global setting correctness is tested against the NS_PROJECT and * NS_PROJECT_TALK namespaces since they are not hardcoded nor specials */ public function testIsCapitalizedWithWgCapitalLinks() { global $wgCapitalLinks; - // Save the global to easily reset to MediaWiki default settings - $savedGlobal = $wgCapitalLinks; - $wgCapitalLinks = true; - $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT ); $this->assertIsCapitalized( NS_PROJECT_TALK ); $wgCapitalLinks = false; + // hardcoded namespaces (see above function) are still capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); + // setting is correctly applied - $this->assertIsNotCapitalized( NS_PROJECT ); + $this->assertIsNotCapitalized( NS_PROJECT ); $this->assertIsNotCapitalized( NS_PROJECT_TALK ); - - // reset global state: - $wgCapitalLinks = $savedGlobal; } /** @@ -532,81 +478,78 @@ class MWNamespaceTest extends MediaWikiTestCase { */ public function testIsCapitalizedWithWgCapitalLinkOverrides() { global $wgCapitalLinkOverrides; - // Save the global to easily reset to MediaWiki default settings - $savedGlobal = $wgCapitalLinkOverrides; // Test default settings - $this->assertIsCapitalized( NS_PROJECT ); + $this->assertIsCapitalized( NS_PROJECT ); $this->assertIsCapitalized( NS_PROJECT_TALK ); + // hardcoded namespaces (see above function) are capitalized: - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); // Hardcoded namespaces remains capitalized - $wgCapitalLinkOverrides[NS_SPECIAL] = false; - $wgCapitalLinkOverrides[NS_USER] = false; + $wgCapitalLinkOverrides[NS_SPECIAL] = false; + $wgCapitalLinkOverrides[NS_USER] = false; $wgCapitalLinkOverrides[NS_MEDIAWIKI] = false; - $this->assertIsCapitalized( NS_SPECIAL ); - $this->assertIsCapitalized( NS_USER ); + + $this->assertIsCapitalized( NS_SPECIAL ); + $this->assertIsCapitalized( NS_USER ); $this->assertIsCapitalized( NS_MEDIAWIKI ); - $wgCapitalLinkOverrides = $savedGlobal; $wgCapitalLinkOverrides[NS_PROJECT] = false; $this->assertIsNotCapitalized( NS_PROJECT ); - $wgCapitalLinkOverrides[NS_PROJECT] = true ; - $this->assertIsCapitalized( NS_PROJECT ); - unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + + $wgCapitalLinkOverrides[NS_PROJECT] = true; $this->assertIsCapitalized( NS_PROJECT ); - // reset global state: - $wgCapitalLinkOverrides = $savedGlobal; + unset( $wgCapitalLinkOverrides[NS_PROJECT] ); + $this->assertIsCapitalized( NS_PROJECT ); } public function testHasGenderDistinction() { // Namespaces with gender distinctions - $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER ) ); + $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER ) ); $this->assertTrue( MWNamespace::hasGenderDistinction( NS_USER_TALK ) ); // Other ones, "genderless" - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MEDIA ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MEDIA ) ); $this->assertFalse( MWNamespace::hasGenderDistinction( NS_SPECIAL ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); - $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); - + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_MAIN ) ); + $this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK ) ); } public function testIsNonincludable() { global $wgNonincludableNamespaces; + $wgNonincludableNamespaces = array( NS_USER ); $this->assertTrue( MWNamespace::isNonincludable( NS_USER ) ); - $this->assertFalse( MWNamespace::isNonincludable( NS_TEMPLATE ) ); } ####### HELPERS ########################################################### function __call( $method, $args ) { // Call the real method if it exists - if( method_exists($this, $method ) ) { + if ( method_exists( $this, $method ) ) { return $this->$method( $args ); } - if( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) { + if ( preg_match( '/^assert(Has|Is|Can)(Not|)(Subject|Talk|Watchable|Content|Subpages|Capitalized)$/', $method, $m ) ) { # Interprets arguments: - $ns = $args[0]; - $msg = isset($args[1]) ? $args[1] : " dummy message"; + $ns = $args[0]; + $msg = isset( $args[1] ) ? $args[1] : " dummy message"; # Forge the namespace constant name: - if( $ns === 0 ) { + if ( $ns === 0 ) { $ns_name = "NS_MAIN"; } else { - $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) ); + $ns_name = "NS_" . strtoupper( MWNamespace::getCanonicalName( $ns ) ); } # ... and the MWNamespace method name $nsMethod = strtolower( $m[1] ) . $m[3]; - $expect = ($m[2] === ''); + $expect = ( $m[2] === '' ); $expect_name = $expect ? 'TRUE' : 'FALSE'; return $this->assertEquals( $expect, @@ -621,8 +564,8 @@ class MWNamespaceTest extends MediaWikiTestCase { function assertSameSubject( $ns1, $ns2, $msg = '' ) { $this->assertTrue( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); } + function assertDifferentSubject( $ns1, $ns2, $msg = '' ) { $this->assertFalse( MWNamespace::subjectEquals( $ns1, $ns2, $msg ) ); } } - diff --git a/tests/phpunit/includes/MessageTest.php b/tests/phpunit/includes/MessageTest.php index 20181fd4..1e18f975 100644 --- a/tests/phpunit/includes/MessageTest.php +++ b/tests/phpunit/includes/MessageTest.php @@ -1,8 +1,16 @@ <?php class MessageTest extends MediaWikiLangTestCase { + protected function setUp() { + parent::setUp(); - function testExists() { + $this->setMwGlobals( array( + 'wgLang' => Language::factory( 'en' ), + 'wgForceUIMsgAsContentMsg' => array(), + ) ); + } + + public function testExists() { $this->assertTrue( wfMessage( 'mainpage' )->exists() ); $this->assertTrue( wfMessage( 'mainpage' )->params( array() )->exists() ); $this->assertTrue( wfMessage( 'mainpage' )->rawParams( 'foo', 123 )->exists() ); @@ -11,7 +19,7 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertFalse( wfMessage( 'i-dont-exist-evar' )->rawParams( 'foo', 123 )->exists() ); } - function testKey() { + public function testKey() { $this->assertInstanceOf( 'Message', wfMessage( 'mainpage' ) ); $this->assertInstanceOf( 'Message', wfMessage( 'i-dont-exist-evar' ) ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->text() ); @@ -20,45 +28,103 @@ class MessageTest extends MediaWikiLangTestCase { $this->assertEquals( '<i-dont-exist-evar>', wfMessage( 'i-dont-exist-evar' )->escaped() ); } - function testInLanguage() { + public function testInLanguage() { $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inLanguage( 'en' )->text() ); $this->assertEquals( 'Заглавная страница', wfMessage( 'mainpage' )->inLanguage( 'ru' )->text() ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inLanguage( Language::factory( 'en' ) )->text() ); $this->assertEquals( 'Заглавная страница', wfMessage( 'mainpage' )->inLanguage( Language::factory( 'ru' ) )->text() ); } - function testMessageParams() { + public function testMessageParams() { $this->assertEquals( 'Return to $1.', wfMessage( 'returnto' )->text() ); $this->assertEquals( 'Return to $1.', wfMessage( 'returnto', array() )->text() ); $this->assertEquals( 'You have foo (bar).', wfMessage( 'youhavenewmessages', 'foo', 'bar' )->text() ); $this->assertEquals( 'You have foo (bar).', wfMessage( 'youhavenewmessages', array( 'foo', 'bar' ) )->text() ); } - function testMessageParamSubstitution() { + public function testMessageParamSubstitution() { $this->assertEquals( '(Заглавная страница)', wfMessage( 'parentheses', 'Заглавная страница' )->plain() ); $this->assertEquals( '(Заглавная страница $1)', wfMessage( 'parentheses', 'Заглавная страница $1' )->plain() ); $this->assertEquals( '(Заглавная страница)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница' )->plain() ); $this->assertEquals( '(Заглавная страница $1)', wfMessage( 'parentheses' )->rawParams( 'Заглавная страница $1' )->plain() ); } - function testInContentLanguage() { - global $wgLang, $wgForceUIMsgAsContentMsg; - $oldLang = $wgLang; - $wgLang = Language::factory( 'fr' ); + public function testDeliciouslyManyParams() { + $msg = new RawMessage( '$1$2$3$4$5$6$7$8$9$10$11$12' ); + // One less than above has placeholders + $params = array( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k' ); + $this->assertEquals( 'abcdefghijka2', $msg->params( $params )->plain(), 'Params > 9 are replaced correctly' ); + } + + /** + * FIXME: This should not need database, but Language#formatExpiry does (bug 55912) + * @group Database + */ + public function testMessageParamTypes() { + $lang = Language::factory( 'en' ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatNum( 123456.789 ), + $msg->inLanguage( $lang )->numParams( 123456.789 )->plain(), + 'numParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatDuration( 1234 ), + $msg->inLanguage( $lang )->durationParams( 1234 )->plain(), + 'durationParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatExpiry( wfTimestampNow() ), + $msg->inLanguage( $lang )->expiryParams( wfTimestampNow() )->plain(), + 'expiryParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatTimePeriod( 1234 ), + $msg->inLanguage( $lang )->timeperiodParams( 1234 )->plain(), + 'timeperiodParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatSize( 123456 ), + $msg->inLanguage( $lang )->sizeParams( 123456 )->plain(), + 'sizeParams is handled correctly' + ); + + $msg = new RawMessage( '$1' ); + $this->assertEquals( + $lang->formatBitrate( 123456 ), + $msg->inLanguage( $lang )->bitrateParams( 123456 )->plain(), + 'bitrateParams is handled correctly' + ); + } + + public function testInContentLanguageDisabled() { + $this->setMwGlobals( 'wgLang', Language::factory( 'fr' ) ); $this->assertEquals( 'Main Page', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg disabled' ); - $wgForceUIMsgAsContentMsg['testInContentLanguage'] = 'mainpage'; - $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' ); + } - /* Restore globals */ - $wgLang = $oldLang; - unset( $wgForceUIMsgAsContentMsg['testInContentLanguage'] ); + public function testInContentLanguageEnabled() { + $this->setMwGlobals( array( + 'wgLang' => Language::factory( 'fr' ), + 'wgForceUIMsgAsContentMsg' => array( 'mainpage' ), + ) ); + + $this->assertEquals( 'Accueil', wfMessage( 'mainpage' )->inContentLanguage()->plain(), 'ForceUIMsg enabled' ); } /** * @expectedException MWException */ - function testInLanguageThrows() { + public function testInLanguageThrows() { wfMessage( 'foo' )->inLanguage( 123 ); } } diff --git a/tests/phpunit/includes/OutputPageTest.php b/tests/phpunit/includes/OutputPageTest.php new file mode 100644 index 00000000..56bb0fce --- /dev/null +++ b/tests/phpunit/includes/OutputPageTest.php @@ -0,0 +1,133 @@ +<?php + +/** + * + * @author Matthew Flaschen + * + * @group Output + * + */ +class OutputPageTest extends MediaWikiTestCase { + const SCREEN_MEDIA_QUERY = 'screen and (min-width: 982px)'; + const SCREEN_ONLY_MEDIA_QUERY = 'only screen and (min-width: 982px)'; + + /** + * Tests a particular case of transformCssMedia, using the given input, globals, + * expected return, and message + * + * Asserts that $expectedReturn is returned. + * + * options['printableQuery'] - value of query string for printable, or omitted for none + * options['handheldQuery'] - value of query string for handheld, or omitted for none + * options['media'] - passed into the method under the same name + * options['expectedReturn'] - expected return value + * options['message'] - PHPUnit message for assertion + * + * @param array $args key-value array of arguments as shown above + */ + protected function assertTransformCssMediaCase( $args ) { + $queryData = array(); + if ( isset( $args['printableQuery'] ) ) { + $queryData['printable'] = $args['printableQuery']; + } + + if ( isset( $args['handheldQuery'] ) ) { + $queryData['handheld'] = $args['handheldQuery']; + } + + $fauxRequest = new FauxRequest( $queryData, false ); + $this->setMWGlobals( array( + 'wgRequest' => $fauxRequest, + ) ); + + $actualReturn = OutputPage::transformCssMedia( $args['media'] ); + $this->assertSame( $args['expectedReturn'], $actualReturn, $args['message'] ); + } + + /** + * Tests print requests + */ + public function testPrintRequests() { + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => 'screen', + 'expectedReturn' => null, + 'message' => 'On printable request, screen returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => self::SCREEN_MEDIA_QUERY, + 'expectedReturn' => null, + 'message' => 'On printable request, screen media query returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => self::SCREEN_ONLY_MEDIA_QUERY, + 'expectedReturn' => null, + 'message' => 'On printable request, screen media query with only returns null' + ) ); + + $this->assertTransformCssMediaCase( array( + 'printableQuery' => '1', + 'media' => 'print', + 'expectedReturn' => '', + 'message' => 'On printable request, media print returns empty string' + ) ); + } + + /** + * Tests screen requests, without either query parameter set + */ + public function testScreenRequests() { + $this->assertTransformCssMediaCase( array( + 'media' => 'screen', + 'expectedReturn' => 'screen', + 'message' => 'On screen request, screen media type is preserved' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => 'handheld', + 'expectedReturn' => 'handheld', + 'message' => 'On screen request, handheld media type is preserved' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => self::SCREEN_MEDIA_QUERY, + 'expectedReturn' => self::SCREEN_MEDIA_QUERY, + 'message' => 'On screen request, screen media query is preserved.' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => self::SCREEN_ONLY_MEDIA_QUERY, + 'expectedReturn' => self::SCREEN_ONLY_MEDIA_QUERY, + 'message' => 'On screen request, screen media query with only is preserved.' + ) ); + + $this->assertTransformCssMediaCase( array( + 'media' => 'print', + 'expectedReturn' => 'print', + 'message' => 'On screen request, print media type is preserved' + ) ); + } + + /** + * Tests handheld behavior + */ + public function testHandheld() { + $this->assertTransformCssMediaCase( array( + 'handheldQuery' => '1', + 'media' => 'handheld', + 'expectedReturn' => '', + 'message' => 'On request with handheld querystring and media is handheld, returns empty string' + ) ); + + $this->assertTransformCssMediaCase( array( + 'handheldQuery' => '1', + 'media' => 'screen', + 'expectedReturn' => null, + 'message' => 'On request with handheld querystring and media is screen, returns null' + ) ); + } +} diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php deleted file mode 100644 index 59c955fe..00000000 --- a/tests/phpunit/includes/ParserOptionsTest.php +++ /dev/null @@ -1,35 +0,0 @@ -<?php - -class ParserOptionsTest extends MediaWikiTestCase { - - private $popts; - private $pcache; - - function setUp() { - global $wgContLang, $wgUser, $wgLanguageCode; - $wgContLang = Language::factory( $wgLanguageCode ); - $this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang ); - $this->pcache = ParserCache::singleton(); - } - - function tearDown() { - parent::tearDown(); - } - - /** - * ParserOptions::optionsHash was not giving consistent results when $wgUseDynamicDates was set - * @group Database - */ - function testGetParserCacheKeyWithDynamicDates() { - global $wgUseDynamicDates; - $wgUseDynamicDates = true; - - $title = Title::newFromText( "Some test article" ); - $page = WikiPage::factory( $title ); - - $pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts ); - $this->assertNotNull( $this->popts->getDateFormat() ); - $pcacheKeyAfter = $this->pcache->getKey( $page, $this->popts ); - $this->assertEquals( $pcacheKeyBefore, $pcacheKeyAfter ); - } -} diff --git a/tests/phpunit/includes/PathRouterTest.php b/tests/phpunit/includes/PathRouterTest.php index f6274584..adfb215a 100644 --- a/tests/phpunit/includes/PathRouterTest.php +++ b/tests/phpunit/includes/PathRouterTest.php @@ -1,13 +1,21 @@ <?php /** - * Tests for the PathRouter parsing + * Tests for the PathRouter parsing. + * + * @todo Add covers tags. */ class PathRouterTest extends MediaWikiTestCase { - public function setUp() { + /** + * @var PathRouter + */ + protected $basicRouter; + + protected function setUp() { + parent::setUp(); $router = new PathRouter; - $router->add("/wiki/$1"); + $router->add( "/wiki/$1" ); $this->basicRouter = $router; } @@ -24,17 +32,17 @@ class PathRouterTest extends MediaWikiTestCase { */ public function testLoose() { $router = new PathRouter; - $router->add("/"); # Should be the same as "/$1" + $router->add( "/" ); # Should be the same as "/$1" $matches = $router->parse( "/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/wiki"); # Should be the same as /wiki/$1 + $router->add( "/wiki" ); # Should be the same as /wiki/$1 $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/wiki/"); # Should be the same as /wiki/$1 + $router->add( "/wiki/" ); # Should be the same as /wiki/$1 $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); } @@ -44,16 +52,16 @@ class PathRouterTest extends MediaWikiTestCase { */ public function testOrder() { $router = new PathRouter; - $router->add("/$1"); - $router->add("/a/$1"); - $router->add("/b/$1"); + $router->add( "/$1" ); + $router->add( "/a/$1" ); + $router->add( "/b/$1" ); $matches = $router->parse( "/a/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); $router = new PathRouter; - $router->add("/b/$1"); - $router->add("/a/$1"); - $router->add("/$1"); + $router->add( "/b/$1" ); + $router->add( "/a/$1" ); + $router->add( "/$1" ); $matches = $router->parse( "/a/Foo" ); $this->assertEquals( $matches, array( 'title' => "Foo" ) ); } @@ -150,18 +158,20 @@ class PathRouterTest extends MediaWikiTestCase { $router->add( array( 'qwerty' => "/qwerty/$1" ), array( 'qwerty' => '$key' ) ); $router->add( "/$2/$1", array( 'restricted-to-y' => '$2' ), array( '$2' => 'y' ) ); - foreach( array( - "/Foo" => array( 'title' => "Foo" ), - "/Bar" => array( 'ping' => 'pong' ), - "/Baz" => array( 'marco' => 'polo' ), - "/asdf-foo" => array( 'title' => "qwerty-foo" ), - "/qwerty-bar" => array( 'title' => "asdf-bar" ), - "/a/Foo" => array( 'title' => "Foo" ), - "/asdf/Foo" => array( 'title' => "Foo" ), - "/qwerty/Foo" => array( 'title' => "Foo", 'qwerty' => 'qwerty' ), - "/baz/Foo" => array( 'title' => "Foo", 'unrestricted' => 'baz' ), - "/y/Foo" => array( 'title' => "Foo", 'restricted-to-y' => 'y' ), - ) as $path => $result ) { + foreach ( + array( + '/Foo' => array( 'title' => 'Foo' ), + '/Bar' => array( 'ping' => 'pong' ), + '/Baz' => array( 'marco' => 'polo' ), + '/asdf-foo' => array( 'title' => 'qwerty-foo' ), + '/qwerty-bar' => array( 'title' => 'asdf-bar' ), + '/a/Foo' => array( 'title' => 'Foo' ), + '/asdf/Foo' => array( 'title' => 'Foo' ), + '/qwerty/Foo' => array( 'title' => 'Foo', 'qwerty' => 'qwerty' ), + '/baz/Foo' => array( 'title' => 'Foo', 'unrestricted' => 'baz' ), + '/y/Foo' => array( 'title' => 'Foo', 'restricted-to-y' => 'y' ), + ) as $path => $result + ) { $this->assertEquals( $router->parse( $path ), $result ); } } @@ -182,7 +192,7 @@ class PathRouterTest extends MediaWikiTestCase { $this->assertEquals( $matches, array( 'title' => "Title_With Space" ) ); } - public function dataRegexpChars() { + public static function provideRegexpChars() { return array( array( "$" ), array( "$1" ), @@ -193,7 +203,7 @@ class PathRouterTest extends MediaWikiTestCase { /** * Make sure the router doesn't break on special characters like $ used in regexp replacements - * @dataProvider dataRegexpChars + * @dataProvider provideRegexpChars */ public function testRegexpChars( $char ) { $matches = $this->basicRouter->parse( "/wiki/$char" ); @@ -250,5 +260,4 @@ class PathRouterTest extends MediaWikiTestCase { $matches = $router->parse( "/wiki/Foo" ); $this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) ); } - } diff --git a/tests/phpunit/includes/PreferencesTest.php b/tests/phpunit/includes/PreferencesTest.php index 0e123177..3dec2da0 100644 --- a/tests/phpunit/includes/PreferencesTest.php +++ b/tests/phpunit/includes/PreferencesTest.php @@ -1,13 +1,20 @@ <?php +/** + * @group Database + */ class PreferencesTest extends MediaWikiTestCase { - /** Array of User objects */ + /** + * @var User[] + */ private $prefUsers; + /** + * @var RequestContext + */ private $context; - function __construct() { + public function __construct() { parent::__construct(); - global $wgEnableEmail; $this->prefUsers['noemail'] = new User; @@ -15,46 +22,54 @@ class PreferencesTest extends MediaWikiTestCase { $this->prefUsers['notauth'] ->setEmail( 'noauth@example.org' ); - $this->prefUsers['auth'] = new User; + $this->prefUsers['auth'] = new User; $this->prefUsers['auth'] ->setEmail( 'noauth@example.org' ); $this->prefUsers['auth'] ->setEmailAuthenticationTimestamp( 1330946623 ); $this->context = new RequestContext; - $this->context->setTitle( Title::newFromText('PreferencesTest') ); + $this->context->setTitle( Title::newFromText( 'PreferencesTest' ) ); + } - //some tests depends on email setting - $wgEnableEmail = true; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgEnableEmail' => true, + 'wgEmailAuthentication' => true, + ) ); } /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserHasNoEmail() { + public function testEmailFieldsWhenUserHasNoEmail() { $prefs = $this->prefsFor( 'noemail' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] ); $this->assertEquals( 'mw-email-none', $prefs['emailaddress']['cssclass'] ); } + /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserEmailNotAuthenticated() { + public function testEmailFieldsWhenUserEmailNotAuthenticated() { $prefs = $this->prefsFor( 'notauth' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] ); $this->assertEquals( 'mw-email-not-authenticated', $prefs['emailaddress']['cssclass'] ); } + /** * Placeholder to verify bug 34302 * @covers Preferences::profilePreferences */ - function testEmailFieldsWhenUserEmailIsAuthenticated() { + public function testEmailFieldsWhenUserEmailIsAuthenticated() { $prefs = $this->prefsFor( 'auth' ); $this->assertArrayHasKey( 'cssclass', $prefs['emailaddress'] @@ -63,13 +78,14 @@ class PreferencesTest extends MediaWikiTestCase { } /** Helper */ - function prefsFor( $user_key ) { + protected function prefsFor( $user_key ) { $preferences = array(); Preferences::profilePreferences( $this->prefUsers[$user_key] , $this->context , $preferences ); + return $preferences; } } diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php deleted file mode 100644 index f451f8a0..00000000 --- a/tests/phpunit/includes/Providers.php +++ /dev/null @@ -1,44 +0,0 @@ -<?php -/** - * Generic providers for the MediaWiki PHPUnit test suite - * - * @author Antoine Musso - * @copyright Copyright © 2011, Antoine Musso - * @file - */ - -/** */ -class MediaWikiProvide { - - /* provide an array of numbers from 1 up to @param $num */ - private static function createProviderUpTo( $num ) { - $ret = array(); - for( $i=1; $i<=$num;$i++ ) { - $ret[] = array( $i ); - } - return $ret; - } - - /* array of months numbers (as an integer) */ - public static function Months() { - return self::createProviderUpTo( 12 ); - } - - /* array of days numbers (as an integer) */ - public static function Days() { - return self::createProviderUpTo( 31 ); - } - - public static function DaysMonths() { - $ret = array(); - - $months = self::Months(); - $days = self::Days(); - foreach( $months as $month) { - foreach( $days as $day ) { - $ret[] = array( $day[0], $month[0] ); - } - } - return $ret; - } -} diff --git a/tests/phpunit/includes/RecentChangeTest.php b/tests/phpunit/includes/RecentChangeTest.php index fbf271cc..cfa3e777 100644 --- a/tests/phpunit/includes/RecentChangeTest.php +++ b/tests/phpunit/includes/RecentChangeTest.php @@ -9,12 +9,12 @@ class RecentChangeTest extends MediaWikiTestCase { protected $user_comment; protected $context; - function __construct() { + public function __construct() { parent::__construct(); - $this->title = Title::newFromText( 'SomeTitle' ); + $this->title = Title::newFromText( 'SomeTitle' ); $this->target = Title::newFromText( 'TestTarget' ); - $this->user = User::newFromName( 'UserName' ); + $this->user = User::newFromName( 'UserName' ); $this->user_comment = '<User comment about action>'; $this->context = RequestContext::newExtraneousContext( $this->title ); @@ -56,17 +56,19 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeBlock() { + public function testIrcMsgForLogTypeBlock() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # block/block $this->assertIRCComment( - $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'blocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'block', 'block', array(), $this->user_comment ); # block/unblock $this->assertIRCComment( - $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'unblocklogentry', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'block', 'unblock', array(), $this->user_comment @@ -76,10 +78,12 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeDelete() { + public function testIrcMsgForLogTypeDelete() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # delete/delete $this->assertIRCComment( - $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'deletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'delete', 'delete', array(), $this->user_comment @@ -87,7 +91,7 @@ class RecentChangeTest extends MediaWikiTestCase { # delete/restore $this->assertIRCComment( - $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'undeletedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'delete', 'restore', array(), $this->user_comment @@ -97,7 +101,7 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeNewusers() { + public function testIrcMsgForLogTypeNewusers() { $this->assertIRCComment( 'New user account', 'newusers', 'newusers', @@ -123,15 +127,16 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeMove() { + public function testIrcMsgForLogTypeMove() { $move_params = array( - '4::target' => $this->target->getPrefixedText(), + '4::target' => $this->target->getPrefixedText(), '5::noredir' => 0, ); + $sep = $this->context->msg( 'colon-separator' )->text(); # move/move $this->assertIRCComment( - $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + $this->context->msg( '1movedto2', 'SomeTitle', 'TestTarget' )->plain() . $sep . $this->user_comment, 'move', 'move', $move_params, $this->user_comment @@ -139,7 +144,7 @@ class RecentChangeTest extends MediaWikiTestCase { # move/move_redir $this->assertIRCComment( - $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' )->plain() . ': ' . $this->user_comment, + $this->context->msg( '1movedto2_redir', 'SomeTitle', 'TestTarget' )->plain() . $sep . $this->user_comment, 'move', 'move_redir', $move_params, $this->user_comment @@ -149,15 +154,15 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypePatrol() { + public function testIrcMsgForLogTypePatrol() { # patrol/patrol $this->assertIRCComment( $this->context->msg( 'patrol-log-line', 'revision 777', '[[SomeTitle]]', '' )->plain(), 'patrol', 'patrol', array( - '4::curid' => '777', + '4::curid' => '777', '5::previd' => '666', - '6::auto' => 0, + '6::auto' => 0, ) ); } @@ -165,14 +170,15 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeProtect() { + public function testIrcMsgForLogTypeProtect() { $protectParams = array( '[edit=sysop] (indefinite) [move=sysop] (indefinite)' ); + $sep = $this->context->msg( 'colon-separator' )->text(); # protect/protect $this->assertIRCComment( - $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'protectedarticle', 'SomeTitle ' . $protectParams[0] )->plain() . $sep . $this->user_comment, 'protect', 'protect', $protectParams, $this->user_comment @@ -180,7 +186,7 @@ class RecentChangeTest extends MediaWikiTestCase { # protect/unprotect $this->assertIRCComment( - $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'unprotectedarticle', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'protect', 'unprotect', array(), $this->user_comment @@ -188,7 +194,7 @@ class RecentChangeTest extends MediaWikiTestCase { # protect/modify $this->assertIRCComment( - $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'modifiedarticleprotection', 'SomeTitle ' . $protectParams[0] )->plain() . $sep . $this->user_comment, 'protect', 'modify', $protectParams, $this->user_comment @@ -198,10 +204,12 @@ class RecentChangeTest extends MediaWikiTestCase { /** * @covers LogFormatter::getIRCActionText */ - function testIrcMsgForLogTypeUpload() { + public function testIrcMsgForLogTypeUpload() { + $sep = $this->context->msg( 'colon-separator' )->text(); + # upload/upload $this->assertIRCComment( - $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'uploadedimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'upload', 'upload', array(), $this->user_comment @@ -209,7 +217,7 @@ class RecentChangeTest extends MediaWikiTestCase { # upload/overwrite $this->assertIRCComment( - $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . ': ' . $this->user_comment, + $this->context->msg( 'overwroteimage', 'SomeTitle' )->plain() . $sep . $this->user_comment, 'upload', 'overwrite', array(), $this->user_comment @@ -217,37 +225,37 @@ class RecentChangeTest extends MediaWikiTestCase { } /** - * @todo: Emulate these edits somehow and extract + * @todo Emulate these edits somehow and extract * raw edit summary from RecentChange object * -- - - function testIrcMsgForBlankingAES() { + */ + /* + public function testIrcMsgForBlankingAES() { // $this->context->msg( 'autosumm-blank', .. ); } - function testIrcMsgForReplaceAES() { + public function testIrcMsgForReplaceAES() { // $this->context->msg( 'autosumm-replace', .. ); } - function testIrcMsgForRollbackAES() { + public function testIrcMsgForRollbackAES() { // $this->context->msg( 'revertpage', .. ); } - function testIrcMsgForUndoAES() { + public function testIrcMsgForUndoAES() { // $this->context->msg( 'undo-summary', .. ); } - - * -- - */ + */ /** * @param $expected String Expected IRC text without colors codes * @param $type String Log type (move, delete, suppress, patrol ...) * @param $action String A log type action + * @param $params * @param $comment String (optional) A comment for the log action * @param $msg String (optional) A message for PHPUnit :-) */ - function assertIRCComment( $expected, $type, $action, $params, $comment = null, $msg = '' ) { + protected function assertIRCComment( $expected, $type, $action, $params, $comment = null, $msg = '' ) { $logEntry = new ManualLogEntry( $type, $action ); $logEntry->setPerformer( $this->user ); @@ -260,8 +268,8 @@ class RecentChangeTest extends MediaWikiTestCase { $formatter = LogFormatter::newFromEntry( $logEntry ); $formatter->setContext( $this->context ); - // Apply the same transformation as done in RecentChange::getIRCLine for rc_comment - $ircRcComment = RecentChange::cleanupForIRC( $formatter->getIRCActionComment() ); + // Apply the same transformation as done in IRCColourfulRCFeedFormatter::getLine for rc_comment + $ircRcComment = IRCColourfulRCFeedFormatter::cleanupForIRC( $formatter->getIRCActionComment() ); $this->assertEquals( $expected, @@ -269,5 +277,4 @@ class RecentChangeTest extends MediaWikiTestCase { $msg ); } - } diff --git a/tests/phpunit/includes/RequestContextTest.php b/tests/phpunit/includes/RequestContextTest.php new file mode 100644 index 00000000..1776b5d5 --- /dev/null +++ b/tests/phpunit/includes/RequestContextTest.php @@ -0,0 +1,73 @@ +<?php + +/** + * @group Database + */ +class RequestContextTest extends MediaWikiTestCase { + + /** + * Test the relationship between title and wikipage in RequestContext + * @covers RequestContext::getWikiPage + * @covers RequestContext::getTitle + */ + public function testWikiPageTitle() { + $context = new RequestContext(); + + $curTitle = Title::newFromText( "A" ); + $context->setTitle( $curTitle ); + $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is first set WikiPage should be created on-demand for that title." ); + + $curTitle = Title::newFromText( "B" ); + $context->setWikiPage( WikiPage::factory( $curTitle ) ); + $this->assertTrue( $curTitle->equals( $context->getTitle() ), + "Title must be updated when a new WikiPage is provided." ); + + $curTitle = Title::newFromText( "C" ); + $context->setTitle( $curTitle ); + $this->assertTrue( $curTitle->equals( $context->getWikiPage()->getTitle() ), + "When a title is updated the WikiPage should be purged and recreated on-demand with the new title." ); + } + + /** + * @covers RequestContext::importScopedSession + */ + public function testImportScopedSession() { + $context = RequestContext::getMain(); + + $oInfo = $context->exportSession(); + $this->assertEquals( '127.0.0.1', $oInfo['ip'], "Correct initial IP address." ); + $this->assertEquals( 0, $oInfo['userId'], "Correct initial user ID." ); + + $user = User::newFromName( 'UnitTestContextUser' ); + $user->addToDatabase(); + + $sinfo = array( + 'sessionId' => 'd612ee607c87e749ef14da4983a702cd', + 'userId' => $user->getId(), + 'ip' => '192.0.2.0', + 'headers' => array( 'USER-AGENT' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:18.0) Gecko/20100101 Firefox/18.0' ) + ); + $sc = RequestContext::importScopedSession( $sinfo ); // load new context + + $info = $context->exportSession(); + $this->assertEquals( $sinfo['ip'], $info['ip'], "Correct IP address." ); + $this->assertEquals( $sinfo['headers'], $info['headers'], "Correct headers." ); + $this->assertEquals( $sinfo['sessionId'], $info['sessionId'], "Correct session ID." ); + $this->assertEquals( $sinfo['userId'], $info['userId'], "Correct user ID." ); + $this->assertEquals( $sinfo['ip'], $context->getRequest()->getIP(), "Correct context IP address." ); + $this->assertEquals( $sinfo['headers'], $context->getRequest()->getAllHeaders(), "Correct context headers." ); + $this->assertEquals( $sinfo['sessionId'], session_id(), "Correct context session ID." ); + $this->assertEquals( true, $context->getUser()->isLoggedIn(), "Correct context user." ); + $this->assertEquals( $sinfo['userId'], $context->getUser()->getId(), "Correct context user ID." ); + $this->assertEquals( 'UnitTestContextUser', $context->getUser()->getName(), "Correct context user name." ); + + unset( $sc ); // restore previous context + + $info = $context->exportSession(); + $this->assertEquals( $oInfo['ip'], $info['ip'], "Correct initial IP address." ); + $this->assertEquals( $oInfo['headers'], $info['headers'], "Correct initial headers." ); + $this->assertEquals( $oInfo['sessionId'], $info['sessionId'], "Correct initial session ID." ); + $this->assertEquals( $oInfo['userId'], $info['userId'], "Correct initial user ID." ); + } +} diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php index ab704839..ca8b2b6e 100644 --- a/tests/phpunit/includes/ResourceLoaderTest.php +++ b/tests/phpunit/includes/ResourceLoaderTest.php @@ -4,6 +4,32 @@ class ResourceLoaderTest extends MediaWikiTestCase { protected static $resourceLoaderRegisterModulesHook; + protected function setUp() { + parent::setUp(); + + // $wgResourceLoaderLESSFunctions, $wgResourceLoaderLESSImportPaths; $wgResourceLoaderLESSVars; + + $this->setMwGlobals( array( + 'wgResourceLoaderLESSFunctions' => array( + 'test-sum' => function ( $frame, $less ) { + $sum = 0; + foreach ( $frame[2] as $arg ) { + $sum += (int)$arg[1]; + } + return $sum; + }, + ), + 'wgResourceLoaderLESSImportPaths' => array( + dirname( __DIR__ ) . '/data/less/common', + ), + 'wgResourceLoaderLESSVars' => array( + 'foo' => '2px', + 'Foo' => '#eeeeee', + 'bar' => 5, + ), + ) ); + } + /* Hook Methods */ /** @@ -11,16 +37,25 @@ class ResourceLoaderTest extends MediaWikiTestCase { */ public static function resourceLoaderRegisterModules( &$resourceLoader ) { self::$resourceLoaderRegisterModulesHook = true; + return true; } /* Provider Methods */ - public function provideValidModules() { + public static function provideValidModules() { return array( array( 'TEST.validModule1', new ResourceLoaderTestModule() ), ); } + public static function provideResourceLoaderContext() { + $resourceLoader = new ResourceLoader(); + $request = new FauxRequest(); + return array( + array( new ResourceLoaderContext( $resourceLoader, $request ) ), + ); + } + /* Test Methods */ /** @@ -31,6 +66,7 @@ class ResourceLoaderTest extends MediaWikiTestCase { self::$resourceLoaderRegisterModulesHook = false; $resourceLoader = new ResourceLoader(); $this->assertTrue( self::$resourceLoaderRegisterModulesHook ); + return $resourceLoader; } @@ -48,7 +84,22 @@ class ResourceLoaderTest extends MediaWikiTestCase { } /** + * @dataProvider provideResourceLoaderContext + * @covers ResourceLoaderFileModule::compileLessFile + */ + public function testLessFileCompilation( $context ) { + $basePath = __DIR__ . '/../data/less/module'; + $module = new ResourceLoaderFileModule( array( + 'localBasePath' => $basePath, + 'styles' => array( 'styles.less' ), + ) ); + $styles = $module->getStyles( $context ); + $this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] ); + } + + /** * @dataProvider providePackedModules + * @covers ResourceLoader::makePackedModulesString */ public function testMakePackedModulesString( $desc, $modules, $packed ) { $this->assertEquals( $packed, ResourceLoader::makePackedModulesString( $modules ), $desc ); @@ -56,12 +107,13 @@ class ResourceLoaderTest extends MediaWikiTestCase { /** * @dataProvider providePackedModules + * @covers ResourceLoaderContext::expandModuleNames */ public function testexpandModuleNames( $desc, $modules, $packed ) { $this->assertEquals( $modules, ResourceLoaderContext::expandModuleNames( $packed ), $desc ); } - public function providePackedModules() { + public static function providePackedModules() { return array( array( 'Example from makePackedModulesString doc comment', @@ -77,14 +129,20 @@ class ResourceLoaderTest extends MediaWikiTestCase { 'Regression fixed in r88706 with dotless names', array( 'foo', 'bar', 'baz' ), 'foo,bar,baz', - ) + ), + array( + 'Prefixless modules after a prefixed module', + array( 'single.module', 'foobar', 'foobaz' ), + 'single.module|foobar,foobaz', + ), ); } } /* Stubs */ -class ResourceLoaderTestModule extends ResourceLoaderModule { } +class ResourceLoaderTestModule extends ResourceLoaderModule { +} /* Hooks */ global $wgHooks; diff --git a/tests/phpunit/includes/RevisionStorageTest.php b/tests/phpunit/includes/RevisionStorageTest.php index 8a7facec..e17c7b0f 100644 --- a/tests/phpunit/includes/RevisionStorageTest.php +++ b/tests/phpunit/includes/RevisionStorageTest.php @@ -3,6 +3,7 @@ /** * Test class for Revision storage. * + * @group ContentHandler * @group Database * ^--- important, causes temporary tables to be used instead of the real database * @@ -11,41 +12,81 @@ */ class RevisionStorageTest extends MediaWikiTestCase { + /** + * @var WikiPage $the_page + */ var $the_page; - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); $this->tablesUsed = array_merge( $this->tablesUsed, - array( 'page', - 'revision', - 'text', - - 'recentchanges', - 'logging', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); } - public function setUp() { + protected function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::setUp(); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = 'DUMMY'; + $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache if ( !$this->the_page ) { - $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page" ); + $this->the_page = $this->createPage( 'RevisionStorageTest_the_page', "just a dummy page", CONTENT_MODEL_WIKITEXT ); } } + public function tearDown() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + parent::tearDown(); + + unset( $wgExtraNamespaces[12312] ); + unset( $wgExtraNamespaces[12313] ); + + unset( $wgNamespaceContentModels[12312] ); + unset( $wgContentHandlers['DUMMY'] ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + protected function makeRevision( $props = null ) { - if ( $props === null ) $props = array(); + if ( $props === null ) { + $props = array(); + } + + if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) { + $props['text'] = 'Lorem Ipsum'; + } - if ( !isset( $props['content'] ) && !isset( $props['text'] ) ) $props['text'] = 'Lorem Ipsum'; - if ( !isset( $props['comment'] ) ) $props['comment'] = 'just a test'; - if ( !isset( $props['page'] ) ) $props['page'] = $this->the_page->getId(); + if ( !isset( $props['comment'] ) ) { + $props['comment'] = 'just a test'; + } + + if ( !isset( $props['page'] ) ) { + $props['page'] = $this->the_page->getId(); + } $rev = new Revision( $props ); @@ -56,14 +97,27 @@ class RevisionStorageTest extends MediaWikiTestCase { } protected function createPage( $page, $text, $model = null ) { - if ( is_string( $page ) ) $page = Title::newFromText( $page ); - if ( $page instanceof Title ) $page = new WikiPage( $page ); + if ( is_string( $page ) ) { + if ( !preg_match( '/:/', $page ) && + ( $model === null || $model === CONTENT_MODEL_WIKITEXT ) + ) { + $ns = $this->getDefaultWikitextNS(); + $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page; + } + + $page = Title::newFromText( $page ); + } + + if ( $page instanceof Title ) { + $page = new WikiPage( $page ); + } if ( $page->exists() ) { $page->doDeleteArticle( "done" ); } - $page->doEdit( $text, "testing", EDIT_NEW ); + $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); + $page->doEditContent( $content, "testing", EDIT_NEW ); return $page; } @@ -75,14 +129,15 @@ class RevisionStorageTest extends MediaWikiTestCase { $this->assertEquals( $orig->getPage(), $rev->getPage() ); $this->assertEquals( $orig->getTimestamp(), $rev->getTimestamp() ); $this->assertEquals( $orig->getUser(), $rev->getUser() ); + $this->assertEquals( $orig->getContentModel(), $rev->getContentModel() ); + $this->assertEquals( $orig->getContentFormat(), $rev->getContentFormat() ); $this->assertEquals( $orig->getSha1(), $rev->getSha1() ); } /** * @covers Revision::__construct */ - public function testConstructFromRow() - { + public function testConstructFromRow() { $orig = $this->makeRevision(); $dbr = wfgetDB( DB_SLAVE ); @@ -100,8 +155,7 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromRow */ - public function testNewFromRow() - { + public function testNewFromRow() { $orig = $this->makeRevision(); $dbr = wfgetDB( DB_SLAVE ); @@ -120,9 +174,8 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromArchiveRow */ - public function testNewFromArchiveRow() - { - $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum' ); + public function testNewFromArchiveRow() { + $page = $this->createPage( 'RevisionStorageTest_testNewFromArchiveRow', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT ); $orig = $page->getRevision(); $page->doDeleteArticle( 'test Revision::newFromArchiveRow' ); @@ -141,8 +194,7 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newFromId */ - public function testNewFromId() - { + public function testNewFromId() { $orig = $this->makeRevision(); $rev = Revision::newFromId( $orig->getId() ); @@ -153,12 +205,11 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::fetchRevision */ - public function testFetchRevision() - { - $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one' ); + public function testFetchRevision() { + $page = $this->createPage( 'RevisionStorageTest_testFetchRevision', 'one', CONTENT_MODEL_WIKITEXT ); $id1 = $page->getRevision()->getId(); - $page->doEdit( 'two', 'second rev' ); + $page->doEditContent( new WikitextContent( 'two' ), 'second rev' ); $id2 = $page->getRevision()->getId(); $res = Revision::fetchRevision( $page->getTitle() ); @@ -166,32 +217,39 @@ class RevisionStorageTest extends MediaWikiTestCase { #note: order is unspecified $rows = array(); while ( ( $row = $res->fetchObject() ) ) { - $rows[ $row->rev_id ]= $row; + $rows[$row->rev_id] = $row; } $row = $res->fetchObject(); - $this->assertEquals( 1, count($rows), 'expected exactly one revision' ); + $this->assertEquals( 1, count( $rows ), 'expected exactly one revision' ); $this->assertArrayHasKey( $id2, $rows, 'missing revision with id ' . $id2 ); } /** * @covers Revision::selectFields */ - public function testSelectFields() - { + public function testSelectFields() { + global $wgContentHandlerUseDB; + $fields = Revision::selectFields(); - $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields'); - $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields'); - $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields'); - $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields'); + $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' ); + $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' ); + $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields' ); + $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' ); + + if ( $wgContentHandlerUseDB ) { + $this->assertTrue( in_array( 'rev_content_model', $fields ), + 'missing rev_content_model in list of fields' ); + $this->assertTrue( in_array( 'rev_content_format', $fields ), + 'missing rev_content_format in list of fields' ); + } } /** * @covers Revision::getPage */ - public function testGetPage() - { + public function testGetPage() { $page = $this->the_page; $orig = $this->makeRevision( array( 'page' => $page->getId() ) ); @@ -203,8 +261,9 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getText */ - public function testGetText() - { + public function testGetText() { + $this->hideDeprecated( 'Revision::getText' ); + $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) ); $rev = Revision::newFromId( $orig->getId() ); @@ -212,10 +271,37 @@ class RevisionStorageTest extends MediaWikiTestCase { } /** + * @covers Revision::getContent + */ + public function testGetContent_failure() { + $rev = new Revision( array( + 'page' => $this->the_page->getId(), + 'content_model' => $this->the_page->getContentModel(), + 'text_id' => 123456789, // not in the test DB + ) ); + + $this->assertNull( $rev->getContent(), + "getContent() should return null if the revision's text blob could not be loaded." ); + + //NOTE: check this twice, once for lazy initialization, and once with the cached value. + $this->assertNull( $rev->getContent(), + "getContent() should return null if the revision's text blob could not be loaded." ); + } + + /** + * @covers Revision::getContent + */ + public function testGetContent() { + $orig = $this->makeRevision( array( 'text' => 'hello hello.' ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( 'hello hello.', $rev->getContent()->getNativeData() ); + } + + /** * @covers Revision::revText */ - public function testRevText() - { + public function testRevText() { $this->hideDeprecated( 'Revision::revText' ); $orig = $this->makeRevision( array( 'text' => 'hello hello rev.' ) ); $rev = Revision::newFromId( $orig->getId() ); @@ -226,31 +312,69 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getRawText */ - public function testGetRawText() - { + public function testGetRawText() { + $this->hideDeprecated( 'Revision::getRawText' ); + $orig = $this->makeRevision( array( 'text' => 'hello hello raw.' ) ); $rev = Revision::newFromId( $orig->getId() ); $this->assertEquals( 'hello hello raw.', $rev->getRawText() ); } + + /** + * @covers Revision::getContentModel + */ + public function testGetContentModel() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $orig = $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * @covers Revision::getContentFormat + */ + public function testGetContentFormat() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $orig = $this->makeRevision( array( + 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT, + 'content_format' => CONTENT_FORMAT_JAVASCRIPT + ) ); + $rev = Revision::newFromId( $orig->getId() ); + + $this->assertEquals( CONTENT_FORMAT_JAVASCRIPT, $rev->getContentFormat() ); + } + /** * @covers Revision::isCurrent */ - public function testIsCurrent() - { - $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum' ); + public function testIsCurrent() { + $page = $this->createPage( 'RevisionStorageTest_testIsCurrent', 'Lorem Ipsum', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); - # @todo: find out if this should be true + # @todo find out if this should be true # $this->assertTrue( $rev1->isCurrent() ); $rev1x = Revision::newFromId( $rev1->getId() ); $this->assertTrue( $rev1x->isCurrent() ); - $page->doEdit( 'Bla bla', 'second rev' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), 'second rev' ); $rev2 = $page->getRevision(); - # @todo: find out if this should be true + # @todo find out if this should be true # $this->assertTrue( $rev2->isCurrent() ); $rev1x = Revision::newFromId( $rev1->getId() ); @@ -263,14 +387,14 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getPrevious */ - public function testGetPrevious() - { - $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious' ); + public function testGetPrevious() { + $page = $this->createPage( 'RevisionStorageTest_testGetPrevious', 'Lorem Ipsum testGetPrevious', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); $this->assertNull( $rev1->getPrevious() ); - $page->doEdit( 'Bla bla', 'second rev testGetPrevious' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + 'second rev testGetPrevious' ); $rev2 = $page->getRevision(); $this->assertNotNull( $rev2->getPrevious() ); @@ -280,14 +404,14 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::getNext */ - public function testGetNext() - { - $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext' ); + public function testGetNext() { + $page = $this->createPage( 'RevisionStorageTest_testGetNext', 'Lorem Ipsum testGetNext', CONTENT_MODEL_WIKITEXT ); $rev1 = $page->getRevision(); $this->assertNull( $rev1->getNext() ); - $page->doEdit( 'Bla bla', 'second rev testGetNext' ); + $page->doEditContent( ContentHandler::makeContent( 'Bla bla', $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + 'second rev testGetNext' ); $rev2 = $page->getRevision(); $this->assertNotNull( $rev1->getNext() ); @@ -297,20 +421,21 @@ class RevisionStorageTest extends MediaWikiTestCase { /** * @covers Revision::newNullRevision */ - public function testNewNullRevision() - { - $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text' ); + public function testNewNullRevision() { + $page = $this->createPage( 'RevisionStorageTest_testNewNullRevision', 'some testing text', CONTENT_MODEL_WIKITEXT ); $orig = $page->getRevision(); $dbw = wfGetDB( DB_MASTER ); $rev = Revision::newNullRevision( $dbw, $page->getId(), 'a null revision', false ); - $this->assertNotEquals( $orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision' ); - $this->assertEquals( $orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision' ); - $this->assertEquals( 'some testing text', $rev->getText() ); + $this->assertNotEquals( $orig->getId(), $rev->getId(), + 'new null revision shold have a different id from the original revision' ); + $this->assertEquals( $orig->getTextId(), $rev->getTextId(), + 'new null revision shold have the same text id as the original revision' ); + $this->assertEquals( 'some testing text', $rev->getContent()->getNativeData() ); } - public function dataUserWasLastToEdit() { + public static function provideUserWasLastToEdit() { return array( array( #0 3, true, # actually the last edit @@ -328,32 +453,37 @@ class RevisionStorageTest extends MediaWikiTestCase { } /** - * @dataProvider dataUserWasLastToEdit + * @dataProvider provideUserWasLastToEdit */ public function testUserWasLastToEdit( $sinceIdx, $expectedLast ) { - $userA = \User::newFromName( "RevisionStorageTest_userA" ); - $userB = \User::newFromName( "RevisionStorageTest_userB" ); + $userA = User::newFromName( "RevisionStorageTest_userA" ); + $userB = User::newFromName( "RevisionStorageTest_userB" ); if ( $userA->getId() === 0 ) { - $userA = \User::createNew( $userA->getName() ); + $userA = User::createNew( $userA->getName() ); } if ( $userB->getId() === 0 ) { - $userB = \User::createNew( $userB->getName() ); + $userB = User::createNew( $userB->getName() ); } + $ns = $this->getDefaultWikitextNS(); + $dbw = wfGetDB( DB_MASTER ); $revisions = array(); // create revisions ----------------------------- - $page = WikiPage::factory( Title::newFromText( 'RevisionStorageTest_testUserWasLastToEdit' ) ); + $page = WikiPage::factory( Title::newFromText( + 'RevisionStorageTest_testUserWasLastToEdit', $ns ) ); # zero $revisions[0] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), // we need the title to determine the page's default content model 'timestamp' => '20120101000000', 'user' => $userA->getId(), 'text' => 'zero', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit zero' ) ); $revisions[0]->insertOn( $dbw ); @@ -361,9 +491,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # one $revisions[1] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), // still need the title, because $page->getId() is 0 (there's no entry in the page table) 'timestamp' => '20120101000100', 'user' => $userA->getId(), 'text' => 'one', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit one' ) ); $revisions[1]->insertOn( $dbw ); @@ -371,9 +503,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # two $revisions[2] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000200', 'user' => $userB->getId(), 'text' => 'two', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit two' ) ); $revisions[2]->insertOn( $dbw ); @@ -381,9 +515,11 @@ class RevisionStorageTest extends MediaWikiTestCase { # three $revisions[3] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000300', 'user' => $userA->getId(), 'text' => 'three', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit three' ) ); $revisions[3]->insertOn( $dbw ); @@ -391,15 +527,17 @@ class RevisionStorageTest extends MediaWikiTestCase { # four $revisions[4] = new Revision( array( 'page' => $page->getId(), + 'title' => $page->getTitle(), 'timestamp' => '20120101000200', 'user' => $userA->getId(), 'text' => 'zero', + 'content_model' => CONTENT_MODEL_WIKITEXT, 'summary' => 'edit four' ) ); $revisions[4]->insertOn( $dbw ); // test it --------------------------------- - $since = $revisions[ $sinceIdx ]->getTimestamp(); + $since = $revisions[$sinceIdx]->getTimestamp(); $wasLast = Revision::userWasLastToEdit( $dbw, $page->getId(), $userA->getId(), $since ); diff --git a/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php new file mode 100644 index 00000000..4e83e355 --- /dev/null +++ b/tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php @@ -0,0 +1,81 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + */ +class RevisionTest_ContentHandlerUseDB extends RevisionStorageTest { + + protected function setUp() { + $this->setMwGlobals( 'wgContentHandlerUseDB', false ); + + $dbw = wfGetDB( DB_MASTER ); + + $page_table = $dbw->tableName( 'page' ); + $revision_table = $dbw->tableName( 'revision' ); + $archive_table = $dbw->tableName( 'archive' ); + + if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { + $dbw->query( "alter table $page_table drop column page_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_format" ); + $dbw->query( "alter table $archive_table drop column ar_content_model" ); + $dbw->query( "alter table $archive_table drop column ar_content_format" ); + } + + parent::setUp(); + } + + /** + * @covers Revision::selectFields + */ + public function testSelectFields() { + $fields = Revision::selectFields(); + + $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' ); + $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' ); + $this->assertTrue( in_array( 'rev_timestamp', $fields ), 'missing rev_timestamp in list of fields' ); + $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' ); + + $this->assertFalse( in_array( 'rev_content_model', $fields ), 'missing rev_content_model in list of fields' ); + $this->assertFalse( in_array( 'rev_content_format', $fields ), 'missing rev_content_format in list of fields' ); + } + + /** + * @covers Revision::getContentModel + */ + public function testGetContentModel() { + try { + $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT ) ); + + $this->fail( "Creating JavaScript content on a wikitext page should fail with " + . "\$wgContentHandlerUseDB disabled" ); + } catch ( MWException $ex ) { + $this->assertTrue( true ); // ok + } + } + + + /** + * @covers Revision::getContentFormat + */ + public function testGetContentFormat() { + try { + // @todo change this to test failure on using a non-standard (but supported) format + // for a content model supported in the given location. As of 1.21, there are + // no alternative formats for any of the standard content models that could be + // used for this though. + + $this->makeRevision( array( 'text' => 'hello hello.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT, + 'content_format' => 'text/javascript' ) ); + + $this->fail( "Creating JavaScript content on a wikitext page should fail with " + . "\$wgContentHandlerUseDB disabled" ); + } catch ( MWException $ex ) { + $this->assertTrue( true ); // ok + } + } +} diff --git a/tests/phpunit/includes/RevisionTest.php b/tests/phpunit/includes/RevisionTest.php index d7654db9..b5819ff6 100644 --- a/tests/phpunit/includes/RevisionTest.php +++ b/tests/phpunit/includes/RevisionTest.php @@ -1,28 +1,63 @@ <?php +/** + * @group ContentHandler + */ class RevisionTest extends MediaWikiTestCase { - var $saveGlobals = array(); - - function setUp() { + protected function setUp() { global $wgContLang; - $wgContLang = Language::factory( 'en' ); - $globalSet = array( + + parent::setUp(); + + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + 'wgLanguageCode' => 'en', 'wgLegacyEncoding' => false, 'wgCompressRevisions' => false, + + 'wgContentHandlerTextFallback' => 'ignore', + ) ); + + $this->mergeMwGlobalArrayValue( + 'wgExtraNamespaces', + array( + 12312 => 'Dummy', + 12313 => 'Dummy_talk', + ) ); - foreach ( $globalSet as $var => $data ) { - $this->saveGlobals[$var] = $GLOBALS[$var]; - $GLOBALS[$var] = $data; - } + + $this->mergeMwGlobalArrayValue( + 'wgNamespaceContentModels', + array( + 12312 => 'testing', + ) + ); + + $this->mergeMwGlobalArrayValue( + 'wgContentHandlers', + array( + 'testing' => 'DummyContentHandlerForTesting', + 'RevisionTestModifyableContent' => 'RevisionTestModifyableContentHandler', + ) + ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache } function tearDown() { - foreach ( $this->saveGlobals as $var => $data ) { - $GLOBALS[$var] = $data; - } + global $wgContLang; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + + parent::tearDown(); } - function testGetRevisionText() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionText() { $row = new stdClass; $row->old_flags = ''; $row->old_text = 'This is a bunch of revision text.'; @@ -31,20 +66,24 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( 'This is a bunch of revision text.' ); - $this->assertEquals( - 'This is a bunch of revision text.', - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip'; + $row->old_text = gzdeflate( 'This is a bunch of revision text.' ); + $this->assertEquals( + 'This is a bunch of revision text.', + Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8Native() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8Native() { $row = new stdClass; $row->old_flags = 'utf-8'; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; @@ -54,7 +93,10 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8Legacy() { + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8Legacy() { $row = new stdClass; $row->old_flags = ''; $row->old_text = "Wiki est l'\xe9cole superieur !"; @@ -64,35 +106,40 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8NativeGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip,utf-8'; - $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8NativeGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip,utf-8'; + $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ); + $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; + $this->assertEquals( + "Wiki est l'\xc3\xa9cole superieur !", + Revision::getRevisionText( $row ) ); } - function testGetRevisionTextUtf8LegacyGzip() { - if ( !function_exists( 'gzdeflate' ) ) { - $this->markTestSkipped( 'Gzip compression is not enabled (requires zlib).' ); - } else { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } + /** + * @covers Revision::getRevisionText + */ + public function testGetRevisionTextUtf8LegacyGzip() { + $this->checkPHPExtension( 'zlib' ); + + $row = new stdClass; + $row->old_flags = 'gzip'; + $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" ); + $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; + $this->assertEquals( + "Wiki est l'\xc3\xa9cole superieur !", + Revision::getRevisionText( $row ) ); } - function testCompressRevisionTextUtf8() { + /** + * @covers Revision::compressRevisionText + */ + public function testCompressRevisionTextUtf8() { $row = new stdClass; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; $row->old_flags = Revision::compressRevisionText( $row->old_text ); @@ -106,8 +153,13 @@ class RevisionTest extends MediaWikiTestCase { Revision::getRevisionText( $row ), "getRevisionText" ); } - function testCompressRevisionTextUtf8Gzip() { - $GLOBALS['wgCompressRevisions'] = true; + /** + * @covers Revision::compressRevisionText + */ + public function testCompressRevisionTextUtf8Gzip() { + $this->checkPHPExtension( 'zlib' ); + $this->setMwGlobals( 'wgCompressRevisions', true ); + $row = new stdClass; $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; $row->old_flags = Revision::compressRevisionText( $row->old_text ); @@ -120,6 +172,310 @@ class RevisionTest extends MediaWikiTestCase { $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", Revision::getRevisionText( $row ), "getRevisionText" ); } + + # ================================================================================================================= + + /** + * @param string $text + * @param string $title + * @param string $model + * @param null $format + * + * @return Revision + */ + function newTestRevision( $text, $title = "Test", $model = CONTENT_MODEL_WIKITEXT, $format = null ) { + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + + $content = ContentHandler::makeContent( $text, $title, $model, $format ); + + $rev = new Revision( + array( + 'id' => 42, + 'page' => 23, + 'title' => $title, + + 'content' => $content, + 'length' => $content->getSize(), + 'comment' => "testing", + 'minor_edit' => false, + + 'content_format' => $format, + ) + ); + + return $rev; + } + + function dataGetContentModel() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, CONTENT_MODEL_WIKITEXT ), + array( 'hello world', 'User:hello/there.css', null, null, CONTENT_MODEL_CSS ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentModel + * @covers Revision::getContentModel + */ + public function testGetContentModel( $text, $title, $model, $format, $expectedModel ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedModel, $rev->getContentModel() ); + } + + function dataGetContentFormat() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, CONTENT_FORMAT_WIKITEXT ), + array( 'hello world', 'Help:Hello', CONTENT_MODEL_CSS, null, CONTENT_FORMAT_CSS ), + array( 'hello world', 'User:hello/there.css', null, null, CONTENT_FORMAT_CSS ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, "testing" ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentFormat + * @covers Revision::getContentFormat + */ + public function testGetContentFormat( $text, $title, $model, $format, $expectedFormat ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedFormat, $rev->getContentFormat() ); + } + + function dataGetContentHandler() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, 'WikitextContentHandler' ), + array( 'hello world', 'User:hello/there.css', null, null, 'CssContentHandler' ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, 'DummyContentHandlerForTesting' ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContentHandler + * @covers Revision::getContentHandler + */ + public function testGetContentHandler( $text, $title, $model, $format, $expectedClass ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedClass, get_class( $rev->getContentHandler() ) ); + } + + function dataGetContent() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ), + array( serialize( 'hello world' ), 'Hello', "testing", null, Revision::FOR_PUBLIC, serialize( 'hello world' ) ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, serialize( 'hello world' ) ), + ); + } + + /** + * @group Database + * @dataProvider dataGetContent + * @covers Revision::getContent + */ + public function testGetContent( $text, $title, $model, $format, $audience, $expectedSerialization ) { + $rev = $this->newTestRevision( $text, $title, $model, $format ); + $content = $rev->getContent( $audience ); + + $this->assertEquals( $expectedSerialization, is_null( $content ) ? null : $content->serialize( $format ) ); + } + + function dataGetText() { + //NOTE: we expect the help namespace to always contain wikitext + return array( + array( 'hello world', 'Help:Hello', null, null, Revision::FOR_PUBLIC, 'hello world' ), + array( serialize( 'hello world' ), 'Hello', "testing", null, Revision::FOR_PUBLIC, null ), + array( serialize( 'hello world' ), 'Dummy:Hello', null, null, Revision::FOR_PUBLIC, null ), + ); + } + + /** + * @group Database + * @dataProvider dataGetText + * @covers Revision::getText + */ + public function testGetText( $text, $title, $model, $format, $audience, $expectedText ) { + $this->hideDeprecated( 'Revision::getText' ); + + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedText, $rev->getText( $audience ) ); + } + + /** + * @group Database + * @dataProvider dataGetText + * @covers Revision::getRawText + */ + public function testGetRawText( $text, $title, $model, $format, $audience, $expectedText ) { + $this->hideDeprecated( 'Revision::getRawText' ); + + $rev = $this->newTestRevision( $text, $title, $model, $format ); + + $this->assertEquals( $expectedText, $rev->getRawText( $audience ) ); + } + + + public function dataGetSize() { + return array( + array( "hello world.", CONTENT_MODEL_WIKITEXT, 12 ), + array( serialize( "hello world." ), "testing", 12 ), + ); + } + + /** + * @covers Revision::getSize + * @group Database + * @dataProvider dataGetSize + */ + public function testGetSize( $text, $model, $expected_size ) { + $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSize', $model ); + $this->assertEquals( $expected_size, $rev->getSize() ); + } + + public function dataGetSha1() { + return array( + array( "hello world.", CONTENT_MODEL_WIKITEXT, Revision::base36Sha1( "hello world." ) ), + array( serialize( "hello world." ), "testing", Revision::base36Sha1( serialize( "hello world." ) ) ), + ); + } + + /** + * @covers Revision::getSha1 + * @group Database + * @dataProvider dataGetSha1 + */ + public function testGetSha1( $text, $model, $expected_hash ) { + $rev = $this->newTestRevision( $text, 'RevisionTest_testGetSha1', $model ); + $this->assertEquals( $expected_hash, $rev->getSha1() ); + } + + /** + * @covers Revision::__construct + */ + public function testConstructWithText() { + $this->hideDeprecated( "Revision::getText" ); + + $rev = new Revision( array( + 'text' => 'hello world.', + 'content_model' => CONTENT_MODEL_JAVASCRIPT + ) ); + + $this->assertNotNull( $rev->getText(), 'no content text' ); + $this->assertNotNull( $rev->getContent(), 'no content object available' ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * @covers Revision::__construct + */ + public function testConstructWithContent() { + $this->hideDeprecated( "Revision::getText" ); + + $title = Title::newFromText( 'RevisionTest_testConstructWithContent' ); + + $rev = new Revision( array( + 'content' => ContentHandler::makeContent( 'hello world.', $title, CONTENT_MODEL_JAVASCRIPT ), + ) ); + + $this->assertNotNull( $rev->getText(), 'no content text' ); + $this->assertNotNull( $rev->getContent(), 'no content object available' ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContent()->getModel() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $rev->getContentModel() ); + } + + /** + * Tests whether $rev->getContent() returns a clone when needed. + * + * @group Database + * @covers Revision::getContent + */ + public function testGetContentClone() { + $content = new RevisionTestModifyableContent( "foo" ); + + $rev = new Revision( + array( + 'id' => 42, + 'page' => 23, + 'title' => Title::newFromText( "testGetContentClone_dummy" ), + + 'content' => $content, + 'length' => $content->getSize(), + 'comment' => "testing", + 'minor_edit' => false, + ) + ); + + $content = $rev->getContent( Revision::RAW ); + $content->setText( "bar" ); + + $content2 = $rev->getContent( Revision::RAW ); + $this->assertNotSame( $content, $content2, "expected a clone" ); // content is mutable, expect clone + $this->assertEquals( "foo", $content2->getText() ); // clone should contain the original text + + $content2->setText( "bla bla" ); + $this->assertEquals( "bar", $content->getText() ); // clones should be independent + } + + + /** + * Tests whether $rev->getContent() returns the same object repeatedly if appropriate. + * + * @group Database + * @covers Revision::getContent + */ + public function testGetContentUncloned() { + $rev = $this->newTestRevision( "hello", "testGetContentUncloned_dummy", CONTENT_MODEL_WIKITEXT ); + $content = $rev->getContent( Revision::RAW ); + $content2 = $rev->getContent( Revision::RAW ); + + // for immutable content like wikitext, this should be the same object + $this->assertSame( $content, $content2 ); + } } +class RevisionTestModifyableContent extends TextContent { + public function __construct( $text ) { + parent::__construct( $text, "RevisionTestModifyableContent" ); + } + + public function copy() { + return new RevisionTestModifyableContent( $this->mText ); + } + + public function getText() { + return $this->mText; + } + + public function setText( $text ) { + $this->mText = $text; + } +} +class RevisionTestModifyableContentHandler extends TextContentHandler { + + public function __construct() { + parent::__construct( "RevisionTestModifyableContent", array( CONTENT_FORMAT_TEXT ) ); + } + + public function unserializeContent( $text, $format = null ) { + $this->checkFormat( $format ); + + return new RevisionTestModifyableContent( $text ); + } + + public function makeEmptyContent() { + return new RevisionTestModifyableContent( '' ); + } +} diff --git a/tests/phpunit/includes/SampleTest.php b/tests/phpunit/includes/SampleTest.php index 59ba0a04..8516a4ce 100644 --- a/tests/phpunit/includes/SampleTest.php +++ b/tests/phpunit/includes/SampleTest.php @@ -5,43 +5,50 @@ class TestSample extends MediaWikiLangTestCase { /** * Anything that needs to happen before your tests should go here. */ - function setUp() { - global $wgContLang; + protected function setUp() { + // Be sure to do call the parent setup and teardown functions. + // This makes sure that all the various cleanup and restorations + // happen as they should (including the restoration for setMwGlobals). parent::setUp(); - /* For example, we need to set $wgContLang for creating a new Title */ - $wgContLang = Language::factory( 'en' ); + // This sets the globals and will restore them automatically + // after each test. + $this->setMwGlobals( array( + 'wgContLang' => Language::factory( 'en' ), + 'wgLanguageCode' => 'en', + ) ); } /** * Anything cleanup you need to do should go here. */ - function tearDown() { + protected function tearDown() { parent::tearDown(); } /** - * Name tests so that PHPUnit can turn them into sentances when + * Name tests so that PHPUnit can turn them into sentences when * they run. While MediaWiki isn't strictly an Agile Programming * project, you are encouraged to use the naming described under * "Agile Documentation" at * http://www.phpunit.de/manual/3.4/en/other-uses-for-tests.html */ - function testTitleObjectStringConversion() { - $title = Title::newFromText("text"); - $this->assertEquals("Text", $title->__toString(), "Title creation"); - $this->assertEquals("Text", "Text", "Automatic string conversion"); - - $title = Title::newFromText("text", NS_MEDIA); - $this->assertEquals("Media:Text", $title->__toString(), "Title creation with namespace"); + public function testTitleObjectStringConversion() { + $title = Title::newFromText( "text" ); + $this->assertInstanceOf( 'Title', $title, "Title creation" ); + $this->assertEquals( "Text", $title, "Automatic string conversion" ); + $title = Title::newFromText( "text", NS_MEDIA ); + $this->assertEquals( "Media:Text", $title, "Title creation with namespace" ); } /** * If you want to run a the same test with a variety of data. use a data provider. * see: http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html + * + * Note: Data providers are always called statically and outside setUp/tearDown! */ - public function provideTitles() { + public static function provideTitles() { return array( array( 'Text', NS_MEDIA, 'Media:Text' ), array( 'Text', null, 'Text' ), @@ -55,14 +62,14 @@ class TestSample extends MediaWikiLangTestCase { * @dataProvider provideTitles * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.dataProvider */ - public function testCreateBasicListOfTitles($titleName, $ns, $text) { - $title = Title::newFromText($titleName, $ns); - $this->assertEquals($text, "$title", "see if '$titleName' matches '$text'"); + public function testCreateBasicListOfTitles( $titleName, $ns, $text ) { + $title = Title::newFromText( $titleName, $ns ); + $this->assertEquals( $text, "$title", "see if '$titleName' matches '$text'" ); } public function testSetUpMainPageTitleForNextTest() { $title = Title::newMainPage(); - $this->assertEquals("Main Page", "$title", "Test initial creation of a title"); + $this->assertEquals( "Main Page", "$title", "Test initial creation of a title" ); return $title; } @@ -78,6 +85,7 @@ class TestSample extends MediaWikiLangTestCase { * example) as arguments to the next method (e.g. $title in * testTitleDepends is whatever testInitialCreatiion returned.) */ + /** * @depends testSetUpMainPageTitleForNextTest * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.depends @@ -90,9 +98,8 @@ class TestSample extends MediaWikiLangTestCase { * @expectedException MWException object * See http://www.phpunit.de/manual/3.4/en/appendixes.annotations.html#appendixes.annotations.expectedException */ - function testTitleObjectFromObject() { + public function testTitleObjectFromObject() { $title = Title::newFromText( Title::newFromText( "test" ) ); $this->assertEquals( "Test", $title->isLocal() ); } } - diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php index 66af2581..81246d33 100644 --- a/tests/phpunit/includes/SanitizerTest.php +++ b/tests/phpunit/includes/SanitizerTest.php @@ -1,12 +1,21 @@ <?php +/** + * @todo Tests covering decodeCharReferences can be refactored into a single + * method and dataprovider. + */ class SanitizerTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); + AutoLoader::loadClass( 'Sanitizer' ); } - function testDecodeNamedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeNamedEntities() { $this->assertEquals( "\xc3\xa9cole", Sanitizer::decodeCharReferences( 'école' ), @@ -14,7 +23,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeNumericEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeNumericEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole!", Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), @@ -22,7 +34,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeMixedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeMixedEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole!", Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), @@ -30,7 +45,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testDecodeMixedComplexEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testDecodeMixedComplexEntities() { $this->assertEquals( "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas Ĉio dans l'école)", Sanitizer::decodeCharReferences( @@ -40,7 +58,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidAmpersand() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidAmpersand() { $this->assertEquals( 'a & b', Sanitizer::decodeCharReferences( 'a & b' ), @@ -48,7 +69,10 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidEntities() { $this->assertEquals( '&foo;', Sanitizer::decodeCharReferences( '&foo;' ), @@ -56,68 +80,150 @@ class SanitizerTest extends MediaWikiTestCase { ); } - function testInvalidNumberedEntities() { + /** + * @covers Sanitizer::decodeCharReferences + */ + public function testInvalidNumberedEntities() { $this->assertEquals( UTF8_REPLACEMENT, Sanitizer::decodeCharReferences( "�" ), 'Invalid numbered entity' ); } - function testSelfClosingTag() { + /** + * @covers Sanitizer::removeHTMLtags + * @dataProvider provideHtml5Tags + * + * @param String $tag Name of an HTML5 element (ie: 'video') + * @param Boolean $escaped Wheter sanitizer let the tag in or escape it (ie: '<video>') + */ + public function testRemovehtmltagsOnHtml5Tags( $tag, $escaped ) { + $this->setMwGlobals( array( + 'wgUseTidy' => false + ) ); + + if ( $escaped ) { + $this->assertEquals( "<$tag>", + Sanitizer::removeHTMLtags( "<$tag>" ) + ); + } else { + $this->assertEquals( "<$tag></$tag>\n", + Sanitizer::removeHTMLtags( "<$tag>" ) + ); + } + } + + /** + * Provide HTML5 tags + */ + public static function provideHtml5Tags() { + $ESCAPED = true; # We want tag to be escaped + $VERBATIM = false; # We want to keep the tag + return array( + array( 'data', $VERBATIM ), + array( 'mark', $VERBATIM ), + array( 'time', $VERBATIM ), + array( 'video', $ESCAPED ), + ); + } + + function dataRemoveHTMLtags() { + return array( + // former testSelfClosingTag + array( + '<div>Hello world</div />', + '<div>Hello world</div>', + 'Self-closing closing div' + ), + // Make sure special nested HTML5 semantics are not broken + // http://www.whatwg.org/html/text-level-semantics.html#the-kbd-element + array( + '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>', + '<kbd><kbd>Shift</kbd>+<kbd>F3</kbd></kbd>', + 'Nested <kbd>.' + ), + // http://www.whatwg.org/html/text-level-semantics.html#the-sub-and-sup-elements + array( + '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>', + '<var>x<sub><var>i</var></sub></var>, <var>y<sub><var>i</var></sub></var>', + 'Nested <var>.' + ), + // http://www.whatwg.org/html/text-level-semantics.html#the-dfn-element + array( + '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>', + '<dfn><abbr title="Garage Door Opener">GDO</abbr></dfn>', + '<abbr> inside <dfn>', + ), + ); + } + + /** + * @dataProvider dataRemoveHTMLtags + * @covers Sanitizer::removeHTMLtags + */ + public function testRemoveHTMLtags( $input, $output, $msg = null ) { $GLOBALS['wgUseTidy'] = false; - $this->assertEquals( - '<div>Hello world</div>', - Sanitizer::removeHTMLtags( '<div>Hello world</div />' ), - 'Self-closing closing div' - ); - } - - function testDecodeTagAttributes() { - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=bar' ), array( 'foo' => 'bar' ), 'Unquoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( ' foo = bar ' ), array( 'foo' => 'bar' ), 'Spaced attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo="bar"' ), array( 'foo' => 'bar' ), 'Double-quoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\'' ), array( 'foo' => 'bar' ), 'Single-quoted attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'bar\' baz="foo"' ), array( 'foo' => 'bar', 'baz' => 'foo' ), 'Several attributes' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( ':foo=\'bar\'' ), array( ':foo' => 'bar' ), 'Leading :' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( '_foo=\'bar\'' ), array( '_foo' => 'bar' ), 'Leading _' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'Foo=\'bar\'' ), array( 'foo' => 'bar' ), 'Leading capital' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'FOO=BAR' ), array( 'foo' => 'BAR' ), 'Attribute keys are normalized to lowercase' ); - - # Invalid beginning - $this->assertEquals( Sanitizer::decodeTagAttributes( '-foo=bar' ), array(), 'Leading - is forbidden' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( '.foo=bar' ), array(), 'Leading . is forbidden' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo-bar=bar' ), array( 'foo-bar' => 'bar' ), 'A - is allowed inside the attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo-=bar' ), array( 'foo-' => 'bar' ), 'A - is allowed inside the attribute' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo.bar=baz' ), array( 'foo.bar' => 'baz' ), 'A . is allowed inside the attribute' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo.=baz' ), array( 'foo.' => 'baz' ), 'A . is allowed as last character' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo6=baz' ), array( 'foo6' => 'baz' ), 'Numbers are allowed' ); - - # This bit is more relaxed than XML rules, but some extensions use it, like ProofreadPage (see bug 27539) - $this->assertEquals( Sanitizer::decodeTagAttributes( '1foo=baz' ), array( '1foo' => 'baz' ), 'Leading numbers are allowed' ); - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo$=baz' ), array(), 'Symbols are not allowed' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo@=baz' ), array(), 'Symbols are not allowed' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo~=baz' ), array(), 'Symbols are not allowed' ); - - - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=1[#^`*%w/(' ), array( 'foo' => '1[#^`*%w/(' ), 'All kind of characters are allowed as values' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo="1[#^`*%\'w/("' ), array( 'foo' => '1[#^`*%\'w/(' ), 'Double quotes are allowed if quoted by single quotes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=\'1[#^`*%"w/(\'' ), array( 'foo' => '1[#^`*%"w/(' ), 'Single quotes are allowed if quoted by double quotes' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&"' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' ); - $this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' ); + $this->assertEquals( $output, Sanitizer::removeHTMLtags( $input ), $msg ); + } + + /** + * @dataProvider provideTagAttributesToDecode + * @covers Sanitizer::decodeTagAttributes + */ + public function testDecodeTagAttributes( $expected, $attributes, $message = '' ) { + $this->assertEquals( $expected, + Sanitizer::decodeTagAttributes( $attributes ), + $message + ); + } + + public static function provideTagAttributesToDecode() { + return array( + array( array( 'foo' => 'bar' ), 'foo=bar', 'Unquoted attribute' ), + array( array( 'foo' => 'bar' ), ' foo = bar ', 'Spaced attribute' ), + array( array( 'foo' => 'bar' ), 'foo="bar"', 'Double-quoted attribute' ), + array( array( 'foo' => 'bar' ), 'foo=\'bar\'', 'Single-quoted attribute' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( 'foo' => 'bar', 'baz' => 'foo' ), 'foo=\'bar\' baz="foo"', 'Several attributes' ), + array( array( ':foo' => 'bar' ), ':foo=\'bar\'', 'Leading :' ), + array( array( '_foo' => 'bar' ), '_foo=\'bar\'', 'Leading _' ), + array( array( 'foo' => 'bar' ), 'Foo=\'bar\'', 'Leading capital' ), + array( array( 'foo' => 'BAR' ), 'FOO=BAR', 'Attribute keys are normalized to lowercase' ), + + # Invalid beginning + array( array(), '-foo=bar', 'Leading - is forbidden' ), + array( array(), '.foo=bar', 'Leading . is forbidden' ), + array( array( 'foo-bar' => 'bar' ), 'foo-bar=bar', 'A - is allowed inside the attribute' ), + array( array( 'foo-' => 'bar' ), 'foo-=bar', 'A - is allowed inside the attribute' ), + array( array( 'foo.bar' => 'baz' ), 'foo.bar=baz', 'A . is allowed inside the attribute' ), + array( array( 'foo.' => 'baz' ), 'foo.=baz', 'A . is allowed as last character' ), + array( array( 'foo6' => 'baz' ), 'foo6=baz', 'Numbers are allowed' ), + + # This bit is more relaxed than XML rules, but some extensions use + # it, like ProofreadPage (see bug 27539) + array( array( '1foo' => 'baz' ), '1foo=baz', 'Leading numbers are allowed' ), + array( array(), 'foo$=baz', 'Symbols are not allowed' ), + array( array(), 'foo@=baz', 'Symbols are not allowed' ), + array( array(), 'foo~=baz', 'Symbols are not allowed' ), + array( array( 'foo' => '1[#^`*%w/(' ), 'foo=1[#^`*%w/(', 'All kind of characters are allowed as values' ), + array( array( 'foo' => '1[#^`*%\'w/(' ), 'foo="1[#^`*%\'w/("', 'Double quotes are allowed if quoted by single quotes' ), + array( array( 'foo' => '1[#^`*%"w/(' ), 'foo=\'1[#^`*%"w/(\'', 'Single quotes are allowed if quoted by double quotes' ), + array( array( 'foo' => '&"' ), 'foo=&"', 'Special chars can be provided as entities' ), + array( array( 'foo' => '&foobar;' ), 'foo=&foobar;', 'Entity-like items are accepted' ), + ); } /** * @dataProvider provideDeprecatedAttributes + * @covers Sanitizer::fixTagAttributes */ - function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl ) { - $this->assertEquals( " $inputAttr", Sanitizer::fixTagAttributes( $inputAttr, $inputEl ) ); + public function testDeprecatedAttributesUnaltered( $inputAttr, $inputEl, $message = '' ) { + $this->assertEquals( " $inputAttr", + Sanitizer::fixTagAttributes( $inputAttr, $inputEl ), + $message + ); } public static function provideDeprecatedAttributes() { + /** array( <attribute>, <element>, [message] ) */ return array( array( 'clear="left"', 'br' ), array( 'clear="all"', 'br' ), @@ -135,28 +241,62 @@ class SanitizerTest extends MediaWikiTestCase { /** * @dataProvider provideCssCommentsFixtures + * @covers Sanitizer::checkCss */ - function testCssCommentsChecking( $expected, $css, $message = '' ) { - $this->assertEquals( - $expected, + public function testCssCommentsChecking( $expected, $css, $message = '' ) { + $this->assertEquals( $expected, Sanitizer::checkCss( $css ), $message ); } - function provideCssCommentsFixtures() { + public static function provideCssCommentsFixtures() { /** array( <expected>, <css>, [message] ) */ return array( - array( ' ', '/**/' ), + // Valid comments spanning entire input + array( '/**/', '/**/' ), + array( '/* comment */', '/* comment */' ), + // Weird stuff array( ' ', '/****/' ), - array( ' ', '/* comment */' ), - array( ' ', "\\2f\\2a foo \\2a\\2f", + array( ' ', '/* /* */' ), + array( 'display: block;', "display:/* foo */block;" ), + array( 'display: block;', "display:\\2f\\2a foo \\2a\\2f block;", 'Backslash-escaped comments must be stripped (bug 28450)' ), array( '', '/* unfinished comment structure', - 'Remove anything after a comment-start token' ), + 'Remove anything after a comment-start token' ), array( '', "\\2f\\2a unifinished comment'", - 'Remove anything after a backslash-escaped comment-start token' ), + 'Remove anything after a backslash-escaped comment-start token' ), + array( '/* insecure input */', 'filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'asdf.png\',sizingMethod=\'scale\');' ), + array( '/* insecure input */', '-ms-filter: "progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\'asdf.png\',sizingMethod=\'scale\')";' ), + array( '/* insecure input */', 'width: expression(1+1);' ), + array( '/* insecure input */', 'background-image: image(asdf.png);' ), + array( '/* insecure input */', 'background-image: -webkit-image(asdf.png);' ), + array( '/* insecure input */', 'background-image: -moz-image(asdf.png);' ), + array( '/* insecure input */', 'background-image: image-set("asdf.png" 1x, "asdf.png" 2x);' ), + array( '/* insecure input */', 'background-image: -webkit-image-set("asdf.png" 1x, "asdf.png" 2x);' ), + array( '/* insecure input */', 'background-image: -moz-image-set("asdf.png" 1x, "asdf.png" 2x);' ), + ); + } + + /** + * Test for support or lack of support for specific attributes in the attribute whitelist. + */ + public static function provideAttributeSupport() { + /** array( <attributes>, <expected>, <message> ) */ + return array( + array( 'div', ' role="presentation"', ' role="presentation"', 'Support for WAI-ARIA\'s role="presentation".' ), + array( 'div', ' role="main"', '', "Other WAI-ARIA roles are currently not supported." ), ); } -} + /** + * @dataProvider provideAttributeSupport + * @covers Sanitizer::fixTagAttributes + */ + public function testAttributeSupport( $tag, $attributes, $expected, $message ) { + $this->assertEquals( $expected, + Sanitizer::fixTagAttributes( $attributes, $tag ), + $message + ); + } +} diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php index 14d799cf..f13e8382 100644 --- a/tests/phpunit/includes/SanitizerValidateEmailTest.php +++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php @@ -1,62 +1,82 @@ <?php +/** + * @covers Sanitizer::validateEmail + * @TODO all test methods in this class should be refactored and... + * use a single test method and a single data provider... + */ class SanitizerValidateEmailTest extends MediaWikiTestCase { - private function checkEmail( $addr, $expected = true, $msg = '') { - if( $msg == '' ) { $msg = "Testing $addr"; } + private function checkEmail( $addr, $expected = true, $msg = '' ) { + if ( $msg == '' ) { + $msg = "Testing $addr"; + } + $this->assertEquals( $expected, Sanitizer::validateEmail( $addr ), $msg ); } + private function valid( $addr, $msg = '' ) { $this->checkEmail( $addr, true, $msg ); } + private function invalid( $addr, $msg = '' ) { $this->checkEmail( $addr, false, $msg ); } - function testEmailWellKnownUserAtHostDotTldAreValid() { + public function testEmailWellKnownUserAtHostDotTldAreValid() { $this->valid( 'user@example.com' ); $this->valid( 'user@example.museum' ); } - function testEmailWithUpperCaseCharactersAreValid() { + + public function testEmailWithUpperCaseCharactersAreValid() { $this->valid( 'USER@example.com' ); $this->valid( 'user@EXAMPLE.COM' ); $this->valid( 'user@Example.com' ); $this->valid( 'USER@eXAMPLE.com' ); } - function testEmailWithAPlusInUserName() { + + public function testEmailWithAPlusInUserName() { $this->valid( 'user+sub@example.com' ); $this->valid( 'user+@example.com' ); } - function testEmailDoesNotNeedATopLevelDomain() { + + public function testEmailDoesNotNeedATopLevelDomain() { $this->valid( "user@localhost" ); $this->valid( "FooBar@localdomain" ); $this->valid( "nobody@mycompany" ); } - function testEmailWithWhiteSpacesBeforeOrAfterAreInvalids() { + + public function testEmailWithWhiteSpacesBeforeOrAfterAreInvalids() { $this->invalid( " user@host.com" ); $this->invalid( "user@host.com " ); $this->invalid( "\tuser@host.com" ); $this->invalid( "user@host.com\t" ); } - function testEmailWithWhiteSpacesAreInvalids() { + + public function testEmailWithWhiteSpacesAreInvalids() { $this->invalid( "User user@host" ); $this->invalid( "first last@mycompany" ); $this->invalid( "firstlast@my company" ); } - // bug 26948 : comma were matched by an incorrect regexp range - function testEmailWithCommasAreInvalids() { + + /** + * bug 26948 : comma were matched by an incorrect regexp range + */ + public function testEmailWithCommasAreInvalids() { $this->invalid( "user,foo@example.org" ); $this->invalid( "userfoo@ex,ample.org" ); } - function testEmailWithHyphens() { + + public function testEmailWithHyphens() { $this->valid( "user-foo@example.org" ); $this->valid( "userfoo@ex-ample.org" ); } - function testEmailDomainCanNotBeginWithDot() { + + public function testEmailDomainCanNotBeginWithDot() { $this->invalid( "user@." ); $this->invalid( "user@.localdomain" ); $this->invalid( "user@localdomain." ); @@ -64,16 +84,20 @@ class SanitizerValidateEmailTest extends MediaWikiTestCase { $this->valid( ".@localdomain" ); $this->invalid( ".@a............" ); } - function testEmailWithFunnyCharacters() { + + public function testEmailWithFunnyCharacters() { $this->valid( "\$user!ex{this}@123.com" ); } - function testEmailTopLevelDomainCanBeNumerical() { + + public function testEmailTopLevelDomainCanBeNumerical() { $this->valid( "user@example.1234" ); } - function testEmailWithoutAtSignIsInvalid() { + + public function testEmailWithoutAtSignIsInvalid() { $this->invalid( 'useràexample.com' ); } - function testEmailWithOneCharacterDomainIsValid() { + + public function testEmailWithOneCharacterDomainIsValid() { $this->valid( 'user@a' ); } } diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php deleted file mode 100644 index 8589c188..00000000 --- a/tests/phpunit/includes/SeleniumConfigurationTest.php +++ /dev/null @@ -1,228 +0,0 @@ -<?php - -class SeleniumConfigurationTest extends MediaWikiTestCase { - - /** - * The file where the test temporarity stores the selenium config. - * This should be cleaned up as part of teardown. - */ - private $tempFileName; - - /** - * String containing the a sample selenium settings - */ - private $testConfig0 = -' -[SeleniumSettings] -browsers[firefox] = "*firefox" -browsers[iexplorer] = "*iexploreproxy" -browsers[chrome] = "*chrome" -host = "localhost" -port = "foobarr" -wikiUrl = "http://localhost/deployment" -username = "xxxxxxx" -userPassword = "" -testBrowser = "chrome" -startserver = -stopserver = -jUnitLogFile = -runAgainstGrid = false - -[SeleniumTests] -testSuite[SimpleSeleniumTestSuite] = "tests/selenium/SimpleSeleniumTestSuite.php" -testSuite[TestSuiteName] = "testSuitePath" -'; - /** - * Array of expected browsers from $testConfig0 - */ - private $testBrowsers0 = array( 'firefox' => '*firefox', - 'iexplorer' => '*iexploreproxy', - 'chrome' => '*chrome' - ); - /** - * Array of expected selenium settings from $testConfig0 - */ - private $testSettings0 = array( - 'host' => 'localhost', - 'port' => 'foobarr', - 'wikiUrl' => 'http://localhost/deployment', - 'username' => 'xxxxxxx', - 'userPassword' => '', - 'testBrowser' => 'chrome', - 'startserver' => null, - 'stopserver' => null, - 'seleniumserverexecpath' => null, - 'jUnitLogFile' => null, - 'runAgainstGrid' => null - ); - /** - * Array of expected testSuites from $testConfig0 - */ - private $testSuites0 = array( - 'SimpleSeleniumTestSuite' => 'tests/selenium/SimpleSeleniumTestSuite.php', - 'TestSuiteName' => 'testSuitePath' - ); - - - /** - * Another sample selenium settings file contents - */ - private $testConfig1 = -' -[SeleniumSettings] -host = "localhost" -testBrowser = "firefox" -'; - /** - * Expected browsers from $testConfig1 - */ - private $testBrowsers1 = null; - /** - * Expected selenium settings from $testConfig1 - */ - private $testSettings1 = array( - 'host' => 'localhost', - 'port' => null, - 'wikiUrl' => null, - 'username' => null, - 'userPassword' => null, - 'testBrowser' => 'firefox', - 'startserver' => null, - 'stopserver' => null, - 'seleniumserverexecpath' => null, - 'jUnitLogFile' => null, - 'runAgainstGrid' => null - ); - /** - * Expected test suites from $testConfig1 - */ - private $testSuites1 = null; - - - public function setUp() { - if ( !defined( 'SELENIUMTEST' ) ) { - define( 'SELENIUMTEST', true ); - } - } - - /** - * Clean up the temporary file used to store the selenium settings. - */ - public function tearDown() { - if ( strlen( $this->tempFileName ) > 0 ) { - unlink( $this->tempFileName ); - unset( $this->tempFileName ); - } - parent::tearDown(); - } - - /** - * @expectedException MWException - * @group SeleniumFramework - */ - public function testErrorOnIncorrectConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites, - "Some_fake_settings_file.ini" ); - - } - - /** - * @expectedException MWException - * @group SeleniumFramework - */ - public function testErrorOnMissingConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - global $wgSeleniumConfigFile; - $wgSeleniumConfigFile = ''; - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites); - } - - /** - * @group SeleniumFramework - */ - public function testUsesGlobalVarForConfigFile() { - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = array(); - global $wgSeleniumConfigFile; - $this->writeToTempFile( $this->testConfig0 ); - $wgSeleniumConfigFile = $this->tempFileName; - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites); - $this->assertEquals($seleniumSettings, $this->testSettings0 , - 'The selenium settings should have been read from the file defined in $wgSeleniumConfigFile' - ); - $this->assertEquals($seleniumBrowsers, $this->testBrowsers0, - 'The available browsers should have been read from the file defined in $wgSeleniumConfigFile' - ); - $this->assertEquals($seleniumTestSuites, $this->testSuites0, - 'The test suites should have been read from the file defined in $wgSeleniumConfigFile' - ); - } - - /** - * @group SeleniumFramework - * @dataProvider sampleConfigs - */ - public function testgetSeleniumSettings($sampleConfig, $expectedSettings, $expectedBrowsers, $expectedSuites ) { - $this->writeToTempFile( $sampleConfig ); - $seleniumSettings = array(); - $seleniumBrowsers = array(); - $seleniumTestSuites = null; - - SeleniumConfig::getSeleniumSettings($seleniumSettings, - $seleniumBrowsers, - $seleniumTestSuites, - $this->tempFileName ); - - $this->assertEquals($seleniumSettings, $expectedSettings, - "The selenium settings for the following test configuration was not retrieved correctly" . $sampleConfig - ); - $this->assertEquals($seleniumBrowsers, $expectedBrowsers, - "The available browsers for the following test configuration was not retrieved correctly" . $sampleConfig - ); - $this->assertEquals($seleniumTestSuites, $expectedSuites, - "The test suites for the following test configuration was not retrieved correctly" . $sampleConfig - ); - - - } - - /** - * create a temp file and write text to it. - * @param $testToWrite the text to write to the temp file - */ - private function writeToTempFile($textToWrite) { - $this->tempFileName = tempnam(sys_get_temp_dir(), 'test_settings.'); - $tempFile = fopen( $this->tempFileName, "w" ); - fwrite($tempFile , $textToWrite); - fclose($tempFile); - } - - /** - * Returns an array containing: - * The contents of the selenium cingiguration ini file - * The expected selenium configuration array that getSeleniumSettings should return - * The expected available browsers array that getSeleniumSettings should return - * The expected test suites arrya that getSeleniumSettings should return - */ - public function sampleConfigs() { - return array( - array($this->testConfig0, $this->testSettings0, $this->testBrowsers0, $this->testSuites0 ), - array($this->testConfig1, $this->testSettings1, $this->testBrowsers1, $this->testSuites1 ) - ); - } - - -} diff --git a/tests/phpunit/includes/SiteConfigurationTest.php b/tests/phpunit/includes/SiteConfigurationTest.php index 57d3532a..053d8a7d 100644 --- a/tests/phpunit/includes/SiteConfigurationTest.php +++ b/tests/phpunit/includes/SiteConfigurationTest.php @@ -10,6 +10,7 @@ function getSiteParams( $conf, $wiki ) { break; } } + return array( 'suffix' => $site, 'lang' => $lang, @@ -23,12 +24,18 @@ function getSiteParams( $conf, $wiki ) { } class SiteConfigurationTest extends MediaWikiTestCase { - var $mConf; - function setUp() { + /** + * @var SiteConfiguration + */ + protected $mConf; + + protected function setUp() { + parent::setUp(); + $this->mConf = new SiteConfiguration; - $this->mConf->suffixes = array( 'wiki' ); + $this->mConf->suffixes = array( 'wikipedia' => 'wiki' ); $this->mConf->wikis = array( 'enwiki', 'dewiki', 'frwiki' ); $this->mConf->settings = array( 'simple' => array( @@ -92,8 +99,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { $GLOBALS['global'] = array( 'global' => 'global' ); } - - function testSiteFromDb() { + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDb() { $this->assertEquals( array( 'wikipedia', 'en' ), $this->mConf->siteFromDB( 'enwiki' ), @@ -118,7 +127,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetLocalDatabases() { + /** + * @covers SiteConfiguration::getLocalDatabases + */ + public function testGetLocalDatabases() { $this->assertEquals( array( 'enwiki', 'dewiki', 'frwiki' ), $this->mConf->getLocalDatabases(), @@ -126,7 +138,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetConfVariables() { + /** + * @covers SiteConfiguration::get + */ + public function testGetConfVariables() { $this->assertEquals( 'enwiki', $this->mConf->get( 'simple', 'enwiki', 'wiki' ), @@ -238,7 +253,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testSiteFromDbWithCallback() { + /** + * @covers SiteConfiguration::siteFromDB + */ + public function testSiteFromDbWithCallback() { $this->mConf->siteParamsCallback = 'getSiteParams'; $this->assertEquals( @@ -258,7 +276,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testParameterReplacement() { + /** + * @covers SiteConfiguration::get + */ + public function testParameterReplacement() { $this->mConf->siteParamsCallback = 'getSiteParams'; $this->assertEquals( @@ -288,7 +309,10 @@ class SiteConfigurationTest extends MediaWikiTestCase { ); } - function testGetAllGlobals() { + /** + * @covers SiteConfiguration::getAll + */ + public function testGetAllGlobals() { $this->mConf->siteParamsCallback = 'getSiteParams'; $getall = array( @@ -305,7 +329,7 @@ class SiteConfigurationTest extends MediaWikiTestCase { $this->assertEquals( $getall['simple'], $GLOBALS['simple'], 'extractAllGlobals(): simple setting' ); $this->assertEquals( $getall['fallback'], $GLOBALS['fallback'], 'extractAllGlobals(): fallback setting' ); $this->assertEquals( $getall['params'], $GLOBALS['params'], 'extractAllGlobals(): parameter replacement' ); - $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' ); - $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' ); + $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' ); + $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' ); } } diff --git a/tests/phpunit/includes/StringUtilsTest.php b/tests/phpunit/includes/StringUtilsTest.php new file mode 100644 index 00000000..89759e5c --- /dev/null +++ b/tests/phpunit/includes/StringUtilsTest.php @@ -0,0 +1,147 @@ +<?php + +class StringUtilsTest extends MediaWikiTestCase { + + /** + * This tests StringUtils::isUtf8 whenever we have the mbstring extension + * loaded. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithMbstring( $expected, $string ) { + if ( !function_exists( 'mb_check_encoding' ) ) { + $this->markTestSkipped( 'Test requires the mbstring PHP extension' ); + } + $this->assertEquals( $expected, + StringUtils::isUtf8( $string ), + 'Testing string "' . $this->escaped( $string ) . '" with mb_check_encoding' + ); + } + + /** + * This tests StringUtils::isUtf8 making sure we use the pure PHP + * implementation used as a fallback when mb_check_encoding() is + * not available. + * + * @covers StringUtils::isUtf8 + * @dataProvider provideStringsForIsUtf8Check + */ + public function testIsUtf8WithPhpFallbackImplementation( $expected, $string ) { + $this->assertEquals( $expected, + StringUtils::isUtf8( $string, /** disable mbstring: */true ), + 'Testing string "' . $this->escaped( $string ) . '" with pure PHP implementation' + ); + } + + /** + * Print high range characters as an hexadecimal + */ + function escaped( $string ) { + $escaped = ''; + $length = strlen( $string ); + for ( $i = 0; $i < $length; $i++ ) { + $char = $string[$i]; + $val = ord( $char ); + if ( $val > 127 ) { + $escaped .= '\x' . dechex( $val ); + } else { + $escaped .= $char; + } + } + + return $escaped; + } + + /** + * See also "UTF-8 decoder capability and stress test" by + * Markus Kuhn: + * http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + */ + public static function provideStringsForIsUtf8Check() { + // Expected return values for StringUtils::isUtf8() + $PASS = true; + $FAIL = false; + + return array( + 'some ASCII' => array( $PASS, 'Some ASCII' ), + 'euro sign' => array( $PASS, "Euro sign €" ), + + 'first possible sequence 1 byte' => array( $PASS, "\x00" ), + 'first possible sequence 2 bytes' => array( $PASS, "\xc2\x80" ), + 'first possible sequence 3 bytes' => array( $PASS, "\xe0\xa0\x80" ), + 'first possible sequence 4 bytes' => array( $PASS, "\xf0\x90\x80\x80" ), + 'first possible sequence 5 bytes' => array( $FAIL, "\xf8\x88\x80\x80\x80" ), + 'first possible sequence 6 bytes' => array( $FAIL, "\xfc\x84\x80\x80\x80\x80" ), + + 'last possible sequence 1 byte' => array( $PASS, "\x7f" ), + 'last possible sequence 2 bytes' => array( $PASS, "\xdf\xbf" ), + 'last possible sequence 3 bytes' => array( $PASS, "\xef\xbf\xbf" ), + 'last possible sequence 4 bytes (U+1FFFFF)' => array( $FAIL, "\xf7\xbf\xbf\xbf" ), + 'last possible sequence 5 bytes' => array( $FAIL, "\xfb\xbf\xbf\xbf\xbf" ), + 'last possible sequence 6 bytes' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf\xbf" ), + + 'boundary 1' => array( $PASS, "\xed\x9f\xbf" ), + 'boundary 2' => array( $PASS, "\xee\x80\x80" ), + 'boundary 3' => array( $PASS, "\xef\xbf\xbd" ), + 'boundary 4' => array( $PASS, "\xf2\x80\x80\x80" ), + 'boundary 5 (U+FFFFF)' => array( $PASS, "\xf3\xbf\xbf\xbf" ), + 'boundary 6 (U+100000)' => array( $PASS, "\xf4\x80\x80\x80" ), + 'boundary 7 (U+10FFFF)' => array( $PASS, "\xf4\x8f\xbf\xbf" ), + 'boundary 8 (U+110000)' => array( $FAIL, "\xf4\x90\x80\x80" ), + + 'malformed 1' => array( $FAIL, "\x80" ), + 'malformed 2' => array( $FAIL, "\xbf" ), + 'malformed 3' => array( $FAIL, "\x80\xbf" ), + 'malformed 4' => array( $FAIL, "\x80\xbf\x80" ), + 'malformed 5' => array( $FAIL, "\x80\xbf\x80\xbf" ), + 'malformed 6' => array( $FAIL, "\x80\xbf\x80\xbf\x80" ), + 'malformed 7' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf" ), + 'malformed 8' => array( $FAIL, "\x80\xbf\x80\xbf\x80\xbf\x80" ), + + 'last byte missing 1' => array( $FAIL, "\xc0" ), + 'last byte missing 2' => array( $FAIL, "\xe0\x80" ), + 'last byte missing 3' => array( $FAIL, "\xf0\x80\x80" ), + 'last byte missing 4' => array( $FAIL, "\xf8\x80\x80\x80" ), + 'last byte missing 5' => array( $FAIL, "\xfc\x80\x80\x80\x80" ), + 'last byte missing 6' => array( $FAIL, "\xdf" ), + 'last byte missing 7' => array( $FAIL, "\xef\xbf" ), + 'last byte missing 8' => array( $FAIL, "\xf7\xbf\xbf" ), + 'last byte missing 9' => array( $FAIL, "\xfb\xbf\xbf\xbf" ), + 'last byte missing 10' => array( $FAIL, "\xfd\xbf\xbf\xbf\xbf" ), + + 'extra continuation byte 1' => array( $FAIL, "e\xaf" ), + 'extra continuation byte 2' => array( $FAIL, "\xc3\x89\xaf" ), + 'extra continuation byte 3' => array( $FAIL, "\xef\xbc\xa5\xaf" ), + 'extra continuation byte 4' => array( $FAIL, "\xf0\x9d\x99\xb4\xaf" ), + + 'impossible bytes 1' => array( $FAIL, "\xfe" ), + 'impossible bytes 2' => array( $FAIL, "\xff" ), + 'impossible bytes 3' => array( $FAIL, "\xfe\xfe\xff\xff" ), + + 'overlong sequences 1' => array( $FAIL, "\xc0\xaf" ), + 'overlong sequences 2' => array( $FAIL, "\xc1\xaf" ), + 'overlong sequences 3' => array( $FAIL, "\xe0\x80\xaf" ), + 'overlong sequences 4' => array( $FAIL, "\xf0\x80\x80\xaf" ), + 'overlong sequences 5' => array( $FAIL, "\xf8\x80\x80\x80\xaf" ), + 'overlong sequences 6' => array( $FAIL, "\xfc\x80\x80\x80\x80\xaf" ), + + 'maximum overlong sequences 1' => array( $FAIL, "\xc1\xbf" ), + 'maximum overlong sequences 2' => array( $FAIL, "\xe0\x9f\xbf" ), + 'maximum overlong sequences 3' => array( $FAIL, "\xf0\x8f\xbf\xbf" ), + 'maximum overlong sequences 4' => array( $FAIL, "\xf8\x87\xbf\xbf" ), + 'maximum overlong sequences 5' => array( $FAIL, "\xfc\x83\xbf\xbf\xbf\xbf" ), + + 'surrogates 1 (U+D799)' => array( $PASS, "\xed\x9f\xbf" ), + 'surrogates 2 (U+E000)' => array( $PASS, "\xee\x80\x80" ), + 'surrogates 3 (U+D800)' => array( $FAIL, "\xed\xa0\x80" ), + 'surrogates 4 (U+DBFF)' => array( $FAIL, "\xed\xaf\xbf" ), + 'surrogates 5 (U+DC00)' => array( $FAIL, "\xed\xb0\x80" ), + 'surrogates 6 (U+DFFF)' => array( $FAIL, "\xed\xbf\xbf" ), + 'surrogates 7 (U+D800 U+DC00)' => array( $FAIL, "\xed\xa0\x80\xed\xb0\x80" ), + + 'noncharacters 1' => array( $PASS, "\xef\xbf\xbe" ), + 'noncharacters 2' => array( $PASS, "\xef\xbf\xbf" ), + ); + } +} diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php index 39ce6e31..fb63a564 100644 --- a/tests/phpunit/includes/TemplateCategoriesTest.php +++ b/tests/phpunit/includes/TemplateCategoriesTest.php @@ -7,22 +7,40 @@ require __DIR__ . "/../../../maintenance/runJobs.php"; class TemplateCategoriesTest extends MediaWikiLangTestCase { - function testTemplateCategories() { + /** + * @covers Title::getParentCategories + */ + public function testTemplateCategories() { $title = Title::newFromText( "Categorized from template" ); $page = WikiPage::factory( $title ); $user = new User(); $user->mRights = array( 'createpage', 'edit', 'purge' ); - $status = $page->doEdit( '{{Categorising template}}', 'Create a page with a template', 0, false, $user ); + $page->doEditContent( + new WikitextContent( '{{Categorising template}}' ), + 'Create a page with a template', + 0, + false, + $user + ); + $this->assertEquals( array() , $title->getParentCategories() ); $template = WikiPage::factory( Title::newFromText( 'Template:Categorising template' ) ); - $status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0, false, $user ); + + $template->doEditContent( + new WikitextContent( '[[Category:Solved bugs]]' ), + 'Add a category through a template', + 0, + false, + $user + ); // Run the job queue + JobQueueGroup::destroySingletons(); $jobs = new RunJobs; $jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null ); $jobs->execute(); @@ -32,5 +50,4 @@ class TemplateCategoriesTest extends MediaWikiLangTestCase { , $title->getParentCategories() ); } - } diff --git a/tests/phpunit/includes/TestUser.php b/tests/phpunit/includes/TestUser.php index c4d89455..23e65031 100644 --- a/tests/phpunit/includes/TestUser.php +++ b/tests/phpunit/includes/TestUser.php @@ -1,6 +1,8 @@ <?php -/* Wraps the user object, so we can also retain full access to properties like password if we log in via the API */ +/** + * Wraps the user object, so we can also retain full access to properties like password if we log in via the API + */ class TestUser { public $username; public $password; @@ -8,7 +10,7 @@ class TestUser { public $groups; public $user; - function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { + public function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) { $this->username = $username; $this->realname = $realname; $this->email = $email; @@ -53,6 +55,5 @@ class TestUser { } } $this->user->saveSettings(); - } } diff --git a/tests/phpunit/includes/TimeAdjustTest.php b/tests/phpunit/includes/TimeAdjustTest.php index cd027c5b..0b368c25 100644 --- a/tests/phpunit/includes/TimeAdjustTest.php +++ b/tests/phpunit/includes/TimeAdjustTest.php @@ -1,51 +1,41 @@ <?php class TimeAdjustTest extends MediaWikiLangTestCase { - static $offset; - - public function setUp() { + protected function setUp() { parent::setUp(); - global $wgLocalTZoffset; - self::$offset = $wgLocalTZoffset; $this->iniSet( 'precision', 15 ); } - public function tearDown() { - global $wgLocalTZoffset; - $wgLocalTZoffset = self::$offset; - parent::tearDown(); - } + /** + * Test offset usage for a given Language::userAdjust + * @dataProvider dataUserAdjust + * @covers Language::userAdjust + */ + public function testUserAdjust( $date, $localTZoffset, $expected ) { + global $wgContLang; - # Test offset usage for a given language::userAdjust - function testUserAdjust() { - global $wgLocalTZoffset, $wgContLang; + $this->setMwGlobals( 'wgLocalTZoffset', $localTZoffset ); - $wgContLang = $en = Language::factory( 'en' ); + $this->assertEquals( + strval( $expected ), + strval( $wgContLang->userAdjust( $date, '' ) ), + "User adjust {$date} by {$localTZoffset} minutes should give {$expected}" + ); + } - # Collection of parameters for Language_t_Offset. - # Format: date to be formatted, localTZoffset value, expected date - $userAdjust_tests = array( - array( 20061231235959, 0, 20061231235959 ), - array( 20061231235959, 5, 20070101000459 ), - array( 20061231235959, 15, 20070101001459 ), - array( 20061231235959, 60, 20070101005959 ), - array( 20061231235959, 90, 20070101012959 ), + public static function dataUserAdjust() { + return array( + array( 20061231235959, 0, 20061231235959 ), + array( 20061231235959, 5, 20070101000459 ), + array( 20061231235959, 15, 20070101001459 ), + array( 20061231235959, 60, 20070101005959 ), + array( 20061231235959, 90, 20070101012959 ), array( 20061231235959, 120, 20070101015959 ), array( 20061231235959, 540, 20070101085959 ), - array( 20061231235959, -5, 20061231235459 ), + array( 20061231235959, -5, 20061231235459 ), array( 20061231235959, -30, 20061231232959 ), array( 20061231235959, -60, 20061231225959 ), ); - - foreach ( $userAdjust_tests as $data ) { - $wgLocalTZoffset = $data[1]; - - $this->assertEquals( - strval( $data[2] ), - strval( $en->userAdjust( $data[0], '' ) ), - "User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}" - ); - } } } diff --git a/tests/phpunit/includes/TimestampTest.php b/tests/phpunit/includes/TimestampTest.php index 231228f5..53388392 100644 --- a/tests/phpunit/includes/TimestampTest.php +++ b/tests/phpunit/includes/TimestampTest.php @@ -3,12 +3,20 @@ /** * Tests timestamp parsing and output. */ -class TimestampTest extends MediaWikiTestCase { +class TimestampTest extends MediaWikiLangTestCase { + + protected function setUp() { + parent::setUp(); + + RequestContext::getMain()->setLanguage( Language::factory( 'en' ) ); + } + /** * Test parsing of valid timestamps and outputing to MW format. * @dataProvider provideValidTimestamps + * @covers MWTimestamp::getTimestamp */ - function testValidParse( $format, $original, $expected ) { + public function testValidParse( $format, $original, $expected ) { $timestamp = new MWTimestamp( $original ); $this->assertEquals( $expected, $timestamp->getTimestamp( TS_MW ) ); } @@ -16,42 +24,37 @@ class TimestampTest extends MediaWikiTestCase { /** * Test outputting valid timestamps to different formats. * @dataProvider provideValidTimestamps + * @covers MWTimestamp::getTimestamp */ - function testValidOutput( $format, $expected, $original ) { + public function testValidOutput( $format, $expected, $original ) { $timestamp = new MWTimestamp( $original ); - $this->assertEquals( $expected, (string) $timestamp->getTimestamp( $format ) ); + $this->assertEquals( $expected, (string)$timestamp->getTimestamp( $format ) ); } /** * Test an invalid timestamp. * @expectedException TimestampException + * @covers MWTimestamp */ - function testInvalidParse() { - $timestamp = new MWTimestamp( "This is not a timestamp." ); + public function testInvalidParse() { + new MWTimestamp( "This is not a timestamp." ); } /** * Test requesting an invalid output format. * @expectedException TimestampException + * @covers MWTimestamp::getTimestamp */ - function testInvalidOutput() { + public function testInvalidOutput() { $timestamp = new MWTimestamp( '1343761268' ); $timestamp->getTimestamp( 98 ); } /** - * Test human readable timestamp format. - */ - function testHumanOutput() { - $timestamp = new MWTimestamp( time() - 3600 ); - $this->assertEquals( "1 hour ago", $timestamp->getHumanTimestamp()->toString() ); - } - - /** * Returns a list of valid timestamps in the format: * array( type, timestamp_of_type, timestamp_in_MW ) */ - function provideValidTimestamps() { + public static function provideValidTimestamps() { return array( // Various formats array( TS_UNIX, '1343761268', '20120731190108' ), @@ -63,10 +66,239 @@ class TimestampTest extends MediaWikiTestCase { array( TS_RFC2822, 'Tue, 31 Jul 2012 19:01:08 GMT', '20120731190108' ), array( TS_ORACLE, '31-07-2012 19:01:08.000000', '20120731190108' ), array( TS_POSTGRES, '2012-07-31 19:01:08 GMT', '20120731190108' ), - array( TS_DB2, '2012-07-31 19:01:08', '20120731190108' ), // Some extremes and weird values array( TS_ISO_8601, '9999-12-31T23:59:59Z', '99991231235959' ), array( TS_UNIX, '-62135596801', '00001231235959' ) ); } + + /** + * @dataProvider provideHumanTimestampTests + * @covers MWTimestamp::getHumanTimestamp + */ + public function testHumanTimestamp( + $tsTime, // The timestamp to format + $currentTime, // The time to consider "now" + $timeCorrection, // The time offset to use + $dateFormat, // The date preference to use + $expectedOutput, // The expected output + $desc // Description + ) { + $user = $this->getMock( 'User' ); + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->with( 'timecorrection' ) + ->will( $this->returnValue( $timeCorrection ) ); + + $user->expects( $this->any() ) + ->method( 'getDatePreference' ) + ->will( $this->returnValue( $dateFormat ) ); + + $tsTime = new MWTimestamp( $tsTime ); + $currentTime = new MWTimestamp( $currentTime ); + + $this->assertEquals( + $expectedOutput, + $tsTime->getHumanTimestamp( $currentTime, $user ), + $desc + ); + } + + public static function provideHumanTimestampTests() { + return array( + array( + '20111231170000', + '20120101000000', + 'Offset|0', + 'mdy', + 'Yesterday at 17:00', + '"Yesterday" across years', + ), + array( + '20120717190900', + '20120717190929', + 'Offset|0', + 'mdy', + 'just now', + '"Just now"', + ), + array( + '20120717190900', + '20120717191530', + 'Offset|0', + 'mdy', + '6 minutes ago', + 'X minutes ago', + ), + array( + '20121006173100', + '20121006173200', + 'Offset|0', + 'mdy', + '1 minute ago', + '"1 minute ago"', + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'mdy', + 'June 17', + 'Another month' + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'mdy', + '15:15, January 30, 1991', + 'Different year', + ), + array( + '20120101050000', + '20120101080000', + 'Offset|-360', + 'mdy', + 'Yesterday at 23:00', + '"Yesterday" across years with time correction', + ), + array( + '20120714184300', + '20120716184300', + 'Offset|-420', + 'mdy', + 'Saturday at 11:43', + 'Recent weekday with time correction', + ), + array( + '20120714184300', + '20120715040000', + 'Offset|-420', + 'mdy', + '11:43', + 'Today at another time with time correction', + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'dmy', + '17 June', + 'Another month with dmy' + ), + array( + '20120617190900', + '20120717190900', + 'Offset|0', + 'ISO 8601', + '06-17', + 'Another month with ISO-8601' + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'ISO 8601', + '1991-01-30T15:15:00', + 'Different year with ISO-8601', + ), + ); + } + + /** + * @dataProvider provideRelativeTimestampTests + * @covers MWTimestamp::getRelativeTimestamp + */ + public function testRelativeTimestamp( + $tsTime, // The timestamp to format + $currentTime, // The time to consider "now" + $timeCorrection, // The time offset to use + $dateFormat, // The date preference to use + $expectedOutput, // The expected output + $desc // Description + ) { + $user = $this->getMock( 'User' ); + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->with( 'timecorrection' ) + ->will( $this->returnValue( $timeCorrection ) ); + + $tsTime = new MWTimestamp( $tsTime ); + $currentTime = new MWTimestamp( $currentTime ); + + $this->assertEquals( + $expectedOutput, + $tsTime->getRelativeTimestamp( $currentTime, $user ), + $desc + ); + } + + public static function provideRelativeTimestampTests() { + return array( + array( + '20111231170000', + '20120101000000', + 'Offset|0', + 'mdy', + '7 hours ago', + '"Yesterday" across years', + ), + array( + '20120717190900', + '20120717190929', + 'Offset|0', + 'mdy', + '29 seconds ago', + '"Just now"', + ), + array( + '20120717190900', + '20120717191530', + 'Offset|0', + 'mdy', + '6 minutes and 30 seconds ago', + 'Combination of multiple units', + ), + array( + '20121006173100', + '20121006173200', + 'Offset|0', + 'mdy', + '1 minute ago', + '"1 minute ago"', + ), + array( + '19910130151500', + '20120716193700', + 'Offset|0', + 'mdy', + '2 decades, 1 year, 168 days, 2 hours, 8 minutes and 48 seconds ago', + 'A long time ago', + ), + array( + '20120101050000', + '20120101080000', + 'Offset|-360', + 'mdy', + '3 hours ago', + '"Yesterday" across years with time correction', + ), + array( + '20120714184300', + '20120716184300', + 'Offset|-420', + 'mdy', + '2 days ago', + 'Recent weekday with time correction', + ), + array( + '20120714184300', + '20120715040000', + 'Offset|-420', + 'mdy', + '9 hours and 17 minutes ago', + 'Today at another time with time correction', + ), + ); + } } diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php index aed658ba..3079d73a 100644 --- a/tests/phpunit/includes/TitleMethodsTest.php +++ b/tests/phpunit/includes/TitleMethodsTest.php @@ -1,8 +1,48 @@ <?php +/** + * @group ContentHandler + * @group Database + * + * @note: We don't make assumptions about the main namespace. + * But we do expect the Help namespace to contain Wikitext. + */ class TitleMethodsTest extends MediaWikiTestCase { - public function dataEquals() { + public function setUp() { + global $wgContLang; + + parent::setUp(); + + $this->mergeMwGlobalArrayValue( + 'wgExtraNamespaces', + array( + 12302 => 'TEST-JS', + 12303 => 'TEST-JS_TALK', + ) + ); + + $this->mergeMwGlobalArrayValue( + 'wgNamespaceContentModels', + array( + 12302 => CONTENT_MODEL_JAVASCRIPT, + ) + ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public function tearDown() { + global $wgContLang; + + parent::tearDown(); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public static function provideEquals() { return array( array( 'Main Page', 'Main Page', true ), array( 'Main Page', 'Not The Main Page', false ), @@ -15,7 +55,8 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataEquals + * @dataProvider provideEquals + * @covers Title::equals */ public function testEquals( $titleA, $titleB, $expectedBool ) { $titleA = Title::newFromText( $titleA ); @@ -25,7 +66,7 @@ class TitleMethodsTest extends MediaWikiTestCase { $this->assertEquals( $expectedBool, $titleB->equals( $titleA ) ); } - public function dataInNamespace() { + public static function provideInNamespace() { return array( array( 'Main Page', NS_MAIN, true ), array( 'Main Page', NS_TALK, false ), @@ -39,13 +80,17 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataInNamespace + * @dataProvider provideInNamespace + * @covers Title::inNamespace */ public function testInNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->inNamespace( $ns ) ); } + /** + * @covers Title::inNamespaces + */ public function testInNamespaces() { $mainpage = Title::newFromText( 'Main Page' ); $this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) ); @@ -54,7 +99,7 @@ class TitleMethodsTest extends MediaWikiTestCase { $this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) ); } - public function dataHasSubjectNamespace() { + public static function provideHasSubjectNamespace() { return array( array( 'Main Page', NS_MAIN, true ), array( 'Main Page', NS_TALK, true ), @@ -68,18 +113,62 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataHasSubjectNamespace + * @dataProvider provideHasSubjectNamespace + * @covers Title::hasSubjectNamespace */ public function testHasSubjectNamespace( $title, $ns, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->hasSubjectNamespace( $ns ) ); } - public function dataIsCssOrJsPage() { + public function dataGetContentModel() { + return array( + array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'TEST-JS:Foo', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'TEST-JS_TALK:Foo.js', CONTENT_MODEL_WIKITEXT ), + ); + } + + /** + * @dataProvider dataGetContentModel + * @covers Title::getContentModel + */ + public function testGetContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedModelId, $title->getContentModel() ); + } + + /** + * @dataProvider dataGetContentModel + * @covers Title::hasContentModel + */ + public function testHasContentModel( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertTrue( $title->hasContentModel( $expectedModelId ) ); + } + + public static function provideIsCssOrJsPage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', false ), @@ -92,23 +181,25 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo.JS', false ), array( 'MediaWiki:Foo.CSS', false ), array( 'MediaWiki:Foo.css.xxx', false ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), ); } /** - * @dataProvider dataIsCssOrJsPage + * @dataProvider provideIsCssOrJsPage + * @covers Title::isCssOrJsPage */ public function testIsCssOrJsPage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssOrJsPage() ); } - - public function dataIsCssJsSubpage() { + public static function provideIsCssJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.js', false ), - array( 'Foo/bar.js', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.js', false ), + array( 'Help:Foo/bar.js', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo/bar.js', true ), @@ -119,21 +210,24 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo.js', false ), array( 'User:Foo/bar.JS', false ), array( 'User:Foo/bar.CSS', false ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), ); } /** - * @dataProvider dataIsCssJsSubpage + * @dataProvider provideIsCssJsSubpage + * @covers Title::isCssJsSubpage */ public function testIsCssJsSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssJsSubpage() ); } - public function dataIsCssSubpage() { + public static function provideIsCssSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@ -143,17 +237,18 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataIsCssSubpage + * @dataProvider provideIsCssSubpage + * @covers Title::isCssSubpage */ public function testIsCssSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isCssSubpage() ); } - public function dataIsJsSubpage() { + public static function provideIsJsSubpage() { return array( - array( 'Foo', false ), - array( 'Foo.css', false ), + array( 'Help:Foo', false ), + array( 'Help:Foo.css', false ), array( 'User:Foo', false ), array( 'User:Foo.js', false ), array( 'User:Foo.css', false ), @@ -163,18 +258,19 @@ class TitleMethodsTest extends MediaWikiTestCase { } /** - * @dataProvider dataIsJsSubpage + * @dataProvider provideIsJsSubpage + * @covers Title::isJsSubpage */ public function testIsJsSubpage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isJsSubpage() ); } - public function dataIsWikitextPage() { + public static function provideIsWikitextPage() { return array( - array( 'Foo', true ), - array( 'Foo.js', true ), - array( 'Foo/bar.js', true ), + array( 'Help:Foo', true ), + array( 'Help:Foo.js', true ), + array( 'Help:Foo/bar.js', true ), array( 'User:Foo', true ), array( 'User:Foo.js', true ), array( 'User:Foo/bar.js', false ), @@ -187,15 +283,18 @@ class TitleMethodsTest extends MediaWikiTestCase { array( 'MediaWiki:Foo/bar.css', false ), array( 'User:Foo/bar.JS', true ), array( 'User:Foo/bar.CSS', true ), + array( 'TEST-JS:Foo', false ), + array( 'TEST-JS:Foo.js', false ), + array( 'TEST-JS_TALK:Foo.js', true ), ); } /** - * @dataProvider dataIsWikitextPage + * @dataProvider provideIsWikitextPage + * @covers Title::isWikitextPage */ public function testIsWikitextPage( $title, $expectedBool ) { $title = Title::newFromText( $title ); $this->assertEquals( $expectedBool, $title->isWikitextPage() ); } - } diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php index f62ac5dd..f15c1772 100644 --- a/tests/phpunit/includes/TitlePermissionTest.php +++ b/tests/phpunit/includes/TitlePermissionTest.php @@ -2,33 +2,47 @@ /** * @group Database + * @todo covers tags */ class TitlePermissionTest extends MediaWikiLangTestCase { - protected $title; /** - * @var User + * @var string */ - protected $user, $anonUser, $userUser, $altUser; + protected $userName, $altUserName; /** - * @var string + * @var Title */ - protected $userName, $altUserName; + protected $title; - function setUp() { - global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang; - parent::setUp(); + /** + * @var User + */ + protected $user, $anonUser, $userUser, $altUser; - if(!$wgMemc) { - $wgMemc = new EmptyBagOStuff; - } - $wgContLang = $wgLang = Language::factory( 'en' ); + protected function setUp() { + parent::setUp(); - $this->userName = "Useruser"; - $this->altUserName = "Altuseruser"; - date_default_timezone_set( $wgLocaltimezone ); - $wgLocalTZoffset = date( "Z" ) / 60; + $langObj = Language::factory( 'en' ); + $localZone = 'UTC'; + $localOffset = date( 'Z' ) / 60; + + $this->setMwGlobals( array( + 'wgMemc' => new EmptyBagOStuff, + 'wgContLang' => $langObj, + 'wgLanguageCode' => 'en', + 'wgLang' => $langObj, + 'wgLocaltimezone' => $localZone, + 'wgLocalTZoffset' => $localOffset, + 'wgNamespaceProtection' => array( + NS_MEDIAWIKI => 'editinterface', + ), + ) ); + + $this->userName = 'Useruser'; + $this->altUserName = 'Altuseruser'; + date_default_timezone_set( $localZone ); $this->title = Title::makeTitle( NS_MAIN, "Main Page" ); if ( !isset( $this->userUser ) || !( $this->userUser instanceOf User ) ) { @@ -55,11 +69,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } } - function tearDown() { - parent::tearDown(); - } - - function setUserPerm( $perm ) { + protected function setUserPerm( $perm ) { // Setting member variables is evil!!! if ( is_array( $perm ) ) { @@ -69,11 +79,11 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } } - function setTitle( $ns, $title = "Main_Page" ) { + protected function setTitle( $ns, $title = "Main_Page" ) { $this->title = Title::makeTitle( $ns, $title ); } - function setUser( $userName = null ) { + protected function setUser( $userName = null ) { if ( $userName === 'anon' ) { $this->user = $this->anonUser; } elseif ( $userName === null || $userName === $this->userName ) { @@ -81,12 +91,13 @@ class TitlePermissionTest extends MediaWikiLangTestCase { } else { $this->user = $this->altUser; } - - global $wgUser; - $wgUser = $this->user; } - function testQuickPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testQuickPermissions() { global $wgContLang; $prefix = $wgContLang->getFormattedNsText( NS_PROJECT ); @@ -109,7 +120,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN ); $this->setUserPerm( "createpage" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_MAIN ); $this->setUserPerm( "createtalk" ); @@ -120,7 +131,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_TALK ); $this->setUserPerm( "createtalk" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_TALK ); $this->setUserPerm( "createpage" ); @@ -135,7 +146,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setTitle( NS_MAIN ); $this->setUserPerm( "createpage" ); $res = $this->title->getUserPermissionsErrors( 'create', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_MAIN ); $this->setUserPerm( "createtalk" ); @@ -225,36 +236,41 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->runGroupPermissions( 'move', array( array( 'movenotallowedfile' ), array( 'movenotallowed' ) ), array( array( 'movenotallowedfile' ), array( 'movenologintext' ) ) ); - $this->setTitle( NS_MAIN ); - $this->setUser( 'anon' ); - $this->setUserPerm( "move" ); - $this->runGroupPermissions( 'move', array( ) ); + if ( $this->isWikitextNS( NS_MAIN ) ) { + //NOTE: some content models don't allow moving + // @todo find a Wikitext namespace for testing - $this->setUserPerm( "" ); - $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ), - array( array( 'movenologintext' ) ) ); + $this->setTitle( NS_MAIN ); + $this->setUser( 'anon' ); + $this->setUserPerm( "move" ); + $this->runGroupPermissions( 'move', array() ); - $this->setUser( $this->userName ); - $this->setUserPerm( "" ); - $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) ); + $this->setUserPerm( "" ); + $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ), + array( array( 'movenologintext' ) ) ); - $this->setUserPerm( "move" ); - $this->runGroupPermissions( 'move', array( ) ); + $this->setUser( $this->userName ); + $this->setUserPerm( "" ); + $this->runGroupPermissions( 'move', array( array( 'movenotallowed' ) ) ); - $this->setUser( 'anon' ); - $this->setUserPerm( 'move' ); - $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->setUserPerm( "move" ); + $this->runGroupPermissions( 'move', array() ); - $this->setUserPerm( '' ); - $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( array( 'movenotallowed' ) ), $res ); + $this->setUser( 'anon' ); + $this->setUserPerm( 'move' ); + $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); + $this->assertEquals( array(), $res ); + + $this->setUserPerm( '' ); + $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); + $this->assertEquals( array( array( 'movenotallowed' ) ), $res ); + } $this->setTitle( NS_USER ); $this->setUser( $this->userName ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUserPerm( "move" ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); @@ -263,27 +279,26 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setUser( 'anon' ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setTitle( NS_USER, "User/subpage" ); $this->setUserPerm( array( "move", "move-rootuserpages" ) ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUserPerm( "move" ); $res = $this->title->getUserPermissionsErrors( 'move-target', $this->user ); - $this->assertEquals( array( ), $res ); + $this->assertEquals( array(), $res ); $this->setUser( 'anon' ); $check = array( 'edit' => array( array( array( 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ) ), - array( array( 'badaccess-group0' ) ), - array( ), true ), - 'protect' => array( array( array( 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ), array( 'protect-cantedit' ) ), - array( array( 'badaccess-group0' ), array( 'protect-cantedit' ) ), - array( array( 'protect-cantedit' ) ), false ), - '' => array( array( ), array( ), array( ), true ) ); - global $wgUser; - $wgUser = $this->user; + array( array( 'badaccess-group0' ) ), + array(), true ), + 'protect' => array( array( array( 'badaccess-groups', "[[$prefix:Administrators|Administrators]]", 1 ), array( 'protect-cantedit' ) ), + array( array( 'badaccess-group0' ), array( 'protect-cantedit' ) ), + array( array( 'protect-cantedit' ) ), false ), + '' => array( array(), array(), array(), true ) ); + foreach ( array( "edit", "protect", "" ) as $action ) { $this->setUserPerm( null ); $this->assertEquals( $check[$action][0], @@ -303,18 +318,19 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->setUserPerm( $action ); $this->assertEquals( $check[$action][3], - $this->title->userCan( $action, true ) ); + $this->title->userCan( $action, $this->user, true ) ); $this->assertEquals( $check[$action][3], - $this->title->quickUserCan( $action ) ); - + $this->title->quickUserCan( $action, $this->user ) ); # count( User::getGroupsWithPermissions( $action ) ) < 1 } } - function runGroupPermissions( $action, $result, $result2 = null ) { + protected function runGroupPermissions( $action, $result, $result2 = null ) { global $wgGroupPermissions; - if ( $result2 === null ) $result2 = $result; + if ( $result2 === null ) { + $result2 = $result; + } $wgGroupPermissions['autoconfirmed']['move'] = false; $wgGroupPermissions['user']['move'] = false; @@ -337,185 +353,247 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->assertEquals( $result2, $res ); } - function testSpecialsAndNSPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testSpecialsAndNSPermissions() { + global $wgNamespaceProtection; $this->setUser( $this->userName ); - global $wgUser; - $wgUser = $this->user; $this->setTitle( NS_SPECIAL ); $this->assertEquals( array( array( 'badaccess-group0' ), array( 'ns-specialprotected' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); - $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'execute', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MAIN ); $this->setUserPerm( 'bogus' ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MAIN ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + + $wgNamespaceProtection[NS_USER] = array( 'bogus' ); - global $wgNamespaceProtection; - $wgNamespaceProtection[NS_USER] = array ( 'bogus' ); $this->setTitle( NS_USER ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ), array( 'namespaceprotected', 'User' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MEDIAWIKI ); $this->setUserPerm( 'bogus' ); $this->assertEquals( array( array( 'protectedinterface' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->setTitle( NS_MEDIAWIKI ); $this->setUserPerm( 'bogus' ); $this->assertEquals( array( array( 'protectedinterface' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $wgNamespaceProtection = null; + $this->setUserPerm( 'bogus' ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); $this->setUserPerm( '' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); } - function testCssAndJavascriptPermissions() { + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testCssAndJavascriptPermissions() { $this->setUser( $this->userName ); - global $wgUser; - $wgUser = $this->user; + + $this->setTitle( NS_USER, $this->userName . '/test.js' ); + $this->runCSSandJSPermissions( + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomjsprotected' ) ), + array( array( 'badaccess-group0' ) ) + ); + + $this->setTitle( NS_USER, $this->userName . '/test.css' ); + $this->runCSSandJSPermissions( + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ), array( 'mycustomcssprotected' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/test.js' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), - array( array( 'badaccess-group0' ) ) ); + array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customjsprotected' ) ), + array( array( 'badaccess-group0' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/test.css' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ), array( array( 'badaccess-group0' ) ), - array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ) ); + array( array( 'badaccess-group0' ), array( 'customcssprotected' ) ) + ); $this->setTitle( NS_USER, $this->altUserName . '/tempo' ); $this->runCSSandJSPermissions( array( array( 'badaccess-group0' ) ), array( array( 'badaccess-group0' ) ), - array( array( 'badaccess-group0' ) ) ); + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ) ), + array( array( 'badaccess-group0' ) ) + ); } - function runCSSandJSPermissions( $result0, $result1, $result2 ) { + protected function runCSSandJSPermissions( $result0, $result1, $result2, $result3, $result4 ) { $this->setUserPerm( '' ); $this->assertEquals( $result0, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); - $this->setUserPerm( 'editusercss' ); + $this->setUserPerm( 'editmyusercss' ); $this->assertEquals( $result1, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); - $this->setUserPerm( 'edituserjs' ); + $this->setUserPerm( 'editmyuserjs' ); $this->assertEquals( $result2, - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + + $this->setUserPerm( 'editusercss' ); + $this->assertEquals( $result3, + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + + $this->setUserPerm( 'edituserjs' ); + $this->assertEquals( $result4, + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->setUserPerm( 'editusercssjs' ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->setUserPerm( array( 'edituserjs', 'editusercss' ) ); $this->assertEquals( array( array( 'badaccess-group0' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); } - function testPageRestrictions() { - global $wgUser, $wgContLang; + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testPageRestrictions() { + global $wgContLang; $prefix = $wgContLang->getFormattedNsText( NS_PROJECT ); - $wgUser = $this->user; $this->setTitle( NS_MAIN ); $this->title->mRestrictionsLoaded = true; $this->setUserPerm( "edit" ); $this->title->mRestrictions = array( "bogus" => array( 'bogus', "sysop", "protect", "" ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->assertEquals( true, - $this->title->quickUserCan( 'edit' ) ); + $this->title->quickUserCan( 'edit', $this->user ) ); $this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ), - "bogus" => array( 'bogus', "sysop", "protect", "" ) ); + "bogus" => array( 'bogus', "sysop", "protect", "" ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->setUserPerm( "" ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'badaccess-groups', "*, [[$prefix:Users|Users]]", 2 ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); $this->setUserPerm( array( "edit", "editprotected" ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + $this->assertEquals( array( + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); + $this->title->mCascadeRestriction = true; + $this->setUserPerm( "edit" ); $this->assertEquals( false, - $this->title->quickUserCan( 'bogus' ) ); + $this->title->quickUserCan( 'bogus', $this->user ) ); $this->assertEquals( false, - $this->title->quickUserCan( 'edit' ) ); + $this->title->quickUserCan( 'edit', $this->user ) ); $this->assertEquals( array( array( 'badaccess-group0' ), - array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'bogus', - $this->user ) ); + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), - array( 'protectedpagetext', 'protect' ), - array( 'protectedpagetext', 'protect' ) ), - $this->title->getUserPermissionsErrors( 'edit', - $this->user ) ); + array( 'protectedpagetext', 'editprotected' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); + + $this->setUserPerm( array( "edit", "editprotected" ) ); + $this->assertEquals( false, + $this->title->quickUserCan( 'bogus', $this->user ) ); + $this->assertEquals( false, + $this->title->quickUserCan( 'edit', $this->user ) ); + $this->assertEquals( array( array( 'badaccess-group0' ), + array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'bogus', + $this->user ) ); + $this->assertEquals( array( array( 'protectedpagetext', 'bogus' ), + array( 'protectedpagetext', 'protect' ), + array( 'protectedpagetext', 'protect' ) ), + $this->title->getUserPermissionsErrors( 'edit', + $this->user ) ); } - function testCascadingSourcesRestrictions() { - global $wgUser; - $wgUser = $this->user; + public function testCascadingSourcesRestrictions() { $this->setTitle( NS_MAIN, "test page" ); $this->setUserPerm( array( "edit", "bogus" ) ); @@ -523,22 +601,23 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->mCascadingRestrictions = array( "bogus" => array( 'bogus', "sysop", "protect", "" ) ); $this->assertEquals( false, - $this->title->userCan( 'bogus' ) ); + $this->title->userCan( 'bogus', $this->user ) ); $this->assertEquals( array( array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ), - array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ) ), - $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); + array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ), + array( "cascadeprotected", 2, "* [[:Bogus]]\n* [[:UnBogus]]\n" ) ), + $this->title->getUserPermissionsErrors( 'bogus', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'edit' ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'edit', $this->user ) ); - + $this->title->userCan( 'edit', $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'edit', $this->user ) ); } - function testActionPermissions() { - global $wgUser; - $wgUser = $this->user; - + /** + * @todo This test method should be split up into separate test methods and + * data providers + */ + public function testActionPermissions() { $this->setUserPerm( array( "createpage" ) ); $this->setTitle( NS_MAIN, "test page" ); $this->title->mTitleProtection['pt_create_perm'] = ''; @@ -548,111 +627,114 @@ class TitlePermissionTest extends MediaWikiLangTestCase { $this->title->mCascadeRestriction = false; $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'create' ) ); + $this->title->userCan( 'create', $this->user ) ); $this->title->mTitleProtection['pt_create_perm'] = 'sysop'; $this->setUserPerm( array( 'createpage', 'protect' ) ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); - $this->assertEquals( true, - $this->title->userCan( 'create' ) ); + $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->assertEquals( false, + $this->title->userCan( 'create', $this->user ) ); + $this->setUserPerm( array( 'createpage', 'editprotected' ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->assertEquals( true, + $this->title->userCan( 'create', $this->user ) ); $this->setUserPerm( array( 'createpage' ) ); $this->assertEquals( array( array( 'titleprotected', 'Useruser', 'test' ) ), - $this->title->getUserPermissionsErrors( 'create', $this->user ) ); + $this->title->getUserPermissionsErrors( 'create', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'create' ) ); + $this->title->userCan( 'create', $this->user ) ); $this->setTitle( NS_MEDIA, "test page" ); $this->setUserPerm( array( "move" ) ); $this->assertEquals( false, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->assertEquals( array( array( 'immobile-source-namespace', 'Media' ) ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); - $this->setTitle( NS_MAIN, "test page" ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->setTitle( NS_HELP, "test page" ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->title->mInterwiki = "no"; $this->assertEquals( array( array( 'immobile-source-page' ) ), - $this->title->getUserPermissionsErrors( 'move', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'move' ) ); + $this->title->userCan( 'move', $this->user ) ); $this->setTitle( NS_MEDIA, "test page" ); $this->assertEquals( false, - $this->title->userCan( 'move-target' ) ); + $this->title->userCan( 'move-target', $this->user ) ); $this->assertEquals( array( array( 'immobile-target-namespace', 'Media' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); - $this->setTitle( NS_MAIN, "test page" ); - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->setTitle( NS_HELP, "test page" ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $this->assertEquals( true, - $this->title->userCan( 'move-target' ) ); + $this->title->userCan( 'move-target', $this->user ) ); $this->title->mInterwiki = "no"; $this->assertEquals( array( array( 'immobile-target-page' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $this->assertEquals( false, - $this->title->userCan( 'move-target' ) ); - + $this->title->userCan( 'move-target', $this->user ) ); } - function testUserBlock() { - global $wgUser, $wgEmailConfirmToEdit, $wgEmailAuthentication; + public function testUserBlock() { + global $wgEmailConfirmToEdit, $wgEmailAuthentication; $wgEmailConfirmToEdit = true; $wgEmailAuthentication = true; - $wgUser = $this->user; $this->setUserPerm( array( "createpage", "move" ) ); - $this->setTitle( NS_MAIN, "test page" ); + $this->setTitle( NS_HELP, "test page" ); # $short $this->assertEquals( array( array( 'confirmedittext' ) ), - $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); + $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); $wgEmailConfirmToEdit = false; - $this->assertEquals( true, $this->title->userCan( 'move-target' ) ); + $this->assertEquals( true, $this->title->userCan( 'move-target', $this->user ) ); # $wgEmailConfirmToEdit && !$user->isEmailConfirmed() && $action != 'createaccount' - $this->assertEquals( array( ), - $this->title->getUserPermissionsErrors( 'move-target', - $this->user ) ); + $this->assertEquals( array(), + $this->title->getUserPermissionsErrors( 'move-target', + $this->user ) ); global $wgLang; $prev = time(); $now = time() + 120; $this->user->mBlockedby = $this->user->getId(); $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), - 'no reason given', $prev + 3600, 1, 0 ); + 'no reason given', $prev + 3600, 1, 0 ); $this->user->mBlock->mTimestamp = 0; $this->assertEquals( array( array( 'autoblockedtext', - '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', - 'Useruser', null, 'infinite', '127.0.8.1', - $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ) ), + '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', + 'Useruser', null, 'infinite', '127.0.8.1', + $wgLang->timeanddate( wfTimestamp( TS_MW, $prev ), true ) ) ), $this->title->getUserPermissionsErrors( 'move-target', - $this->user ) ); + $this->user ) ); - $this->assertEquals( false, $this->title->userCan( 'move-target' ) ); + $this->assertEquals( false, $this->title->userCan( 'move-target', $this->user ) ); // quickUserCan should ignore user blocks - $this->assertEquals( true, $this->title->quickUserCan( 'move-target' ) ); + $this->assertEquals( true, $this->title->quickUserCan( 'move-target', $this->user ) ); global $wgLocalTZoffset; $wgLocalTZoffset = -60; $this->user->mBlockedby = $this->user->getName(); - $this->user->mBlock = new Block( '127.0.8.1', 0, 1, 'no reason given', $now, 0, 10 ); + $this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(), + 'no reason given', $now, 0, 10 ); $this->assertEquals( array( array( 'blockedtext', - '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', - 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', - $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ) ), + '[[User:Useruser|Useruser]]', 'no reason given', '127.0.0.1', + 'Useruser', null, '23:00, 31 December 1969', '127.0.8.1', + $wgLang->timeanddate( wfTimestamp( TS_MW, $now ), true ) ) ), $this->title->getUserPermissionsErrors( 'move-target', $this->user ) ); - # $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) # $user->blockedFor() == '' # $user->mBlock->mExpiry == 'infinity' diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php index f61652df..6bfe5453 100644 --- a/tests/phpunit/includes/TitleTest.php +++ b/tests/phpunit/includes/TitleTest.php @@ -1,8 +1,27 @@ <?php +/** + * @group Database + * ^--- needed for language cache stuff + */ class TitleTest extends MediaWikiTestCase { + protected function setUp() { + parent::setUp(); - function testLegalChars() { + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + // User language + 'wgLang' => Language::factory( 'en' ), + 'wgAllowUserJs' => false, + 'wgDefaultLanguageVariant' => false, + ) ); + } + + /** + * @covers Title::legalChars + */ + public function testLegalChars() { $titlechars = Title::legalChars(); foreach ( range( 1, 255 ) as $num ) { @@ -16,12 +35,160 @@ class TitleTest extends MediaWikiTestCase { } /** - * @dataProvider dataBug31100 + * See also mediawiki.Title.test.js + * @covers Title::secureAndSplit + * @todo This method should be split into 2 separate tests each with a provider */ - function testBug31100FixSpecialName( $text, $expectedParam ) { + public function testSecureAndSplit() { + // Valid + foreach ( array( + 'Sandbox', + 'A "B"', + 'A \'B\'', + '.com', + '~', + '"', + '\'', + 'Talk:Sandbox', + 'Talk:Foo:Sandbox', + 'File:Example.svg', + 'File_talk:Example.svg', + 'Foo/.../Sandbox', + 'Sandbox/...', + 'A~~', + // Length is 256 total, but only title part matters + 'Category:' . str_repeat( 'x', 248 ), + str_repeat( 'x', 252 ) + ) as $text ) { + $this->assertInstanceOf( 'Title', Title::newFromText( $text ), "Valid: $text" ); + } + + // Invalid + foreach ( array( + '', + '__ __', + ' __ ', + // Bad characters forbidden regardless of wgLegalTitleChars + 'A [ B', + 'A ] B', + 'A { B', + 'A } B', + 'A < B', + 'A > B', + 'A | B', + // URL encoding + 'A%20B', + 'A%23B', + 'A%2523B', + // XML/HTML character entity references + // Note: Commented out because they are not marked invalid by the PHP test as + // Title::newFromText runs Sanitizer::decodeCharReferencesAndNormalize first. + //'A é B', + //'A é B', + //'A é B', + // Subject of NS_TALK does not roundtrip to NS_MAIN + 'Talk:File:Example.svg', + // Directory navigation + '.', + '..', + './Sandbox', + '../Sandbox', + 'Foo/./Sandbox', + 'Foo/../Sandbox', + 'Sandbox/.', + 'Sandbox/..', + // Tilde + 'A ~~~ Name', + 'A ~~~~ Signature', + 'A ~~~~~ Timestamp', + str_repeat( 'x', 256 ), + // Namespace prefix without actual title + // ':', // bug 54044 + 'Talk:', + 'Category: ', + 'Category: #bar' + ) as $text ) { + $this->assertNull( Title::newFromText( $text ), "Invalid: $text" ); + } + } + + public static function provideConvertByteClassToUnicodeClass() { + return array( + array( + ' %!"$&\'()*,\\-.\\/0-9:;=?@A-Z\\\\^_`a-z~\\x80-\\xFF+', + ' %!"$&\'()*,\\-./0-9:;=?@A-Z\\\\\\^_`a-z~+\\u0080-\\uFFFF', + ), + array( + 'QWERTYf-\\xFF+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\xFD+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTYf-y+', + 'QWERTYf-y+', + ), + array( + 'QWERTYf-\\x80+', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\x80+\\x23', + 'QWERTYf-\\x7F+#\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\x66-\\x80+\\xD3', + 'QWERTYf-\\x7F+\\u0080-\\uFFFF', + ), + array( + '\\\\\\x99', + '\\\\\\u0080-\\uFFFF', + ), + array( + '-\\x99', + '\\-\\u0080-\\uFFFF', + ), + array( + 'QWERTY\\-\\x99', + 'QWERTY\\-\\u0080-\\uFFFF', + ), + array( + '\\\\x99', + '\\\\x99', + ), + array( + 'A-\\x9F', + 'A-\\x7F\\u0080-\\uFFFF', + ), + array( + '\\x66-\\x77QWERTY\\x88-\\x91FXZ', + 'f-wQWERTYFXZ\\u0080-\\uFFFF', + ), + array( + '\\x66-\\x99QWERTY\\xAA-\\xEEFXZ', + 'f-\\x7FQWERTYFXZ\\u0080-\\uFFFF', + ), + ); + } + + /** + * @dataProvider provideConvertByteClassToUnicodeClass + * @covers Title::convertByteClassToUnicodeClass + */ + public function testConvertByteClassToUnicodeClass( $byteClass, $unicodeClass ) { + $this->assertEquals( $unicodeClass, Title::convertByteClassToUnicodeClass( $byteClass ) ); + } + + /** + * @dataProvider provideBug31100 + * @covers Title::fixSpecialName + */ + public function testBug31100FixSpecialName( $text, $expectedParam ) { $title = Title::newFromText( $text ); $fixed = $title->fixSpecialName(); - $stuff = explode( '/', $fixed->getDbKey(), 2 ); + $stuff = explode( '/', $fixed->getDBkey(), 2 ); if ( count( $stuff ) == 2 ) { $par = $stuff[1]; } else { @@ -30,24 +197,25 @@ class TitleTest extends MediaWikiTestCase { $this->assertEquals( $expectedParam, $par, "Bug 31100 regression check: Title->fixSpecialName() should preserve parameter" ); } - function dataBug31100() { + public static function provideBug31100() { return array( array( 'Special:Version', null ), array( 'Special:Version/', '' ), array( 'Special:Version/param', 'param' ), ); } - + /** * Auth-less test of Title::isValidMoveOperation - * + * * @group Database * @param string $source * @param string $target - * @param array|string|true $expected Required error - * @dataProvider dataTestIsValidMoveOperation + * @param array|string|bool $expected Required error + * @dataProvider provideTestIsValidMoveOperation + * @covers Title::isValidMoveOperation */ - function testIsValidMoveOperation( $source, $target, $expected ) { + public function testIsValidMoveOperation( $source, $target, $expected ) { $title = Title::newFromText( $source ); $nt = Title::newFromText( $target ); $errors = $title->isValidMoveOperation( $nt, false ); @@ -60,42 +228,150 @@ class TitleTest extends MediaWikiTestCase { } } } - - function flattenErrorsArray( $errors ) { + + /** + * Provides test parameter values for testIsValidMoveOperation() + */ + public function dataTestIsValidMoveOperation() { + return array( + array( 'Test', 'Test', 'selfmove' ), + array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ) + ); + } + + /** + * Auth-less test of Title::userCan + * + * @param array $whitelistRegexp + * @param string $source + * @param string $action + * @param array|string|bool $expected Required error + * + * @covers Title::checkReadPermissions + * @dataProvider dataWgWhitelistReadRegexp + */ + public function testWgWhitelistReadRegexp( $whitelistRegexp, $source, $action, $expected ) { + // $wgWhitelistReadRegexp must be an array. Since the provided test cases + // usually have only one regex, it is more concise to write the lonely regex + // as a string. Thus we cast to an array() to honor $wgWhitelistReadRegexp + // type requisite. + if ( is_string( $whitelistRegexp ) ) { + $whitelistRegexp = array( $whitelistRegexp ); + } + + $title = Title::newFromDBkey( $source ); + + global $wgGroupPermissions; + $oldPermissions = $wgGroupPermissions; + // Disallow all so we can ensure our regex works + $wgGroupPermissions = array(); + $wgGroupPermissions['*']['read'] = false; + + global $wgWhitelistRead; + $oldWhitelist = $wgWhitelistRead; + // Undo any LocalSettings explicite whitelists so they won't cause a + // failing test to succeed. Set it to some random non sense just + // to make sure we properly test Title::checkReadPermissions() + $wgWhitelistRead = array( 'some random non sense title' ); + + global $wgWhitelistReadRegexp; + $oldWhitelistRegexp = $wgWhitelistReadRegexp; + $wgWhitelistReadRegexp = $whitelistRegexp; + + // Just use $wgUser which in test is a user object for '127.0.0.1' + global $wgUser; + // Invalidate user rights cache to take in account $wgGroupPermissions + // change above. + $wgUser->clearInstanceCache(); + $errors = $title->userCan( $action, $wgUser ); + + // Restore globals + $wgGroupPermissions = $oldPermissions; + $wgWhitelistRead = $oldWhitelist; + $wgWhitelistReadRegexp = $oldWhitelistRegexp; + + if ( is_bool( $expected ) ) { + # Forge the assertion message depending on the assertion expectation + $allowableness = $expected + ? " should be allowed" + : " should NOT be allowed"; + $this->assertEquals( $expected, $errors, "User action '$action' on [[$source]] $allowableness." ); + } else { + $errors = $this->flattenErrorsArray( $errors ); + foreach ( (array)$expected as $error ) { + $this->assertContains( $error, $errors ); + } + } + } + + /** + * Provides test parameter values for testWgWhitelistReadRegexp() + */ + public function dataWgWhitelistReadRegexp() { + $ALLOWED = true; + $DISALLOWED = false; + + return array( + // Everything, if this doesn't work, we're really in trouble + array( '/.*/', 'Main_Page', 'read', $ALLOWED ), + array( '/.*/', 'Main_Page', 'edit', $DISALLOWED ), + + // We validate against the title name, not the db key + array( '/^Main_Page$/', 'Main_Page', 'read', $DISALLOWED ), + // Main page + array( '/^Main/', 'Main_Page', 'read', $ALLOWED ), + array( '/^Main.*/', 'Main_Page', 'read', $ALLOWED ), + // With spaces + array( '/Mic\sCheck/', 'Mic Check', 'read', $ALLOWED ), + // Unicode multibyte + // ...without unicode modifier + array( '/Unicode Test . Yes/', 'Unicode Test Ñ Yes', 'read', $DISALLOWED ), + // ...with unicode modifier + array( '/Unicode Test . Yes/u', 'Unicode Test Ñ Yes', 'read', $ALLOWED ), + // Case insensitive + array( '/MiC ChEcK/', 'mic check', 'read', $DISALLOWED ), + array( '/MiC ChEcK/i', 'mic check', 'read', $ALLOWED ), + + // From DefaultSettings.php: + array( "@^UsEr.*@i", 'User is banned', 'read', $ALLOWED ), + array( "@^UsEr.*@i", 'User:John Doe', 'read', $ALLOWED ), + + // With namespaces: + array( '/^Special:NewPages$/', 'Special:NewPages', 'read', $ALLOWED ), + array( null, 'Special:Newpages', 'read', $DISALLOWED ), + + ); + } + + public function flattenErrorsArray( $errors ) { $result = array(); foreach ( $errors as $error ) { $result[] = $error[0]; } + return $result; } - - function dataTestIsValidMoveOperation() { - return array( + + public static function provideTestIsValidMoveOperation() { + return array( array( 'Test', 'Test', 'selfmove' ), array( 'File:Test.jpg', 'Page', 'imagenocrossnamespace' ) ); } - - + /** - * @dataProvider provideCasesForGetpageviewlanguage + * @dataProvider provideGetPageViewLanguage + * @covers Title::getPageViewLanguage */ - function testGetpageviewlanguage( $expected, $titleText, $contLang, $lang, $variant, $msg='' ) { - // Save globals - global $wgContLang, $wgLang, $wgAllowUserJs, $wgLanguageCode, $wgDefaultLanguageVariant; - $save['wgContLang'] = $wgContLang; - $save['wgLang'] = $wgLang; - $save['wgAllowUserJs'] = $wgAllowUserJs; - $save['wgLanguageCode'] = $wgLanguageCode; - $save['wgDefaultLanguageVariant'] = $wgDefaultLanguageVariant; - - // Setup test environnement: - $wgContLang = Language::factory( $contLang ); - $wgLang = Language::factory( $lang ); - # To test out .js titles: - $wgAllowUserJs = true; + public function testGetPageViewLanguage( $expected, $titleText, $contLang, $lang, $variant, $msg = '' ) { + global $wgLanguageCode, $wgContLang, $wgLang, $wgDefaultLanguageVariant, $wgAllowUserJs; + + // Setup environnement for this test $wgLanguageCode = $contLang; + $wgContLang = Language::factory( $contLang ); + $wgLang = Language::factory( $lang ); $wgDefaultLanguageVariant = $variant; + $wgAllowUserJs = true; $title = Title::newFromText( $titleText ); $this->assertInstanceOf( 'Title', $title, @@ -105,16 +381,9 @@ class TitleTest extends MediaWikiTestCase { $title->getPageViewLanguage()->getCode(), $msg ); - - // Restore globals - $wgContLang = $save['wgContLang']; - $wgLang = $save['wgLang']; - $wgAllowUserJs = $save['wgAllowUserJs']; - $wgLanguageCode = $save['wgLanguageCode']; - $wgDefaultLanguageVariant = $save['wgDefaultLanguageVariant']; } - function provideCasesForGetpageviewlanguage() { + public static function provideGetPageViewLanguage() { # Format: # - expected # - Title name @@ -123,33 +392,94 @@ class TitleTest extends MediaWikiTestCase { # - wgDefaultLanguageVariant # - Optional message return array( - array( 'fr', 'Main_page', 'fr', 'fr', false ), - array( 'es', 'Main_page', 'es', 'zh-tw', false ), - array( 'zh', 'Main_page', 'zh', 'zh-tw', false ), - - array( 'es', 'Main_page', 'es', 'zh-tw', 'zh-cn' ), - array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ), - array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ), - array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ), - - array( 'zh-cn', 'Main_page', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ), - array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ), - array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ), - array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ), - - array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ), - array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ), + array( 'fr', 'Help:I_need_somebody', 'fr', 'fr', false ), + array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', false ), + array( 'zh', 'Help:I_need_somebody', 'zh', 'zh-tw', false ), + + array( 'es', 'Help:I_need_somebody', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About', 'es', 'zh-tw', 'zh-cn' ), + array( 'es', 'MediaWiki:About/', 'es', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'es', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'es', 'zh-tw', 'zh-cn' ), + array( 'zh-cn', 'Help:I_need_somebody', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh', 'MediaWiki:About/', 'zh', 'zh-tw', 'zh-cn' ), + array( 'de', 'MediaWiki:About/de', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-cn', 'MediaWiki:About/zh-cn', 'zh', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'MediaWiki:About/zh-tw', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'MediaWiki:Common.css', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Common.js', 'zh', 'zh-tw', 'zh-cn' ), + array( 'en', 'User:JohnDoe/Monobook.css', 'zh', 'zh-tw', 'zh-cn' ), + + array( 'zh-tw', 'Special:NewPages', 'es', 'zh-tw', 'zh-cn' ), + array( 'zh-tw', 'Special:NewPages', 'zh', 'zh-tw', 'zh-cn' ), + + ); + } + + /** + * @dataProvider provideBaseTitleCases + * @covers Title::getBaseText + */ + public function testGetBaseText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getBaseText(), + $msg + ); + } + + public static function provideBaseTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'John Doe/subOne' ), + array( 'User:Foo/Bar/Baz', 'Foo/Bar' ), + ); + } + + /** + * @dataProvider provideRootTitleCases + * @covers Title::getRootText + */ + public function testGetRootText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getRootText(), + $msg + ); + } + + public static function provideRootTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'John Doe' ), + array( 'User:Foo/Bar/Baz', 'Foo' ), + ); + } + + /** + * @todo Handle $wgNamespacesWithSubpages cases + * @dataProvider provideSubpageTitleCases + * @covers Title::getSubpageText + */ + public function testGetSubpageText( $title, $expected, $msg = '' ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expected, + $title->getSubpageText(), + $msg + ); + } + + public static function provideSubpageTitleCases() { + return array( + # Title, expected base, optional message + array( 'User:John_Doe/subOne/subTwo', 'subTwo' ), + array( 'User:John_Doe/subOne', 'subOne' ), ); } } diff --git a/tests/phpunit/includes/UIDGeneratorTest.php b/tests/phpunit/includes/UIDGeneratorTest.php new file mode 100644 index 00000000..8f78ae51 --- /dev/null +++ b/tests/phpunit/includes/UIDGeneratorTest.php @@ -0,0 +1,98 @@ +<?php + +class UIDGeneratorTest extends MediaWikiTestCase { + + /** + * @dataProvider provider_testTimestampedUID + * @covers UIDGenerator::newTimestampedUID128 + * @covers UIDGenerator::newTimestampedUID88 + */ + public function testTimestampedUID( $method, $digitlen, $bits, $tbits, $hostbits ) { + $id = call_user_func( array( 'UIDGenerator', $method ) ); + $this->assertEquals( true, ctype_digit( $id ), "UID made of digit characters" ); + $this->assertLessThanOrEqual( $digitlen, strlen( $id ), + "UID has the right number of digits" ); + $this->assertLessThanOrEqual( $bits, strlen( wfBaseConvert( $id, 10, 2 ) ), + "UID has the right number of bits" ); + + $ids = array(); + for ( $i = 0; $i < 300; $i++ ) { + $ids[] = call_user_func( array( 'UIDGenerator', $method ) ); + } + + $lastId = array_shift( $ids ); + if ( $hostbits ) { + $lastHost = substr( wfBaseConvert( $lastId, 10, 2, $bits ), -$hostbits ); + } + + $this->assertArrayEquals( array_unique( $ids ), $ids, "All generated IDs are unique." ); + + foreach ( $ids as $id ) { + $id_bin = wfBaseConvert( $id, 10, 2 ); + $lastId_bin = wfBaseConvert( $lastId, 10, 2 ); + + $this->assertGreaterThanOrEqual( + substr( $id_bin, 0, $tbits ), + substr( $lastId_bin, 0, $tbits ), + "New ID timestamp ($id_bin) >= prior one ($lastId_bin)." ); + + if ( $hostbits ) { + $this->assertEquals( + substr( $id_bin, 0, -$hostbits ), + substr( $lastId_bin, 0, -$hostbits ), + "Host ID of ($id_bin) is same as prior one ($lastId_bin)." ); + } + + $lastId = $id; + } + } + + /** + * array( method, length, bits, hostbits ) + * NOTE: When adding a new method name here please update the covers tags for the tests! + */ + public static function provider_testTimestampedUID() { + return array( + array( 'newTimestampedUID128', 39, 128, 46, 48 ), + array( 'newTimestampedUID128', 39, 128, 46, 48 ), + array( 'newTimestampedUID88', 27, 88, 46, 32 ), + ); + } + + /** + * @covers UIDGenerator::newUUIDv4 + */ + public function testUUIDv4() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newUUIDv4(); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$!', $id ), + "UID $id has the right format" ); + } + } + + /** + * @covers UIDGenerator::newRawUUIDv4 + */ + public function testRawUUIDv4() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newRawUUIDv4(); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ), + "UID $id has the right format" ); + } + } + + /** + * @covers UIDGenerator::newRawUUIDv4 + */ + public function testRawUUIDv4QuickRand() { + for ( $i = 0; $i < 100; $i++ ) { + $id = UIDGenerator::newRawUUIDv4( UIDGenerator::QUICK_RAND ); + $this->assertEquals( true, + preg_match( '!^[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}$!', $id ), + "UID $id has the right format" ); + } + } + +} diff --git a/tests/phpunit/includes/UserMailerTest.php b/tests/phpunit/includes/UserMailerTest.php new file mode 100644 index 00000000..278edfaa --- /dev/null +++ b/tests/phpunit/includes/UserMailerTest.php @@ -0,0 +1,14 @@ +<?php + +class UserMailerTest extends MediaWikiLangTestCase { + + /** + * @covers UserMailer::quotedPrintable + */ + public function testQuotedPrintable() { + $this->assertEquals( + "=?UTF-8?Q?=C4=88u=20legebla=3F?=", + UserMailer::quotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); + } + +}
\ No newline at end of file diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php index 7a424aef..ff33e825 100644 --- a/tests/phpunit/includes/UserTest.php +++ b/tests/phpunit/includes/UserTest.php @@ -7,22 +7,25 @@ define( 'NS_UNITTEST_TALK', 5601 ); * @group Database */ class UserTest extends MediaWikiTestCase { - protected $savedGroupPermissions, $savedRevokedPermissions; - /** * @var User */ protected $user; - public function setUp() { + protected function setUp() { parent::setUp(); - $this->savedGroupPermissions = $GLOBALS['wgGroupPermissions']; - $this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions']; + $this->setMwGlobals( array( + 'wgGroupPermissions' => array(), + 'wgRevokePermissions' => array(), + ) ); $this->setUpPermissionGlobals(); - $this->setUpUser(); + + $this->user = new User; + $this->user->addGroup( 'unittesters' ); } + private function setUpPermissionGlobals() { global $wgGroupPermissions, $wgRevokePermissions; @@ -38,23 +41,21 @@ class UserTest extends MediaWikiTestCase { 'writetest' => true, 'modifytest' => true, ); + # Data for regular $wgRevokePermissions test $wgRevokePermissions['formertesters'] = array( 'runtest' => true, ); - } - private function setUpUser() { - $this->user = new User; - $this->user->addGroup( 'unittesters' ); - } - public function tearDown() { - parent::tearDown(); - - $GLOBALS['wgGroupPermissions'] = $this->savedGroupPermissions; - $GLOBALS['wgRevokePermissions'] = $this->savedRevokedPermissions; + # For the options test + $wgGroupPermissions['*'] = array( + 'editmyoptions' => true, + ); } + /** + * @covers User::getGroupPermissions + */ public function testGroupPermissions() { $rights = User::getGroupPermissions( array( 'unittesters' ) ); $this->assertContains( 'runtest', $rights ); @@ -68,6 +69,10 @@ class UserTest extends MediaWikiTestCase { $this->assertContains( 'modifytest', $rights ); $this->assertNotContains( 'nukeworld', $rights ); } + + /** + * @covers User::getGroupPermissions + */ public function testRevokePermissions() { $rights = User::getGroupPermissions( array( 'unittesters', 'formertesters' ) ); $this->assertNotContains( 'runtest', $rights ); @@ -76,6 +81,9 @@ class UserTest extends MediaWikiTestCase { $this->assertNotContains( 'nukeworld', $rights ); } + /** + * @covers User::getRights + */ public function testUserPermissions() { $rights = $this->user->getRights(); $this->assertContains( 'runtest', $rights ); @@ -86,6 +94,7 @@ class UserTest extends MediaWikiTestCase { /** * @dataProvider provideGetGroupsWithPermission + * @covers User::getGroupsWithPermission */ public function testGetGroupsWithPermission( $expected, $right ) { $result = User::getGroupsWithPermission( $right ); @@ -95,7 +104,7 @@ class UserTest extends MediaWikiTestCase { $this->assertEquals( $expected, $result, "Groups with permission $right" ); } - public function provideGetGroupsWithPermission() { + public static function provideGetGroupsWithPermission() { return array( array( array( 'unittesters', 'testwriters' ), @@ -118,25 +127,26 @@ class UserTest extends MediaWikiTestCase { /** * @dataProvider provideUserNames + * @covers User::isValidUserName */ public function testIsValidUserName( $username, $result, $message ) { $this->assertEquals( $this->user->isValidUserName( $username ), $result, $message ); } - public function provideUserNames() { + public static function provideUserNames() { return array( array( '', false, 'Empty string' ), array( ' ', false, 'Blank space' ), array( 'abcd', false, 'Starts with small letter' ), - array( 'Ab/cd', false, 'Contains slash' ), - array( 'Ab cd' , true, 'Whitespace' ), - array( '192.168.1.1', false, 'IP' ), + array( 'Ab/cd', false, 'Contains slash' ), + array( 'Ab cd', true, 'Whitespace' ), + array( '192.168.1.1', false, 'IP' ), array( 'User:Abcd', false, 'Reserved Namespace' ), - array( '12abcd232' , true , 'Starts with Numbers' ), - array( '?abcd' , true, 'Start with ? mark' ), + array( '12abcd232', true, 'Starts with Numbers' ), + array( '?abcd', true, 'Start with ? mark' ), array( '#abcd', false, 'Start with #' ), - array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), - array( 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ), + array( 'Abcdകഖഗഘ', true, ' Mixed scripts' ), + array( 'ജോസ്തോമസ്', false, 'ZWNJ- Format control character' ), array( 'Ab cd', false, ' Ideographic space' ), ); } @@ -168,4 +178,60 @@ class UserTest extends MediaWikiTestCase { 'Each user rights (core/extensions) has a corresponding right- message.' ); } + + /** + * Test User::editCount + * @group medium + * @covers User::getEditCount + */ + public function testEditCount() { + $user = User::newFromName( 'UnitTestUser' ); + $user->loadDefaults(); + $user->addToDatabase(); + + // let the user have a few (3) edits + $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) ); + for ( $i = 0; $i < 3; $i++ ) { + $page->doEdit( (string)$i, 'test', 0, false, $user ); + } + + $user->clearInstanceCache(); + $this->assertEquals( 3, $user->getEditCount(), 'After three edits, the user edit count should be 3' ); + + // increase the edit count and clear the cache + $user->incEditCount(); + + $user->clearInstanceCache(); + $this->assertEquals( 4, $user->getEditCount(), 'After increasing the edit count manually, the user edit count should be 4' ); + } + + /** + * Test changing user options. + * @covers User::setOption + * @covers User::getOption + */ + public function testOptions() { + $user = User::newFromName( 'UnitTestUser' ); + $user->addToDatabase(); + + $user->setOption( 'someoption', 'test' ); + $user->setOption( 'cols', 200 ); + $user->saveSettings(); + + $user = User::newFromName( 'UnitTestUser' ); + $this->assertEquals( 'test', $user->getOption( 'someoption' ) ); + $this->assertEquals( 200, $user->getOption( 'cols' ) ); + } + + /** + * Bug 37963 + * Make sure defaults are loaded when setOption is called. + * @covers User::loadOptions + */ + public function testAnonOptions() { + global $wgDefaultUserOptions; + $this->user->setOption( 'someoption', 'test' ); + $this->assertEquals( $wgDefaultUserOptions['cols'], $this->user->getOption( 'cols' ) ); + $this->assertEquals( 'test', $this->user->getOption( 'someoption' ) ); + } } diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php index 1fc0b4b3..f8ed14b6 100644 --- a/tests/phpunit/includes/WebRequestTest.php +++ b/tests/phpunit/includes/WebRequestTest.php @@ -1,26 +1,34 @@ <?php +/** + * @group WebRequest + */ class WebRequestTest extends MediaWikiTestCase { - static $oldServer; + protected $oldServer; - function setUp() { - self::$oldServer = $_SERVER; + protected function setUp() { + parent::setUp(); + + $this->oldServer = $_SERVER; } - function tearDown() { - $_SERVER = self::$oldServer; + protected function tearDown() { + $_SERVER = $this->oldServer; + + parent::tearDown(); } /** * @dataProvider provideDetectServer + * @covers WebRequest::detectServer */ - function testDetectServer( $expected, $input, $description ) { + public function testDetectServer( $expected, $input, $description ) { $_SERVER = $input; $result = WebRequest::detectServer(); $this->assertEquals( $expected, $result, $description ); } - function provideDetectServer() { + public static function provideDetectServer() { return array( array( 'http://x', @@ -96,18 +104,29 @@ class WebRequestTest extends MediaWikiTestCase { /** * @dataProvider provideGetIP + * @covers WebRequest::getIP */ - function testGetIP( $expected, $input, $squid, $private, $description ) { - global $wgSquidServersNoPurge, $wgUsePrivateIPs; + public function testGetIP( $expected, $input, $squid, $xffList, $private, $description ) { $_SERVER = $input; - $wgSquidServersNoPurge = $squid; - $wgUsePrivateIPs = $private; + $this->setMwGlobals( array( + 'wgSquidServersNoPurge' => $squid, + 'wgUsePrivateIPs' => $private, + 'wgHooks' => array( + 'IsTrustedProxy' => array( + function( &$ip, &$trusted ) use ( $xffList ) { + $trusted = $trusted || in_array( $ip, $xffList ); + return true; + } + ) + ) + ) ); + $request = new WebRequest(); $result = $request->getIP(); $this->assertEquals( $expected, $result, $description ); } - function provideGetIP() { + public static function provideGetIP() { return array( array( '127.0.0.1', @@ -115,6 +134,7 @@ class WebRequestTest extends MediaWikiTestCase { 'REMOTE_ADDR' => '127.0.0.1' ), array(), + array(), false, 'Simple IPv4' ), @@ -124,16 +144,29 @@ class WebRequestTest extends MediaWikiTestCase { 'REMOTE_ADDR' => '::1' ), array(), + array(), false, 'Simple IPv6' ), array( + '12.0.0.1', + array( + 'REMOTE_ADDR' => 'abcd:0001:002:03:4:555:6666:7777', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.1, abcd:0001:002:03:4:555:6666:7777', + ), + array( 'ABCD:1:2:3:4:555:6666:7777' ), + array(), + false, + 'IPv6 normalisation' + ), + array( '12.0.0.3', array( 'REMOTE_ADDR' => '12.0.0.1', 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array(), false, 'With X-Forwaded-For' ), @@ -144,6 +177,7 @@ class WebRequestTest extends MediaWikiTestCase { 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array(), + array(), false, 'With X-Forwaded-For and disallowed server' ), @@ -154,42 +188,101 @@ class WebRequestTest extends MediaWikiTestCase { 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' ), array( '12.0.0.1' ), + array(), false, 'With multiple X-Forwaded-For and only one allowed server' ), array( - '12.0.0.2', + '10.0.0.3', array( 'REMOTE_ADDR' => '12.0.0.2', - 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array(), false, - 'With X-Forwaded-For and private IP' + 'With X-Forwaded-For and private IP (from cache proxy)' ), array( - '10.0.0.3', + '10.0.0.4', array( 'REMOTE_ADDR' => '12.0.0.2', - 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2', '10.0.0.3' ), + array(), + true, + 'With X-Forwaded-For and private IP (allowed)' + ), + array( + '10.0.0.4', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' ), array( '12.0.0.1', '12.0.0.2' ), + array( '10.0.0.3' ), true, 'With X-Forwaded-For and private IP (allowed)' ), + array( + '10.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.4, 10.0.0.3, 12.0.0.2' + ), + array( '12.0.0.1', '12.0.0.2' ), + array( '10.0.0.3' ), + false, + 'With X-Forwaded-For and private IP (disallowed)' + ), + array( + '12.0.0.3', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.1', '12.0.0.2' ), + false, + 'With X-Forwaded-For' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.1', + 'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.1' ), + false, + 'With multiple X-Forwaded-For and only one allowed server' + ), + array( + '12.0.0.2', + array( + 'REMOTE_ADDR' => '12.0.0.2', + 'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2' + ), + array(), + array( '12.0.0.2' ), + false, + 'With X-Forwaded-For and private IP and hook (disallowed)' + ), ); } /** * @expectedException MWException + * @covers WebRequest::getIP */ - function testGetIpLackOfRemoteAddrThrowAnException() { + public function testGetIpLackOfRemoteAddrThrowAnException() { $request = new WebRequest(); # Next call throw an exception about lacking an IP $request->getIP(); } - function languageProvider() { + public static function provideLanguageData() { return array( array( '', array(), 'Empty Accept-Language header' ), array( 'en', array( 'en' => 1 ), 'One language' ), @@ -206,11 +299,12 @@ class WebRequestTest extends MediaWikiTestCase { } /** - * @dataProvider languageProvider + * @dataProvider provideLanguageData + * @covers WebRequest::getAcceptLang */ - function testAcceptLang($acceptLanguageHeader, $expectedLanguages, $description) { + public function testAcceptLang( $acceptLanguageHeader, $expectedLanguages, $description ) { $_SERVER = array( 'HTTP_ACCEPT_LANGUAGE' => $acceptLanguageHeader ); $request = new WebRequest(); - $this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description); + $this->assertSame( $request->getAcceptLang(), $expectedLanguages, $description ); } } diff --git a/tests/phpunit/includes/WikiPageTest.php b/tests/phpunit/includes/WikiPageTest.php index 0e1e1ce8..e0d786b9 100644 --- a/tests/phpunit/includes/WikiPageTest.php +++ b/tests/phpunit/includes/WikiPageTest.php @@ -1,41 +1,45 @@ <?php -/** -* @group Database -* ^--- important, causes temporary tables to be used instead of the real database -* @group medium -**/ +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + * @group medium + **/ class WikiPageTest extends MediaWikiLangTestCase { - var $pages_to_delete; + protected $pages_to_delete; - function __construct( $name = null, array $data = array(), $dataName = '' ) { + function __construct( $name = null, array $data = array(), $dataName = '' ) { parent::__construct( $name, $data, $dataName ); - $this->tablesUsed = array_merge ( $this->tablesUsed, - array( 'page', - 'revision', - 'text', - - 'recentchanges', - 'logging', - - 'page_props', - 'pagelinks', - 'categorylinks', - 'langlinks', - 'externallinks', - 'imagelinks', - 'templatelinks', - 'iwlinks' ) ); + $this->tablesUsed = array_merge( + $this->tablesUsed, + array( 'page', + 'revision', + 'text', + + 'recentchanges', + 'logging', + + 'page_props', + 'pagelinks', + 'categorylinks', + 'langlinks', + 'externallinks', + 'imagelinks', + 'templatelinks', + 'iwlinks' ) ); } - public function setUp() { + protected function setUp() { parent::setUp(); $this->pages_to_delete = array(); + + LinkCache::singleton()->clear(); # avoid cached redirect status, etc } - public function tearDown() { + protected function tearDown() { foreach ( $this->pages_to_delete as $p ) { /* @var $p WikiPage */ @@ -50,8 +54,16 @@ class WikiPageTest extends MediaWikiLangTestCase { parent::tearDown(); } - protected function newPage( $title ) { - if ( is_string( $title ) ) $title = Title::newFromText( $title ); + /** + * @param Title $title + * @param String $model + * @return WikiPage + */ + protected function newPage( $title, $model = null ) { + if ( is_string( $title ) ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + } $p = new WikiPage( $title ); @@ -60,31 +72,114 @@ class WikiPageTest extends MediaWikiLangTestCase { return $p; } + /** + * @param String|Title|WikiPage $page + * @param String $text + * @param int $model + * + * @return WikiPage + */ protected function createPage( $page, $text, $model = null ) { - if ( is_string( $page ) ) $page = Title::newFromText( $page ); - if ( $page instanceof Title ) $page = $this->newPage( $page ); + if ( is_string( $page ) || $page instanceof Title ) { + $page = $this->newPage( $page, $model ); + } - $page->doEdit( $text, "testing", EDIT_NEW ); + $content = ContentHandler::makeContent( $text, $page->getTitle(), $model ); + $page->doEditContent( $content, "testing", EDIT_NEW ); return $page; } + /** + * @covers WikiPage::doEditContent + */ + public function testDoEditContent() { + $page = $this->newPage( "WikiPageTest_testDoEditContent" ); + $title = $page->getTitle(); + + $content = ContentHandler::makeContent( "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat.", + $title, CONTENT_MODEL_WIKITEXT ); + + $page->doEditContent( $content, "[[testing]] 1" ); + + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); + $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); + $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); + + $id = $page->getId(); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $content = ContentHandler::makeContent( "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est.", + $title, CONTENT_MODEL_WIKITEXT ); + + $page->doEditContent( $content, "testing 2" ); + + # ------------------------ + $page = new WikiPage( $title ); + + $retrieved = $page->getContent(); + $this->assertTrue( $content->equals( $retrieved ), 'retrieved content doesn\'t equal original' ); + + # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); + } + + /** + * @covers WikiPage::doEdit + */ public function testDoEdit() { - $title = Title::newFromText( "WikiPageTest_testDoEdit" ); + $this->hideDeprecated( "WikiPage::doEdit" ); + $this->hideDeprecated( "WikiPage::getText" ); + $this->hideDeprecated( "Revision::getText" ); + + //NOTE: assume help namespace will default to wikitext + $title = Title::newFromText( "Help:WikiPageTest_testDoEdit" ); $page = $this->newPage( $title ); $text = "[[Lorem ipsum]] dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat."; - $page->doEdit( $text, "testing 1" ); + $page->doEdit( $text, "[[testing]] 1" ); + $this->assertTrue( $title->getArticleID() > 0, "Title object should have new page id" ); + $this->assertTrue( $page->getId() > 0, "WikiPage should have new page id" ); $this->assertTrue( $title->exists(), "Title object should indicate that the page now exists" ); $this->assertTrue( $page->exists(), "WikiPage object should indicate that the page now exists" ); $id = $page->getId(); # ------------------------ + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'pagelinks', '*', array( 'pl_from' => $id ) ); + $n = $res->numRows(); + $res->free(); + + $this->assertEquals( 1, $n, 'pagelinks should contain one link from the page' ); + + # ------------------------ $page = new WikiPage( $title ); $retrieved = $page->getText(); @@ -92,7 +187,7 @@ class WikiPageTest extends MediaWikiLangTestCase { # ------------------------ $text = "At vero eos et accusam et justo duo [[dolores]] et ea rebum. " - . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; + . "Stet clita kasd [[gubergren]], no sea takimata sanctus est."; $page->doEdit( $text, "testing 2" ); @@ -111,10 +206,16 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' ); } + /** + * @covers WikiPage::doQuickEdit + */ public function testDoQuickEdit() { global $wgUser; - $page = $this->createPage( "WikiPageTest_testDoQuickEdit", "original text" ); + $this->hideDeprecated( "WikiPage::doQuickEdit" ); + + //NOTE: assume help namespace will default to wikitext + $page = $this->createPage( "Help:WikiPageTest_testDoQuickEdit", "original text" ); $text = "quick text"; $page->doQuickEdit( $text, $wgUser, "testing q" ); @@ -124,13 +225,35 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( $text, $page->getText() ); } + /** + * @covers WikiPage::doQuickEditContent + */ + public function testDoQuickEditContent() { + global $wgUser; + + $page = $this->createPage( "WikiPageTest_testDoQuickEditContent", "original text", CONTENT_MODEL_WIKITEXT ); + + $content = ContentHandler::makeContent( "quick text", $page->getTitle(), CONTENT_MODEL_WIKITEXT ); + $page->doQuickEditContent( $content, $wgUser, "testing q" ); + + # --------------------- + $page = new WikiPage( $page->getTitle() ); + $this->assertTrue( $content->equals( $page->getContent() ) ); + } + + /** + * @covers WikiPage::doDeleteArticle + */ public function testDoDeleteArticle() { - $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT ); $id = $page->getId(); $page->doDeleteArticle( "testing deletion" ); + $this->assertFalse( $page->getTitle()->getArticleID() > 0, "Title object should now have page id 0" ); + $this->assertFalse( $page->getId() > 0, "WikiPage should now have page id 0" ); $this->assertFalse( $page->exists(), "WikiPage::exists should return false after page was deleted" ); + $this->assertNull( $page->getContent(), "WikiPage::getContent should return null after page was deleted" ); $this->assertFalse( $page->getText(), "WikiPage::getText should return false after page was deleted" ); $t = Title::newFromText( $page->getTitle()->getPrefixedText() ); @@ -145,8 +268,11 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); } + /** + * @covers WikiPage::doDeleteUpdates + */ public function testDoDeleteUpdates() { - $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo" ); + $page = $this->createPage( "WikiPageTest_testDoDeleteArticle", "[[original text]] foo", CONTENT_MODEL_WIKITEXT ); $id = $page->getId(); $page->doDeleteUpdates( $id ); @@ -160,6 +286,9 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertEquals( 0, $n, 'pagelinks should contain no more links from the page' ); } + /** + * @covers WikiPage::getRevision + */ public function testGetRevision() { $page = $this->newPage( "WikiPageTest_testGetRevision" ); @@ -167,47 +296,107 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertNull( $rev ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $rev = $page->getRevision(); $this->assertEquals( $page->getLatest(), $rev->getId() ); - $this->assertEquals( "some text", $rev->getText() ); + $this->assertEquals( "some text", $rev->getContent()->getNativeData() ); } + /** + * @covers WikiPage::getContent + */ + public function testGetContent() { + $page = $this->newPage( "WikiPageTest_testGetContent" ); + + $content = $page->getContent(); + $this->assertNull( $content ); + + # ----------------- + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); + + $content = $page->getContent(); + $this->assertEquals( "some text", $content->getNativeData() ); + } + + /** + * @covers WikiPage::getText + */ public function testGetText() { + $this->hideDeprecated( "WikiPage::getText" ); + $page = $this->newPage( "WikiPageTest_testGetText" ); $text = $page->getText(); $this->assertFalse( $text ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $text = $page->getText(); $this->assertEquals( "some text", $text ); } + /** + * @covers WikiPage::getRawText + */ public function testGetRawText() { + $this->hideDeprecated( "WikiPage::getRawText" ); + $page = $this->newPage( "WikiPageTest_testGetRawText" ); $text = $page->getRawText(); $this->assertFalse( $text ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $text = $page->getRawText(); $this->assertEquals( "some text", $text ); } - + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + global $wgContentHandlerUseDB; + + if ( !$wgContentHandlerUseDB ) { + $this->markTestSkipped( '$wgContentHandlerUseDB is disabled' ); + } + + $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'JavaScriptContentHandler', get_class( $page->getContentHandler() ) ); + } + + /** + * @covers WikiPage::exists + */ public function testExists() { $page = $this->newPage( "WikiPageTest_testExists" ); $this->assertFalse( $page->exists() ); # ----------------- - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $this->assertTrue( $page->exists() ); $page = new WikiPage( $page->getTitle() ); @@ -221,7 +410,7 @@ class WikiPageTest extends MediaWikiLangTestCase { $this->assertFalse( $page->exists() ); } - public function dataHasViewableContent() { + public static function provideHasViewableContent() { return array( array( 'WikiPageTest_testHasViewableContent', false, true ), array( 'Special:WikiPageTest_testHasViewableContent', false ), @@ -232,14 +421,15 @@ class WikiPageTest extends MediaWikiLangTestCase { } /** - * @dataProvider dataHasViewableContent + * @dataProvider provideHasViewableContent + * @covers WikiPage::hasViewableContent */ public function testHasViewableContent( $title, $viewable, $create = false ) { $page = $this->newPage( $title ); $this->assertEquals( $viewable, $page->hasViewableContent() ); if ( $create ) { - $this->createPage( $page, "some text" ); + $this->createPage( $page, "some text", CONTENT_MODEL_WIKITEXT ); $this->assertTrue( $page->hasViewableContent() ); $page = new WikiPage( $page->getTitle() ); @@ -247,18 +437,23 @@ class WikiPageTest extends MediaWikiLangTestCase { } } - public function dataGetRedirectTarget() { + public static function provideGetRedirectTarget() { return array( - array( 'WikiPageTest_testGetRedirectTarget_1', "hello world", null ), - array( 'WikiPageTest_testGetRedirectTarget_2', "#REDIRECT [[hello world]]", "Hello world" ), + array( 'WikiPageTest_testGetRedirectTarget_1', CONTENT_MODEL_WIKITEXT, "hello world", null ), + array( 'WikiPageTest_testGetRedirectTarget_2', CONTENT_MODEL_WIKITEXT, "#REDIRECT [[hello world]]", "Hello world" ), ); } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::getRedirectTarget */ - public function testGetRedirectTarget( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testGetRedirectTarget( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); + + # sanity check, because this test seems to fail for no reason for some people. + $c = $page->getContent(); + $this->assertEquals( 'WikitextContent', get_class( $c ) ); # now, test the actual redirect $t = $page->getRedirectTarget(); @@ -266,143 +461,169 @@ class WikiPageTest extends MediaWikiLangTestCase { } /** - * @dataProvider dataGetRedirectTarget + * @dataProvider provideGetRedirectTarget + * @covers WikiPage::isRedirect */ - public function testIsRedirect( $title, $text, $target ) { - $page = $this->createPage( $title, $text ); + public function testIsRedirect( $title, $model, $text, $target ) { + $page = $this->createPage( $title, $text, $model ); $this->assertEquals( !is_null( $target ), $page->isRedirect() ); } - public function dataIsCountable() { + public static function provideIsCountable() { return array( // any array( 'WikiPageTest_testIsCountable', - '', - 'any', - true + CONTENT_MODEL_WIKITEXT, + '', + 'any', + true ), array( 'WikiPageTest_testIsCountable', - 'Foo', - 'any', - true + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + true ), // comma array( 'WikiPageTest_testIsCountable', - 'Foo', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'comma', + false ), array( 'WikiPageTest_testIsCountable', - 'Foo, bar', - 'comma', - true + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + true ), // link array( 'WikiPageTest_testIsCountable', - 'Foo', - 'link', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'link', + false ), array( 'WikiPageTest_testIsCountable', - 'Foo [[bar]]', - 'link', - true + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + true ), // redirects array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'any', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'any', + false ), array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'comma', + false ), array( 'WikiPageTest_testIsCountable', - '#REDIRECT [[bar]]', - 'link', - false + CONTENT_MODEL_WIKITEXT, + '#REDIRECT [[bar]]', + 'link', + false ), // not a content namespace array( 'Talk:WikiPageTest_testIsCountable', - 'Foo', - 'any', - false + CONTENT_MODEL_WIKITEXT, + 'Foo', + 'any', + false ), array( 'Talk:WikiPageTest_testIsCountable', - 'Foo, bar', - 'comma', - false + CONTENT_MODEL_WIKITEXT, + 'Foo, bar', + 'comma', + false ), array( 'Talk:WikiPageTest_testIsCountable', - 'Foo [[bar]]', - 'link', - false + CONTENT_MODEL_WIKITEXT, + 'Foo [[bar]]', + 'link', + false ), // not a content namespace, different model array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo', - 'any', - false + null, + 'Foo', + 'any', + false ), array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo, bar', - 'comma', - false + null, + 'Foo, bar', + 'comma', + false ), array( 'MediaWiki:WikiPageTest_testIsCountable.js', - 'Foo [[bar]]', - 'link', - false + null, + 'Foo [[bar]]', + 'link', + false ), ); } /** - * @dataProvider dataIsCountable + * @dataProvider provideIsCountable + * @covers WikiPage::isCountable */ - public function testIsCountable( $title, $text, $mode, $expected ) { - global $wgArticleCountMethod; + public function testIsCountable( $title, $model, $text, $mode, $expected ) { + global $wgContentHandlerUseDB; - $old = $wgArticleCountMethod; - $wgArticleCountMethod = $mode; + $this->setMwGlobals( 'wgArticleCountMethod', $mode ); + + $title = Title::newFromText( $title ); + + if ( !$wgContentHandlerUseDB && $model && ContentHandler::getDefaultModelFor( $title ) != $model ) { + $this->markTestSkipped( "Can not use non-default content model $model for " + . $title->getPrefixedDBkey() . " with \$wgContentHandlerUseDB disabled." ); + } - $page = $this->createPage( $title, $text ); - $editInfo = $page->prepareTextForEdit( $page->getText() ); + $page = $this->createPage( $title, $text, $model ); + $hasLinks = wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1, + array( 'pl_from' => $page->getId() ), __METHOD__ ); + + $editInfo = $page->prepareContentForEdit( $page->getContent() ); $v = $page->isCountable(); $w = $page->isCountable( $editInfo ); - $wgArticleCountMethod = $old; $this->assertEquals( $expected, $v, "isCountable( null ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); $this->assertEquals( $expected, $w, "isCountable( \$editInfo ) returned unexpected value " . var_export( $v, true ) - . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + . " instead of " . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); } - public function dataGetParserOutput() { + public static function provideGetParserOutput() { return array( - array("hello ''world''\n", "<p>hello <i>world</i></p>"), - // @todo: more...? + array( CONTENT_MODEL_WIKITEXT, "hello ''world''\n", "<p>hello <i>world</i></p>" ), + // @todo more...? ); } /** - * @dataProvider dataGetParserOutput + * @dataProvider provideGetParserOutput + * @covers WikiPage::getParserOutput */ - public function testGetParserOutput( $text, $expectedHtml ) { - $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text ); + public function testGetParserOutput( $model, $text, $expectedHtml ) { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', $text, $model ); - $opt = new ParserOptions(); + $opt = $page->makeParserOptions( 'canonical' ); $po = $page->getParserOutput( $opt ); $text = $po->getText(); @@ -410,9 +631,39 @@ class WikiPageTest extends MediaWikiLangTestCase { $text = preg_replace( '!\s*(</p>)!sm', '\1', $text ); # don't let tidy confuse us $this->assertEquals( $expectedHtml, $text ); + return $po; } + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_nonexisting() { + static $count = 0; + $count++; + + $page = new WikiPage( new Title( "WikiPageTest_testGetParserOutput_nonexisting_$count" ) ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt ); + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing pages." ); + } + + /** + * @covers WikiPage::getParserOutput + */ + public function testGetParserOutput_badrev() { + $page = $this->createPage( 'WikiPageTest_testGetParserOutput', "dummy", CONTENT_MODEL_WIKITEXT ); + + $opt = new ParserOptions(); + $po = $page->getParserOutput( $opt, $page->getLatest() + 1234 ); + + // @todo would be neat to also test deleted revision + + $this->assertFalse( $po, "getParserOutput() shall return false for non-existing revisions." ); + } + static $sections = "Intro @@ -429,129 +680,147 @@ more stuff public function dataReplaceSection() { + //NOTE: assume the Help namespace to contain wikitext return array( - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "0", - "No more", - null, - trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "", - "No more", - null, - "No more" - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "2", - "== TEST ==\nmore fun", - null, - trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikiPageTest::$sections ) ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "8", - "No more", - null, - trim( WikiPageTest::$sections ) - ), - array( 'WikiPageTest_testReplaceSection', - WikiPageTest::$sections, - "new", - "No more", - "New", - trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "", + "No more", + null, + "No more" + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', + "== TEST ==\nmore fun\n\n== foo ==", + WikiPageTest::$sections ) ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "8", + "No more", + null, + trim( WikiPageTest::$sections ) + ), + array( 'Help:WikiPageTest_testReplaceSection', + CONTENT_MODEL_WIKITEXT, + WikiPageTest::$sections, + "new", + "No more", + "New", + trim( WikiPageTest::$sections ) . "\n\n== New ==\n\nNo more" ), ); } /** * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSection */ - public function testReplaceSection( $title, $text, $section, $with, $sectionTitle, $expected ) { - $page = $this->createPage( $title, $text ); + public function testReplaceSection( $title, $model, $text, $section, $with, $sectionTitle, $expected ) { + $this->hideDeprecated( "WikiPage::replaceSection" ); + + $page = $this->createPage( $title, $text, $model ); $text = $page->replaceSection( $section, $with, $sectionTitle ); $text = trim( $text ); $this->assertEquals( $expected, $text ); } - /* @todo FIXME: fix this! - public function testGetUndoText() { - global $wgDiff3; + /** + * @dataProvider dataReplaceSection + * @covers WikiPage::replaceSectionContent + */ + public function testReplaceSectionContent( $title, $model, $text, $section, $with, $sectionTitle, $expected ) { + $page = $this->createPage( $title, $text, $model ); - wfSuppressWarnings(); - $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 ); - wfRestoreWarnings(); + $content = ContentHandler::makeContent( $with, $page->getTitle(), $page->getContentModel() ); + $c = $page->replaceSectionContent( $section, $content, $sectionTitle ); - if( !$haveDiff3 ) { - $this->markTestSkipped( "diff3 not installed or not found" ); - return; - } + $this->assertEquals( $expected, is_null( $c ) ? null : trim( $c->getNativeData() ) ); + } - $text = "one"; - $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); - $rev1 = $page->getRevision(); + /* @todo FIXME: fix this! + public function testGetUndoText() { + $this->checkHasDiff3(); - $text .= "\n\ntwo"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two"); - $rev2 = $page->getRevision(); + $text = "one"; + $page = $this->createPage( "WikiPageTest_testGetUndoText", $text ); + $rev1 = $page->getRevision(); - $text .= "\n\nthree"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section three"); - $rev3 = $page->getRevision(); + $text .= "\n\ntwo"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section two"); + $rev2 = $page->getRevision(); - $text .= "\n\nfour"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section four"); - $rev4 = $page->getRevision(); + $text .= "\n\nthree"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section three"); + $rev3 = $page->getRevision(); - $text .= "\n\nfive"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section five"); - $rev5 = $page->getRevision(); + $text .= "\n\nfour"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section four"); + $rev4 = $page->getRevision(); - $text .= "\n\nsix"; - $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section six"); - $rev6 = $page->getRevision(); + $text .= "\n\nfive"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section five"); + $rev5 = $page->getRevision(); - $undo6 = $page->getUndoText( $rev6 ); - if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); - $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); + $text .= "\n\nsix"; + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), "adding section six"); + $rev6 = $page->getRevision(); - $undo3 = $page->getUndoText( $rev4, $rev2 ); - if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); - $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + $undo6 = $page->getUndoText( $rev6 ); + if ( $undo6 === false ) $this->fail( "getUndoText failed for rev6" ); + $this->assertEquals( "one\n\ntwo\n\nthree\n\nfour\n\nfive", $undo6 ); - $undo2 = $page->getUndoText( $rev2 ); - if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); - $this->assertEquals( "one\n\nfive", $undo2 ); + $undo3 = $page->getUndoText( $rev4, $rev2 ); + if ( $undo3 === false ) $this->fail( "getUndoText failed for rev4..rev2" ); + $this->assertEquals( "one\n\ntwo\n\nfive", $undo3 ); + + $undo2 = $page->getUndoText( $rev2 ); + if ( $undo2 === false ) $this->fail( "getUndoText failed for rev2" ); + $this->assertEquals( "one\n\nfive", $undo2 ); } - */ + */ /** * @todo FIXME: this is a better rollback test than the one below, but it keeps failing in jenkins for some reason. */ public function broken_testDoRollback() { $admin = new User(); - $admin->setName("Admin"); + $admin->setName( "Admin" ); $text = "one"; $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "section one", EDIT_NEW, false, $admin ); $user1 = new User(); $user1->setName( "127.0.1.11" ); $text .= "\n\ntwo"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section two", 0, false, $user1 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section two", 0, false, $user1 ); $user2 = new User(); $user2->setName( "127.0.2.13" ); $text .= "\n\nthree"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section three", 0, false, $user2 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), + "adding section three", 0, false, $user2 ); # we are having issues with doRollback spuriously failing. apparently the last revision somehow goes missing # or not committed under some circumstances. so, make sure the last revision has the right user name. @@ -578,27 +847,31 @@ more stuff } $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); - $this->assertEquals( "one\n\ntwo", $page->getText() ); + $this->assertEquals( $rev2->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one\n\ntwo", $page->getContent()->getNativeData() ); } /** * @todo FIXME: the above rollback test is better, but it keeps failing in jenkins for some reason. + * @covers WikiPage::doRollback */ public function testDoRollback() { $admin = new User(); - $admin->setName("Admin"); + $admin->setName( "Admin" ); $text = "one"; $page = $this->newPage( "WikiPageTest_testDoRollback" ); - $page->doEdit( $text, "section one", EDIT_NEW, false, $admin ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "section one", EDIT_NEW, false, $admin ); $rev1 = $page->getRevision(); $user1 = new User(); $user1->setName( "127.0.1.11" ); $text .= "\n\ntwo"; $page = new WikiPage( $page->getTitle() ); - $page->doEdit( $text, "adding section two", 0, false, $user1 ); + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle(), CONTENT_MODEL_WIKITEXT ), + "adding section two", 0, false, $user1 ); # now, try the rollback $admin->addGroup( "sysop" ); #XXX: make the test user a sysop... @@ -610,11 +883,12 @@ more stuff } $page = new WikiPage( $page->getTitle() ); - $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), "rollback did not revert to the correct revision" ); - $this->assertEquals( "one", $page->getText() ); + $this->assertEquals( $rev1->getSha1(), $page->getRevision()->getSha1(), + "rollback did not revert to the correct revision" ); + $this->assertEquals( "one", $page->getContent()->getNativeData() ); } - public function dataGetAutosummary( ) { + public static function provideGetAutosummary() { return array( array( 'Hello there, world!', @@ -656,17 +930,21 @@ more stuff } /** - * @dataProvider dataGetAutoSummary + * @dataProvider provideGetAutoSummary + * @covers WikiPage::getAutosummary */ public function testGetAutosummary( $old, $new, $flags, $expected ) { + $this->hideDeprecated( "WikiPage::getAutosummary" ); + $page = $this->newPage( "WikiPageTest_testGetAutosummary" ); $summary = $page->getAutosummary( $old, $new, $flags ); - $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + $this->assertTrue( (bool)preg_match( $expected, $summary ), + "Autosummary didn't match expected pattern $expected: $summary" ); } - public function dataGetAutoDeleteReason( ) { + public static function provideGetAutoDeleteReason() { return array( array( array(), @@ -703,10 +981,10 @@ more stuff array( array( array( "first edit: " - . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " - . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " - . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " - . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ), + . "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam " + . " nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. " + . "At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea " + . "takimata sanctus est Lorem ipsum dolor sit amet.'", null ), ), '/first edit:.*\.\.\."/', false @@ -725,60 +1003,72 @@ more stuff } /** - * @dataProvider dataGetAutoDeleteReason + * @dataProvider provideGetAutoDeleteReason + * @covers WikiPage::getAutoDeleteReason */ public function testGetAutoDeleteReason( $edits, $expectedResult, $expectedHistory ) { global $wgUser; - $page = $this->newPage( "WikiPageTest_testGetAutoDeleteReason" ); + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testGetAutoDeleteReason" ); $c = 1; foreach ( $edits as $edit ) { $user = new User(); - if ( !empty( $edit[1] ) ) $user->setName( $edit[1] ); - else $user = $wgUser; + if ( !empty( $edit[1] ) ) { + $user->setName( $edit[1] ); + } else { + $user = $wgUser; + } + + $content = ContentHandler::makeContent( $edit[0], $page->getTitle(), $page->getContentModel() ); - $page->doEdit( $edit[0], "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); + $page->doEditContent( $content, "test edit $c", $c < 2 ? EDIT_NEW : 0, false, $user ); $c += 1; } $reason = $page->getAutoDeleteReason( $hasHistory ); - if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) $this->assertEquals( $expectedResult, $reason ); - else $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), "Autosummary didn't match expected pattern $expectedResult: $reason" ); + if ( is_bool( $expectedResult ) || is_null( $expectedResult ) ) { + $this->assertEquals( $expectedResult, $reason ); + } else { + $this->assertTrue( (bool)preg_match( $expectedResult, $reason ), + "Autosummary didn't match expected pattern $expectedResult: $reason" ); + } - $this->assertEquals( $expectedHistory, $hasHistory, "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); + $this->assertEquals( $expectedHistory, $hasHistory, + "expected \$hasHistory to be " . var_export( $expectedHistory, true ) ); $page->doDeleteArticle( "done" ); } - public function dataPreSaveTransform() { + public static function providePreSaveTransform() { return array( array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", ), array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', - 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', ), ); } /** - * @dataProvider dataPreSaveTransform + * @dataProvider providePreSaveTransform + * @covers WikiPage::preSaveTransform */ public function testPreSaveTransform( $text, $expected ) { $this->hideDeprecated( 'WikiPage::preSaveTransform' ); $user = new User(); - $user->setName("127.0.0.1"); + $user->setName( "127.0.0.1" ); - $page = $this->newPage( "WikiPageTest_testPreloadTransform" ); + //NOTE: assume Help namespace to contain wikitext + $page = $this->newPage( "Help:WikiPageTest_testPreloadTransform" ); $text = $page->preSaveTransform( $text, $user ); $this->assertEquals( $expected, $text ); } - } - diff --git a/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php b/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php new file mode 100644 index 00000000..2a723e85 --- /dev/null +++ b/tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php @@ -0,0 +1,53 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- important, causes temporary tables to be used instead of the real database + */ +class WikiPageTest_ContentHandlerUseDB extends WikiPageTest { + + protected function setUp() { + parent::setUp(); + $this->setMwGlobals( 'wgContentHandlerUseDB', false ); + + $dbw = wfGetDB( DB_MASTER ); + + $page_table = $dbw->tableName( 'page' ); + $revision_table = $dbw->tableName( 'revision' ); + $archive_table = $dbw->tableName( 'archive' ); + + if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) { + $dbw->query( "alter table $page_table drop column page_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_model" ); + $dbw->query( "alter table $revision_table drop column rev_content_format" ); + $dbw->query( "alter table $archive_table drop column ar_content_model" ); + $dbw->query( "alter table $archive_table drop column ar_content_format" ); + } + } + + /** + * @covers WikiPage::getContentModel + */ + public function testGetContentModel() { + $page = $this->createPage( "WikiPageTest_testGetContentModel", "some text", CONTENT_MODEL_JAVASCRIPT ); + + $page = new WikiPage( $page->getTitle() ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $page->getContentModel() ); + } + + /** + * @covers WikiPage::getContentHandler + */ + public function testGetContentHandler() { + $page = $this->createPage( "WikiPageTest_testGetContentHandler", "some text", CONTENT_MODEL_JAVASCRIPT ); + + // NOTE: since the content model is not recorded in the database, + // we expect to get the default, namely CONTENT_MODEL_WIKITEXT + $page = new WikiPage( $page->getTitle() ); + $this->assertEquals( 'WikitextContentHandler', get_class( $page->getContentHandler() ) ); + } +} diff --git a/tests/phpunit/includes/XmlJsTest.php b/tests/phpunit/includes/XmlJsTest.php index c5b411fb..161468e2 100644 --- a/tests/phpunit/includes/XmlJsTest.php +++ b/tests/phpunit/includes/XmlJsTest.php @@ -1,9 +1,24 @@ <?php + +/** + * @group Xml + */ class XmlJs extends MediaWikiTestCase { - public function testConstruction() { - $obj = new XmlJsCode( null ); - $this->assertNull( $obj->value ); - $obj = new XmlJsCode( '' ); - $this->assertSame( $obj->value, '' ); + + /** + * @covers XmlJsCode::__construct + * @dataProvider provideConstruction + */ + public function testConstruction( $value ) { + $obj = new XmlJsCode( $value ); + $this->assertEquals( $value, $obj->value ); } + + public function provideConstruction(){ + return array( + array( null ), + array( '' ), + ); + } + } diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php index 2407c151..56d28b54 100644 --- a/tests/phpunit/includes/XmlSelectTest.php +++ b/tests/phpunit/includes/XmlSelectTest.php @@ -1,18 +1,31 @@ <?php -// TODO +/** + * @group Xml + */ class XmlSelectTest extends MediaWikiTestCase { + + /** + * @var XmlSelect + */ protected $select; protected function setUp() { + parent::setUp(); + $this->setMwGlobals( array( + 'wgWellFormedXml' => true, + ) ); $this->select = new XmlSelect(); } + protected function tearDown() { + parent::tearDown(); $this->select = null; } - ### START OF TESTS ### - + /** + * @covers XmlSelect::__construct + */ public function testConstructWithoutParameters() { $this->assertEquals( '<select></select>', $this->select->getHTML() ); } @@ -20,6 +33,7 @@ class XmlSelectTest extends MediaWikiTestCase { /** * Parameters are $name (false), $id (false), $default (false) * @dataProvider provideConstructionParameters + * @covers XmlSelect::__construct */ public function testConstructParameters( $name, $id, $default, $expected ) { $this->select = new XmlSelect( $name, $id, $default ); @@ -33,9 +47,8 @@ class XmlSelectTest extends MediaWikiTestCase { * - $id (default: false) * - $default (default: false) * Provides a fourth parameters representing the expected HTML output - * */ - public function provideConstructionParameters() { + public static function provideConstructionParameters() { return array( /** * Values are set following a 3-bit Gray code where two successive @@ -43,51 +56,68 @@ class XmlSelectTest extends MediaWikiTestCase { * See http://en.wikipedia.org/wiki/Gray_code */ # $name $id $default - array( false , false, false, '<select></select>' ), - array( false , false, 'foo', '<select></select>' ), - array( false , 'id' , 'foo', '<select id="id"></select>' ), - array( false , 'id' , false, '<select id="id"></select>' ), - array( 'name', 'id' , false, '<select name="name" id="id"></select>' ), - array( 'name', 'id' , 'foo', '<select name="name" id="id"></select>' ), - array( 'name', false, 'foo', '<select name="name"></select>' ), - array( 'name', false, false, '<select name="name"></select>' ), + array( false, false, false, '<select></select>' ), + array( false, false, 'foo', '<select></select>' ), + array( false, 'id', 'foo', '<select id="id"></select>' ), + array( false, 'id', false, '<select id="id"></select>' ), + array( 'name', 'id', false, '<select name="name" id="id"></select>' ), + array( 'name', 'id', 'foo', '<select name="name" id="id"></select>' ), + array( 'name', false, 'foo', '<select name="name"></select>' ), + array( 'name', false, false, '<select name="name"></select>' ), ); } - # Begin XmlSelect::addOption() similar to Xml::option + /** + * @covers XmlSelect::addOption + */ public function testAddOption() { $this->select->addOption( 'foo' ); $this->assertEquals( '<select><option value="foo">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithDefault() { $this->select->addOption( 'foo', true ); $this->assertEquals( '<select><option value="1">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithFalse() { $this->select->addOption( 'foo', false ); $this->assertEquals( '<select><option value="foo">foo</option></select>', $this->select->getHTML() ); } + + /** + * @covers XmlSelect::addOption + */ public function testAddOptionWithValueZero() { $this->select->addOption( 'foo', 0 ); $this->assertEquals( '<select><option value="0">foo</option></select>', $this->select->getHTML() ); } - # End XmlSelect::addOption() similar to Xml::option + /** + * @covers XmlSelect::setDefault + */ public function testSetDefault() { $this->select->setDefault( 'bar1' ); $this->select->addOption( 'foo1' ); $this->select->addOption( 'bar1' ); $this->select->addOption( 'foo2' ); $this->assertEquals( -'<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="">bar1</option>' . "\n" . -'<option value="foo2">foo2</option></select>', $this->select->getHTML() ); + '<select><option value="foo1">foo1</option>' . "\n" . + '<option value="bar1" selected="">bar1</option>' . "\n" . + '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } /** * Adding default later on should set the correct selection or * raise an exception. * To handle this, we need to render the options in getHtml() + * @covers XmlSelect::setDefault */ public function testSetDefaultAfterAddingOptions() { $this->select->addOption( 'foo1' ); @@ -95,11 +125,15 @@ class XmlSelectTest extends MediaWikiTestCase { $this->select->addOption( 'foo2' ); $this->select->setDefault( 'bar1' ); # setting default after adding options $this->assertEquals( -'<select><option value="foo1">foo1</option>' . "\n" . -'<option value="bar1" selected="">bar1</option>' . "\n" . -'<option value="foo2">foo2</option></select>', $this->select->getHTML() ); + '<select><option value="foo1">foo1</option>' . "\n" . + '<option value="bar1" selected="">bar1</option>' . "\n" . + '<option value="foo2">foo2</option></select>', $this->select->getHTML() ); } + /** + * @covers XmlSelect::setAttribute + * @covers XmlSelect::getAttribute + */ public function testGetAttributes() { # create some attributes $this->select->setAttribute( 'dummy', 0x777 ); @@ -129,7 +163,7 @@ class XmlSelectTest extends MediaWikiTestCase { # verify string / integer $this->assertEquals( $this->select->getAttribute( '1911' ), - 'razor' + 'razor' ); $this->assertEquals( $this->select->getAttribute( 'dummy' ), diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php index 93ed3dc7..8205029f 100644 --- a/tests/phpunit/includes/XmlTest.php +++ b/tests/phpunit/includes/XmlTest.php @@ -1,48 +1,44 @@ <?php +/** + * @group Xml + */ class XmlTest extends MediaWikiTestCase { - private static $oldLang; - private static $oldNamespaces; - public function setUp() { - global $wgLang, $wgContLang; + protected function setUp() { + parent::setUp(); - self::$oldLang = $wgLang; - $wgLang = Language::factory( 'en' ); - - // Hardcode namespaces during test runs, - // so that html output based on existing namespaces - // can be properly evaluated. - self::$oldNamespaces = $wgContLang->getNamespaces(); - $wgContLang->setNamespaces( array( + $langObj = Language::factory( 'en' ); + $langObj->setNamespaces( array( -2 => 'Media', -1 => 'Special', - 0 => '', - 1 => 'Talk', - 2 => 'User', - 3 => 'User_talk', - 4 => 'MyWiki', - 5 => 'MyWiki_Talk', - 6 => 'File', - 7 => 'File_talk', - 8 => 'MediaWiki', - 9 => 'MediaWiki_talk', - 10 => 'Template', - 11 => 'Template_talk', - 100 => 'Custom', - 101 => 'Custom_talk', + 0 => '', + 1 => 'Talk', + 2 => 'User', + 3 => 'User_talk', + 4 => 'MyWiki', + 5 => 'MyWiki_Talk', + 6 => 'File', + 7 => 'File_talk', + 8 => 'MediaWiki', + 9 => 'MediaWiki_talk', + 10 => 'Template', + 11 => 'Template_talk', + 100 => 'Custom', + 101 => 'Custom_talk', ) ); - } - public function tearDown() { - global $wgLang, $wgContLang; - $wgLang = self::$oldLang; - - $wgContLang->setNamespaces( self::$oldNamespaces ); + $this->setMwGlobals( array( + 'wgLang' => $langObj, + 'wgWellFormedXml' => true, + ) ); } + /** + * @covers Xml::expandAttributes + */ public function testExpandAttributes() { - $this->assertNull( Xml::expandAttributes(null), + $this->assertNull( Xml::expandAttributes( null ), 'Converting a null list of attributes' ); $this->assertEquals( '', Xml::expandAttributes( array() ), @@ -50,12 +46,18 @@ class XmlTest extends MediaWikiTestCase { ); } + /** + * @covers Xml::expandAttributes + */ public function testExpandAttributesException() { - $this->setExpectedException('MWException'); - Xml::expandAttributes('string'); + $this->setExpectedException( 'MWException' ); + Xml::expandAttributes( 'string' ); } - function testElementOpen() { + /** + * @covers Xml::element + */ + public function testElementOpen() { $this->assertEquals( '<element>', Xml::element( 'element', null, null ), @@ -63,7 +65,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testElementEmpty() { + /** + * @covers Xml::element + */ + public function testElementEmpty() { $this->assertEquals( '<element />', Xml::element( 'element', null, '' ), @@ -71,14 +76,21 @@ class XmlTest extends MediaWikiTestCase { ); } - function testElementInputCanHaveAValueOfZero() { + /** + * @covers Xml::input + */ + public function testElementInputCanHaveAValueOfZero() { $this->assertEquals( '<input name="name" value="0" />', Xml::input( 'name', false, 0 ), 'Input with a value of 0 (bug 23797)' ); } - function testElementEscaping() { + + /** + * @covers Xml::element + */ + public function testElementEscaping() { $this->assertEquals( '<element>hello <there> you & you</element>', Xml::element( 'element', null, 'hello <there> you & you' ), @@ -86,13 +98,19 @@ class XmlTest extends MediaWikiTestCase { ); } + /** + * @covers Xml::escapeTagsOnly + */ public function testEscapeTagsOnly() { $this->assertEquals( '"><', Xml::escapeTagsOnly( '"><' ), 'replace " > and < with their HTML entitites' ); } - function testElementAttributes() { + /** + * @covers Xml::element + */ + public function testElementAttributes() { $this->assertEquals( '<element key="value" <>="<>">', Xml::element( 'element', array( 'key' => 'value', '<>' => '<>' ), null ), @@ -100,7 +118,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testOpenElement() { + /** + * @covers Xml::openElement + */ + public function testOpenElement() { $this->assertEquals( '<element k="v">', Xml::openElement( 'element', array( 'k' => 'v' ) ), @@ -108,95 +129,100 @@ class XmlTest extends MediaWikiTestCase { ); } - function testCloseElement() { + /** + * @covers Xml::closeElement + */ + public function testCloseElement() { $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' ); } /** - * @group Broken + * @covers Xml::dateMenu */ - public function testDateMenu( ) { - $curYear = intval(gmdate('Y')); - $prevYear = $curYear - 1; + public function testDateMenu() { + $curYear = intval( gmdate( 'Y' ) ); + $prevYear = $curYear - 1; - $curMonth = intval(gmdate('n')); + $curMonth = intval( gmdate( 'n' ) ); $prevMonth = $curMonth - 1; - if( $prevMonth == 0 ) { $prevMonth = 12; } + if ( $prevMonth == 0 ) { + $prevMonth = 12; + } $nextMonth = $curMonth + 1; - if( $nextMonth == 13 ) { $nextMonth = 1; } + if ( $nextMonth == 13 ) { + $nextMonth = 1; + } $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2" selected="">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2" selected="">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', Xml::dateMenu( 2011, 02 ), "Date menu for february 2011" ); $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="2011" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', - Xml::dateMenu( 2011, -1), + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" value="2011" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', + Xml::dateMenu( 2011, -1 ), "Date menu with negative month for 'All'" ); $this->assertEquals( Xml::dateMenu( $curYear, $curMonth ), - Xml::dateMenu( '' , $curMonth ), + Xml::dateMenu( '', $curMonth ), "Date menu year is the current one when not specified" ); - // @todo FIXME: next month can be in the next year - // test failing because it is now december + $wantedYear = $nextMonth == 1 ? $curYear : $prevYear; $this->assertEquals( - Xml::dateMenu( $prevYear, $nextMonth ), + Xml::dateMenu( $wantedYear, $nextMonth ), Xml::dateMenu( '', $nextMonth ), "Date menu next month is 11 months ago" ); - # @todo FIXME: Please note there is no year there! $this->assertEquals( - '<label for="year">From year (and earlier):</label> <input name="year" size="4" value="" id="year" maxlength="4" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . -'<option value="1">January</option>' . "\n" . -'<option value="2">February</option>' . "\n" . -'<option value="3">March</option>' . "\n" . -'<option value="4">April</option>' . "\n" . -'<option value="5">May</option>' . "\n" . -'<option value="6">June</option>' . "\n" . -'<option value="7">July</option>' . "\n" . -'<option value="8">August</option>' . "\n" . -'<option value="9">September</option>' . "\n" . -'<option value="10">October</option>' . "\n" . -'<option value="11">November</option>' . "\n" . -'<option value="12">December</option></select>', + '<label for="year">From year (and earlier):</label> <input id="year" maxlength="4" size="7" type="number" name="year" /> <label for="month">From month (and earlier):</label> <select id="month" name="month" class="mw-month-selector"><option value="-1">all</option>' . "\n" . + '<option value="1">January</option>' . "\n" . + '<option value="2">February</option>' . "\n" . + '<option value="3">March</option>' . "\n" . + '<option value="4">April</option>' . "\n" . + '<option value="5">May</option>' . "\n" . + '<option value="6">June</option>' . "\n" . + '<option value="7">July</option>' . "\n" . + '<option value="8">August</option>' . "\n" . + '<option value="9">September</option>' . "\n" . + '<option value="10">October</option>' . "\n" . + '<option value="11">November</option>' . "\n" . + '<option value="12">December</option></select>', Xml::dateMenu( '', '' ), "Date menu with neither year or month" ); } - # - # textarea - # - function testTextareaNoContent() { + /** + * @covers Xml::textarea + */ + public function testTextareaNoContent() { $this->assertEquals( '<textarea name="name" id="name" cols="40" rows="5"></textarea>', Xml::textarea( 'name', '' ), @@ -204,7 +230,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testTextareaAttribs() { + /** + * @covers Xml::textarea + */ + public function testTextareaAttribs() { $this->assertEquals( '<textarea name="name" id="name" cols="20" rows="10"><txt></textarea>', Xml::textarea( 'name', '<txt>', 20, 10 ), @@ -212,17 +241,21 @@ class XmlTest extends MediaWikiTestCase { ); } - # - # input and label - # - function testLabelCreation() { + /** + * @covers Xml::label + */ + public function testLabelCreation() { $this->assertEquals( '<label for="id">name</label>', Xml::label( 'name', 'id' ), 'label() with no attribs' ); } - function testLabelAttributeCanOnlyBeClassOrTitle() { + + /** + * @covers Xml::label + */ + public function testLabelAttributeCanOnlyBeClassOrTitle() { $this->assertEquals( '<label for="id">name</label>', Xml::label( 'name', 'id', array( 'generated' => true ) ), @@ -241,17 +274,20 @@ class XmlTest extends MediaWikiTestCase { $this->assertEquals( '<label for="id" class="nice" title="nice tooltip">name</label>', Xml::label( 'name', 'id', array( - 'generated' => true, - 'class' => 'nice', - 'title' => 'nice tooltip', - 'anotherattr' => 'value', + 'generated' => true, + 'class' => 'nice', + 'title' => 'nice tooltip', + 'anotherattr' => 'value', ) ), 'label() skip all attributes but "class" and "title"' ); } - function testLanguageSelector() { + /** + * @covers Xml::languageSelector + */ + public function testLanguageSelector() { $select = Xml::languageSelector( 'en', true, null, array( 'id' => 'testlang' ), wfMessage( 'yourlanguage' ) ); $this->assertEquals( @@ -260,10 +296,10 @@ class XmlTest extends MediaWikiTestCase { ); } - # - # JS - # - function testEscapeJsStringSpecialChars() { + /** + * @covers Xml::escapeJsString + */ + public function testEscapeJsStringSpecialChars() { $this->assertEquals( '\\\\\r\n', Xml::escapeJsString( "\\\r\n" ), @@ -271,7 +307,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarBoolean() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarBoolean() { $this->assertEquals( 'true', Xml::encodeJsVar( true ), @@ -279,7 +318,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarNull() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarNull() { $this->assertEquals( 'null', Xml::encodeJsVar( null ), @@ -287,7 +329,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarArray() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarArray() { $this->assertEquals( '["a",1]', Xml::encodeJsVar( array( 'a', 1 ) ), @@ -300,7 +345,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarObject() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarObject() { $this->assertEquals( '{"a":"a","b":1}', Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ), @@ -308,7 +356,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarInt() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarInt() { $this->assertEquals( '123456', Xml::encodeJsVar( 123456 ), @@ -316,7 +367,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarFloat() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarFloat() { $this->assertEquals( '1.23456', Xml::encodeJsVar( 1.23456 ), @@ -324,7 +378,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarIntString() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarIntString() { $this->assertEquals( '"123456"', Xml::encodeJsVar( '123456' ), @@ -332,7 +389,10 @@ class XmlTest extends MediaWikiTestCase { ); } - function testEncodeJsVarFloatString() { + /** + * @covers Xml::encodeJsVar + */ + public function testEncodeJsVarFloatString() { $this->assertEquals( '"1.23456"', Xml::encodeJsVar( '1.23456' ), diff --git a/tests/phpunit/includes/XmlTypeCheckTest.php b/tests/phpunit/includes/XmlTypeCheckTest.php new file mode 100644 index 00000000..8d6f1ed7 --- /dev/null +++ b/tests/phpunit/includes/XmlTypeCheckTest.php @@ -0,0 +1,30 @@ +<?php +/** + * PHPUnit tests for XMLTypeCheck. + * @author physikerwelt + * @group Xml + * @covers XMLTypeCheck + */ +class XmlTypeCheckTest extends MediaWikiTestCase { + const WELL_FORMED_XML = "<root><child /></root>"; + const MAL_FORMED_XML = "<root><child /></error>"; + + /** + * @covers XMLTypeCheck::newFromString + * @covers XMLTypeCheck::getRootElement + */ + public function testWellFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::WELL_FORMED_XML ); + $this->assertTrue( $testXML->wellFormed ); + $this->assertEquals( 'root', $testXML->getRootElement() ); + } + + /** + * @covers XMLTypeCheck::newFromString + */ + public function testMalFormedXML() { + $testXML = XmlTypeCheck::newFromString( self::MAL_FORMED_XML ); + $this->assertFalse( $testXML->wellFormed ); + } + +} diff --git a/tests/phpunit/includes/ZipDirectoryReaderTest.php b/tests/phpunit/includes/ZipDirectoryReaderTest.php index d90a6950..2627a417 100644 --- a/tests/phpunit/includes/ZipDirectoryReaderTest.php +++ b/tests/phpunit/includes/ZipDirectoryReaderTest.php @@ -1,9 +1,15 @@ <?php +/** + * @covers ZipDirectoryReader + * NOTE: this test is more like an integration test than a unit test + */ class ZipDirectoryReaderTest extends MediaWikiTestCase { - var $zipDir, $entries; + protected $zipDir; + protected $entries; - function setUp() { + protected function setUp() { + parent::setUp(); $this->zipDir = __DIR__ . '/../data/zip'; } @@ -23,21 +29,21 @@ class ZipDirectoryReaderTest extends MediaWikiTestCase { $this->assertTrue( $status->isOK(), $assertMessage ); } - function testEmpty() { + public function testEmpty() { $this->readZipAssertSuccess( 'empty.zip', 'Empty zip' ); } - function testMultiDisk0() { - $this->readZipAssertError( 'split.zip', 'zip-unsupported', + public function testMultiDisk0() { + $this->readZipAssertError( 'split.zip', 'zip-unsupported', 'Split zip error' ); } - function testNoSignature() { - $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format', + public function testNoSignature() { + $this->readZipAssertError( 'nosig.zip', 'zip-wrong-format', 'No signature should give "wrong format" error' ); } - function testSimple() { + public function testSimple() { $this->readZipAssertSuccess( 'class.zip', 'Simple ZIP' ); $this->assertEquals( $this->entries, array( array( 'name' => 'Class.class', @@ -46,33 +52,33 @@ class ZipDirectoryReaderTest extends MediaWikiTestCase { ) ) ); } - function testBadCentralEntrySignature() { + public function testBadCentralEntrySignature() { $this->readZipAssertError( 'wrong-central-entry-sig.zip', 'zip-bad', 'Bad central entry error' ); } - function testTrailingBytes() { + public function testTrailingBytes() { $this->readZipAssertError( 'trail.zip', 'zip-bad', 'Trailing bytes error' ); } - function testWrongCDStart() { - $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported', + public function testWrongCDStart() { + $this->readZipAssertError( 'wrong-cd-start-disk.zip', 'zip-unsupported', 'Wrong CD start disk error' ); } - function testCentralDirectoryGap() { + public function testCentralDirectoryGap() { $this->readZipAssertError( 'cd-gap.zip', 'zip-bad', 'CD gap error' ); } - function testCentralDirectoryTruncated() { + public function testCentralDirectoryTruncated() { $this->readZipAssertError( 'cd-truncated.zip', 'zip-bad', 'CD truncated error (should hit unpack() overrun)' ); } - function testLooksLikeZip64() { + public function testLooksLikeZip64() { $this->readZipAssertError( 'looks-like-zip64.zip', 'zip-unsupported', 'A file which looks like ZIP64 but isn\'t, should give error' ); } diff --git a/tests/phpunit/includes/api/ApiAccountCreationTest.php b/tests/phpunit/includes/api/ApiAccountCreationTest.php new file mode 100644 index 00000000..68f80ac9 --- /dev/null +++ b/tests/phpunit/includes/api/ApiAccountCreationTest.php @@ -0,0 +1,159 @@ +<?php + +/** + * @group Database + * @group API + * @group medium + */ +class ApiCreateAccountTest extends ApiTestCase { + function setUp() { + parent::setUp(); + LoginForm::setCreateaccountToken(); + $this->setMwGlobals( array( 'wgEnableEmail' => true ) ); + } + + /** + * Test the account creation API with a valid request. Also + * make sure the new account can log in and is valid. + * + * This test does multiple API requests so it might end up being + * a bit slow. Raise the default timeout. + * @group medium + */ + public function testValid() { + global $wgServer; + + if ( !isset( $wgServer ) ) { + $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); + } + + $password = User::randomPassword(); + + $ret = $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Apitestnew', + 'password' => $password, + 'email' => 'test@domain.test', + 'realname' => 'Test Name' + ) ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertNotInternalType( 'null', $result['createaccount'] ); + + // Should first ask for token. + $a = $result['createaccount']; + $this->assertEquals( 'needtoken', $a['result'] ); + $token = $a['token']; + + // Finally create the account + $ret = $this->doApiRequest( + array( + 'action' => 'createaccount', + 'name' => 'Apitestnew', + 'password' => $password, + 'token' => $token, + 'email' => 'test@domain.test', + 'realname' => 'Test Name' + ), + $ret[2] + ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertEquals( 'success', $result['createaccount']['result'] ); + + // Try logging in with the new user. + $ret = $this->doApiRequest( array( + 'action' => 'login', + 'lgname' => 'Apitestnew', + 'lgpassword' => $password, + ) ); + + $result = $ret[0]; + $this->assertNotInternalType( 'bool', $result ); + $this->assertNotInternalType( 'null', $result['login'] ); + + $a = $result['login']['result']; + $this->assertEquals( 'NeedToken', $a ); + $token = $result['login']['token']; + + $ret = $this->doApiRequest( + array( + 'action' => 'login', + 'lgtoken' => $token, + 'lgname' => 'Apitestnew', + 'lgpassword' => $password, + ), + $ret[2] + ); + + $result = $ret[0]; + + $this->assertNotInternalType( 'bool', $result ); + $a = $result['login']['result']; + + $this->assertEquals( 'Success', $a ); + + // log out to destroy the session + $ret = $this->doApiRequest( + array( + 'action' => 'logout', + ), + $ret[2] + ); + $this->assertEquals( array(), $ret[0] ); + } + + /** + * Make sure requests with no names are invalid. + * @expectedException UsageException + */ + public function testNoName() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + ) ); + } + + /** + * Make sure requests with no password are invalid. + * @expectedException UsageException + */ + public function testNoPassword() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'testName', + 'token' => LoginForm::getCreateaccountToken(), + ) ); + } + + /** + * Make sure requests with existing users are invalid. + * @expectedException UsageException + */ + public function testExistingUser() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Apitestsysop', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + 'email' => 'test@domain.test', + ) ); + } + + /** + * Make sure requests with invalid emails are invalid. + * @expectedException UsageException + */ + public function testInvalidEmail() { + $this->doApiRequest( array( + 'action' => 'createaccount', + 'name' => 'Test User', + 'token' => LoginForm::getCreateaccountToken(), + 'password' => 'password', + 'email' => 'invalid', + ) ); + } +} diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php index 5dfceee8..8afb748a 100644 --- a/tests/phpunit/includes/api/ApiBlockTest.php +++ b/tests/phpunit/includes/api/ApiBlockTest.php @@ -3,10 +3,10 @@ /** * @group API * @group Database + * @group medium */ class ApiBlockTest extends ApiTestCase { - - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } @@ -34,9 +34,8 @@ class ApiBlockTest extends ApiTestCase { * Which made the Block/Unblock API to actually verify the token * previously always considered valid (bug 34212). */ - function testMakeNormalBlock() { - - $data = $this->getTokens(); + public function testMakeNormalBlock() { + $tokens = $this->getTokens(); $user = User::newFromName( 'UTApiBlockee' ); @@ -44,44 +43,23 @@ class ApiBlockTest extends ApiTestCase { $this->markTestIncomplete( "The user UTApiBlockee does not exist" ); } - if( !isset( $data[0]['query']['pages'] ) ) { + if ( !array_key_exists( 'blocktoken', $tokens ) ) { $this->markTestIncomplete( "No block token found" ); } - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; - - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'block', 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - 'token' => $pageinfo['blocktoken'] ), null, false, self::$users['sysop']->user ); + 'token' => $tokens['blocktoken'] ), null, false, self::$users['sysop']->user ); - $block = Block::newFromTarget('UTApiBlockee'); + $block = Block::newFromTarget( 'UTApiBlockee' ); $this->assertTrue( !is_null( $block ), 'Block is valid' ); $this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() ); $this->assertEquals( 'Some reason', $block->mReason ); $this->assertEquals( 'infinity', $block->mExpiry ); - - } - - /** - * @dataProvider provideBlockUnblockAction - */ - function testGetTokenUsingABlockingAction( $action ) { - $data = $this->doApiRequest( - array( - 'action' => $action, - 'user' => 'UTApiBlockee', - 'gettoken' => '' ), - null, - false, - self::$users['sysop']->user - ); - $this->assertEquals( 34, strlen( $data[0][$action]["{$action}token"] ) ); } /** @@ -92,13 +70,13 @@ class ApiBlockTest extends ApiTestCase { * @dataProvider provideBlockUnblockAction * @expectedException UsageException */ - function testBlockingActionWithNoToken( $action ) { + public function testBlockingActionWithNoToken( $action ) { $this->doApiRequest( array( 'action' => $action, 'user' => 'UTApiBlockee', 'reason' => 'Some reason', - ), + ), null, false, self::$users['sysop']->user @@ -108,9 +86,9 @@ class ApiBlockTest extends ApiTestCase { /** * Just provide the 'block' and 'unblock' action to test both API calls */ - function provideBlockUnblockAction() { + public static function provideBlockUnblockAction() { return array( - array( 'block' ), + array( 'block' ), array( 'unblock' ), ); } diff --git a/tests/phpunit/includes/api/ApiEditPageTest.php b/tests/phpunit/includes/api/ApiEditPageTest.php index 5297d6da..0c49b12b 100644 --- a/tests/phpunit/includes/api/ApiEditPageTest.php +++ b/tests/phpunit/includes/api/ApiEditPageTest.php @@ -7,25 +7,54 @@ * * @group API * @group Database + * @group medium */ class ApiEditPageTest extends ApiTestCase { - function setUp() { + public function setUp() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + parent::setUp(); + + $wgExtraNamespaces[12312] = 'Dummy'; + $wgExtraNamespaces[12313] = 'Dummy_talk'; + + $wgNamespaceContentModels[12312] = "testing"; + $wgContentHandlers["testing"] = 'DummyContentHandlerForTesting'; + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + $this->doLogin(); } - function testEdit( ) { - $name = 'ApiEditPageTest_testEdit'; + public function tearDown() { + global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang; + + unset( $wgExtraNamespaces[12312] ); + unset( $wgExtraNamespaces[12313] ); + + unset( $wgNamespaceContentModels[12312] ); + unset( $wgContentHandlers["testing"] ); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + + parent::tearDown(); + } + + public function testEdit() { + $name = 'Help:ApiEditPageTest_testEdit'; // assume Help namespace to default to wikitext // -- test new page -------------------------------------------- $apiResult = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'some text', ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', + ) ); $apiResult = $apiResult[0]; - # Validate API result data + // Validate API result data $this->assertArrayHasKey( 'edit', $apiResult ); $this->assertArrayHasKey( 'result', $apiResult['edit'] ); $this->assertEquals( 'Success', $apiResult['edit']['result'] ); @@ -37,9 +66,10 @@ class ApiEditPageTest extends ApiTestCase { // -- test existing page, no change ---------------------------- $data = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'some text', ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'some text', + ) ); $this->assertEquals( 'Success', $data[0]['edit']['result'] ); @@ -48,9 +78,10 @@ class ApiEditPageTest extends ApiTestCase { // -- test existing page, with change -------------------------- $data = $this->doApiRequestWithToken( array( - 'action' => 'edit', - 'title' => $name, - 'text' => 'different text' ) ); + 'action' => 'edit', + 'title' => $name, + 'text' => 'different text' + ) ); $this->assertEquals( 'Success', $data[0]['edit']['result'] ); @@ -66,19 +97,321 @@ class ApiEditPageTest extends ApiTestCase { ); } - function testEditAppend() { - $this->markTestIncomplete( "not yet implemented" ); + public function testNonTextEdit() { + $name = 'Dummy:ApiEditPageTest_testNonTextEdit'; + $data = serialize( 'some bla bla text' ); + + // -- test new page -------------------------------------------- + $apiResult = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $data, ) ); + $apiResult = $apiResult[0]; + + // Validate API result data + $this->assertArrayHasKey( 'edit', $apiResult ); + $this->assertArrayHasKey( 'result', $apiResult['edit'] ); + $this->assertEquals( 'Success', $apiResult['edit']['result'] ); + + $this->assertArrayHasKey( 'new', $apiResult['edit'] ); + $this->assertArrayNotHasKey( 'nochange', $apiResult['edit'] ); + + $this->assertArrayHasKey( 'pageid', $apiResult['edit'] ); + + // validate resulting revision + $page = WikiPage::factory( Title::newFromText( $name ) ); + $this->assertEquals( "testing", $page->getContentModel() ); + $this->assertEquals( $data, $page->getContent()->serialize() ); } - function testEditSection() { - $this->markTestIncomplete( "not yet implemented" ); + public static function provideEditAppend() { + return array( + array( #0: append + 'foo', 'append', 'bar', "foobar" + ), + array( #1: prepend + 'foo', 'prepend', 'bar', "barfoo" + ), + array( #2: append to empty page + '', 'append', 'foo', "foo" + ), + array( #3: prepend to empty page + '', 'prepend', 'foo', "foo" + ), + array( #4: append to non-existing page + null, 'append', 'foo', "foo" + ), + array( #5: prepend to non-existing page + null, 'prepend', 'foo', "foo" + ), + ); } - function testUndo() { - $this->markTestIncomplete( "not yet implemented" ); + /** + * @dataProvider provideEditAppend + */ + public function testEditAppend( $text, $op, $append, $expected ) { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditAppend_$count"; + + // -- create page (or not) ----------------------------------------- + if ( $text !== null ) { + if ( $text === '' ) { + // can't create an empty page, so create it with some content + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => '(dummy)', ) ); + } + + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => $text, ) ); + + $this->assertEquals( 'Success', $re['edit']['result'] ); // sanity + } + + // -- try append/prepend -------------------------------------------- + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + $op . 'text' => $append, ) ); + + $this->assertEquals( 'Success', $re['edit']['result'] ); + + // -- validate ----------------------------------------------------- + $page = new WikiPage( Title::newFromText( $name ) ); + $content = $page->getContent(); + $this->assertNotNull( $content, 'Page should have been created' ); + + $text = $content->getNativeData(); + + $this->assertEquals( $expected, $text ); } - function testEditNonText() { - $this->markTestIncomplete( "not yet implemented" ); + /** + * Test editing of sections + */ + public function testEditSection() { + $name = 'Help:ApiEditPageTest_testEditSection'; + $page = WikiPage::factory( Title::newFromText( $name ) ); + $text = "==section 1==\ncontent 1\n==section 2==\ncontent2"; + // Preload the page with some text + $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), 'summary' ); + + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => '1', + 'text' => "==section 1==\nnew content 1", + ) ); + $this->assertEquals( 'Success', $re['edit']['result'] ); + $newtext = WikiPage::factory( Title::newFromText( $name) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $newtext, "==section 1==\nnew content 1\n\n==section 2==\ncontent2" ); + + // Test that we raise a 'nosuchsection' error + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => '9999', + 'text' => 'text', + ) ); + $this->fail( "Should have raised a UsageException" ); + } catch ( UsageException $e ) { + $this->assertEquals( $e->getCodeString(), 'nosuchsection' ); + } + } + + /** + * Test action=edit§ion=new + * Run it twice so we test adding a new section on a + * page that doesn't exist (bug 52830) and one that + * does exist + */ + public function testEditNewSection() { + $name = 'Help:ApiEditPageTest_testEditNewSection'; + + // Test on a page that does not already exist + $this->assertFalse( Title::newFromText( $name )->exists() ); + list( $re ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => 'new', + 'text' => 'test', + 'summary' => 'header', + )); + + $this->assertEquals( 'Success', $re['edit']['result'] ); + // Check the page text is correct + $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $text, "== header ==\n\ntest" ); + + // Now on one that does + $this->assertTrue( Title::newFromText( $name )->exists() ); + list( $re2 ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'section' => 'new', + 'text' => 'test', + 'summary' => 'header', + )); + + $this->assertEquals( 'Success', $re2['edit']['result'] ); + $text = WikiPage::factory( Title::newFromText( $name ) )->getContent( Revision::RAW )->getNativeData(); + $this->assertEquals( $text, "== header ==\n\ntest\n\n== header ==\n\ntest" ); + } + + public function testEditConflict() { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_$count"; + $title = Title::newFromText( $name ); + + $page = WikiPage::factory( $title ); + + // base edit + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + $baseTime = $page->getRevision()->getTimestamp(); + + // conflicting edit + $page->doEditContent( new WikitextContent( "Foo bar" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $page, '20120101020202' ); + + // try to save edit, expect conflict + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $name, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + ), null, self::$users['sysop']->user ); + + $this->fail( 'edit conflict expected' ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'editconflict', $ex->getCodeString() ); + } + } + + public function testEditConflict_redirect() { + static $count = 0; + $count++; + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_redirect_$count"; + $title = Title::newFromText( $name ); + $page = WikiPage::factory( $title ); + + $rname = "Help:ApiEditPageTest_testEditConflict_redirect_r$count"; + $rtitle = Title::newFromText( $rname ); + $rpage = WikiPage::factory( $rtitle ); + + // base edit for content + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + $baseTime = $page->getRevision()->getTimestamp(); + + // base edit for redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $rpage, '20120101000000' ); + + // conflicting edit to redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]\n\n[[Category:Test]]" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $rpage, '20120101020202' ); + + // try to save edit; should work, because we follow the redirect + list( $re, , ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + 'redirect' => true, + ), null, self::$users['sysop']->user ); + + $this->assertEquals( 'Success', $re['edit']['result'], + "no edit conflict expected when following redirect" ); + + // try again, without following the redirect. Should fail. + try { + $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'basetimestamp' => $baseTime, + ), null, self::$users['sysop']->user ); + + $this->fail( 'edit conflict expected' ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'editconflict', $ex->getCodeString() ); + } + } + + public function testEditConflict_bug41990() { + static $count = 0; + $count++; + + /* + * bug 41990: if the target page has a newer revision than the redirect, then editing the + * redirect while specifying 'redirect' and *not* specifying 'basetimestamp' erroneously + * caused an edit conflict to be detected. + */ + + // assume NS_HELP defaults to wikitext + $name = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_$count"; + $title = Title::newFromText( $name ); + $page = WikiPage::factory( $title ); + + $rname = "Help:ApiEditPageTest_testEditConflict_redirect_bug41990_r$count"; + $rtitle = Title::newFromText( $rname ); + $rpage = WikiPage::factory( $rtitle ); + + // base edit for content + $page->doEditContent( new WikitextContent( "Foo" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $page, '20120101000000' ); + + // base edit for redirect + $rpage->doEditContent( new WikitextContent( "#REDIRECT [[$name]]" ), + "testing 1", EDIT_NEW, false, self::$users['sysop']->user ); + $this->forceRevisionDate( $rpage, '20120101000000' ); + $baseTime = $rpage->getRevision()->getTimestamp(); + + // new edit to content + $page->doEditContent( new WikitextContent( "Foo bar" ), + "testing 2", EDIT_UPDATE, $page->getLatest(), self::$users['uploader']->user ); + $this->forceRevisionDate( $rpage, '20120101020202' ); + + // try to save edit; should work, following the redirect. + list( $re, , ) = $this->doApiRequestWithToken( array( + 'action' => 'edit', + 'title' => $rname, + 'text' => 'nix bar!', + 'redirect' => true, + ), null, self::$users['sysop']->user ); + + $this->assertEquals( 'Success', $re['edit']['result'], + "no edit conflict expected here" ); + } + + protected function forceRevisionDate( WikiPage $page, $timestamp ) { + $dbw = wfGetDB( DB_MASTER ); + + $dbw->update( 'revision', + array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ), + array( 'rev_id' => $page->getLatest() ) ); + + $page->clear(); } } diff --git a/tests/phpunit/includes/api/ApiOptionsTest.php b/tests/phpunit/includes/api/ApiOptionsTest.php index 5243fca1..ad1e73ab 100644 --- a/tests/phpunit/includes/api/ApiOptionsTest.php +++ b/tests/phpunit/includes/api/ApiOptionsTest.php @@ -3,48 +3,44 @@ /** * @group API * @group Database + * @group medium */ class ApiOptionsTest extends MediaWikiLangTestCase { - private $mTested, $mApiMainMock, $mUserMock, $mContext, $mSession; + private $mTested, $mUserMock, $mContext, $mSession; private $mOldGetPreferencesHooks = false; private static $Success = array( 'options' => 'success' ); - function setUp() { + protected function setUp() { parent::setUp(); $this->mUserMock = $this->getMockBuilder( 'User' ) ->disableOriginalConstructor() ->getMock(); - $this->mApiMainMock = $this->getMockBuilder( 'ApiBase' ) - ->disableOriginalConstructor() - ->getMock(); + // Set up groups and rights + $this->mUserMock->expects( $this->any() ) + ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user' ) ) ); + $this->mUserMock->expects( $this->any() ) + ->method( 'isAllowed' )->will( $this->returnValue( true ) ); - // Set up groups + // Set up callback for User::getOptionKinds $this->mUserMock->expects( $this->any() ) - ->method( 'getEffectiveGroups' )->will( $this->returnValue( array( '*', 'user')) ); + ->method( 'getOptionKinds' )->will( $this->returnCallback( array( $this, 'getOptionKinds' ) ) ); // Create a new context $this->mContext = new DerivativeContext( new RequestContext() ); $this->mContext->getContext()->setTitle( Title::newFromText( 'Test' ) ); $this->mContext->setUser( $this->mUserMock ); - $this->mApiMainMock->expects( $this->any() ) - ->method( 'getContext' ) - ->will( $this->returnValue( $this->mContext ) ); - - $this->mApiMainMock->expects( $this->any() ) - ->method( 'getResult' ) - ->will( $this->returnValue( new ApiResult( $this->mApiMainMock ) ) ); - + $main = new ApiMain( $this->mContext ); // Empty session $this->mSession = array(); - $this->mTested = new ApiOptions( $this->mApiMainMock, 'options' ); + $this->mTested = new ApiOptions( $main, 'options' ); global $wgHooks; if ( !isset( $wgHooks['GetPreferences'] ) ) { @@ -54,7 +50,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $wgHooks['GetPreferences'][] = array( $this, 'hookGetPreferences' ); } - public function tearDown() { + protected function tearDown() { global $wgHooks; if ( $this->mOldGetPreferencesHooks !== false ) { @@ -66,6 +62,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } public function hookGetPreferences( $user, &$preferences ) { + $preferences = array(); + foreach ( array( 'name', 'willBeNull', 'willBeEmpty', 'willBeHappy' ) as $k ) { $preferences[$k] = array( 'type' => 'text', @@ -74,9 +72,56 @@ class ApiOptionsTest extends MediaWikiLangTestCase { ); } + $preferences['testmultiselect'] = array( + 'type' => 'multiselect', + 'options' => array( + 'Test' => array( + '<span dir="auto">Some HTML here for option 1</span>' => 'opt1', + '<span dir="auto">Some HTML here for option 2</span>' => 'opt2', + '<span dir="auto">Some HTML here for option 3</span>' => 'opt3', + '<span dir="auto">Some HTML here for option 4</span>' => 'opt4', + ), + ), + 'section' => 'test', + 'label' => ' ', + 'prefix' => 'testmultiselect-', + 'default' => array(), + ); + return true; } + public function getOptionKinds( IContextSource $context, $options = null ) { + // Match with above. + $kinds = array( + 'name' => 'registered', + 'willBeNull' => 'registered', + 'willBeEmpty' => 'registered', + 'willBeHappy' => 'registered', + 'testmultiselect-opt1' => 'registered-multiselect', + 'testmultiselect-opt2' => 'registered-multiselect', + 'testmultiselect-opt3' => 'registered-multiselect', + 'testmultiselect-opt4' => 'registered-multiselect', + ); + + if ( $options === null ) { + return $kinds; + } + + $mapping = array(); + foreach ( $options as $key => $value ) { + if ( isset( $kinds[$key] ) ) { + $mapping[$key] = $kinds[$key]; + } elseif ( substr( $key, 0, 7 ) === 'userjs-' ) { + $mapping[$key] = 'userjs'; + } else { + $mapping[$key] = 'unused'; + } + } + + return $mapping; + } + private function getSampleRequest( $custom = array() ) { $request = array( 'token' => '123ABC', @@ -84,12 +129,14 @@ class ApiOptionsTest extends MediaWikiLangTestCase { 'optionname' => null, 'optionvalue' => null, ); + return array_merge( $request, $custom ); } private function executeQuery( $request ) { $this->mContext->setRequest( new FauxRequest( $request, true, $this->mSession ) ); $this->mTested->execute(); + return $this->mTested->getResult()->getData(); } @@ -114,6 +161,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'notloggedin', $e->getCodeString() ); $this->assertEquals( 'Anonymous users cannot change preferences', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -127,6 +175,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'nooptionname', $e->getCodeString() ); $this->assertEquals( 'The optionname parameter must be set', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -149,6 +198,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { } catch ( UsageException $e ) { $this->assertEquals( 'nochanges', $e->getCodeString() ); $this->assertEquals( 'No changes were requested', $e->getMessage() ); + return; } $this->fail( "UsageException was not thrown" ); @@ -156,7 +206,8 @@ class ApiOptionsTest extends MediaWikiLangTestCase { public function testReset() { $this->mUserMock->expects( $this->once() ) - ->method( 'resetOptions' ); + ->method( 'resetOptions' ) + ->with( $this->equalTo( array( 'all' ) ) ); $this->mUserMock->expects( $this->never() ) ->method( 'setOption' ); @@ -171,6 +222,24 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->assertEquals( self::$Success, $response ); } + public function testResetKinds() { + $this->mUserMock->expects( $this->once() ) + ->method( 'resetOptions' ) + ->with( $this->equalTo( array( 'registered' ) ) ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'setOption' ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( 'reset' => '', 'resetkinds' => 'registered' ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + public function testOptionWithValue() { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); @@ -195,7 +264,7 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'setOption' ) - ->with( $this->equalTo( 'name' ), $this->equalTo( null ) ); + ->with( $this->equalTo( 'name' ), $this->identicalTo( null ) ); $this->mUserMock->expects( $this->once() ) ->method( 'saveSettings' ); @@ -210,24 +279,24 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->never() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 1 ) ) + $this->mUserMock->expects( $this->at( 2 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'setOption' ) - ->with( $this->equalTo( 'willBeNull' ), $this->equalTo( null ) ); + ->with( $this->equalTo( 'willBeNull' ), $this->identicalTo( null ) ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeEmpty' ), $this->equalTo( '' ) ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 6 ) ) + $this->mUserMock->expects( $this->at( 8 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); @@ -245,17 +314,17 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->mUserMock->expects( $this->once() ) ->method( 'resetOptions' ); - $this->mUserMock->expects( $this->at( 2 ) ) + $this->mUserMock->expects( $this->at( 4 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 3 ) ) + $this->mUserMock->expects( $this->at( 5 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'willBeHappy' ), $this->equalTo( 'Happy' ) ); - $this->mUserMock->expects( $this->at( 4 ) ) + $this->mUserMock->expects( $this->at( 6 ) ) ->method( 'getOptions' ); - $this->mUserMock->expects( $this->at( 5 ) ) + $this->mUserMock->expects( $this->at( 7 ) ) ->method( 'setOption' ) ->with( $this->equalTo( 'name' ), $this->equalTo( 'value' ) ); @@ -273,4 +342,79 @@ class ApiOptionsTest extends MediaWikiLangTestCase { $this->assertEquals( self::$Success, $response ); } + + public function testMultiSelect() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt1' ), $this->identicalTo( true ) ); + + $this->mUserMock->expects( $this->at( 4 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt2' ), $this->identicalTo( null ) ); + + $this->mUserMock->expects( $this->at( 5 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt3' ), $this->identicalTo( false ) ); + + $this->mUserMock->expects( $this->at( 6 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'testmultiselect-opt4' ), $this->identicalTo( false ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'testmultiselect-opt1=1|testmultiselect-opt2|testmultiselect-opt3=|testmultiselect-opt4=0' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } + + public function testUnknownOption() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->never() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'unknownOption=1' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( array( + 'options' => 'success', + 'warnings' => array( + 'options' => array( + '*' => "Validation error for 'unknownOption': not a valid preference" + ) + ) + ), $response ); + } + + public function testUserjsOption() { + $this->mUserMock->expects( $this->never() ) + ->method( 'resetOptions' ); + + $this->mUserMock->expects( $this->at( 3 ) ) + ->method( 'setOption' ) + ->with( $this->equalTo( 'userjs-option' ), $this->equalTo( '1' ) ); + + $this->mUserMock->expects( $this->once() ) + ->method( 'saveSettings' ); + + $request = $this->getSampleRequest( array( + 'change' => 'userjs-option=1' + ) ); + + $response = $this->executeQuery( $request ); + + $this->assertEquals( self::$Success, $response ); + } } diff --git a/tests/phpunit/includes/api/ApiParseTest.php b/tests/phpunit/includes/api/ApiParseTest.php new file mode 100644 index 00000000..2d714e65 --- /dev/null +++ b/tests/phpunit/includes/api/ApiParseTest.php @@ -0,0 +1,29 @@ +<?php + +/** + * @group API + * @group Database + * @group medium + */ +class ApiParseTest extends ApiTestCase { + + protected function setUp() { + parent::setUp(); + $this->doLogin(); + } + + public function testParseNonexistentPage() { + $somePage = mt_rand(); + + try { + $this->doApiRequest( array( + 'action' => 'parse', + 'page' => $somePage ) ); + + $this->fail( "API did not return an error when parsing a nonexistent page" ); + } catch ( UsageException $ex ) { + $this->assertEquals( 'missingtitle', $ex->getCodeString(), + "Parse request for nonexistent page must give 'missingtitle' error: " . var_export( $ex->getMessageArray(), true ) ); + } + } +} diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php index 2566c6cd..28b5ff4d 100644 --- a/tests/phpunit/includes/api/ApiPurgeTest.php +++ b/tests/phpunit/includes/api/ApiPurgeTest.php @@ -3,10 +3,11 @@ /** * @group API * @group Database + * @group medium */ class ApiPurgeTest extends ApiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } @@ -14,7 +15,7 @@ class ApiPurgeTest extends ApiTestCase { /** * @group Broken */ - function testPurgeMainPage() { + public function testPurgeMainPage() { if ( !Title::newFromText( 'UTPage' )->exists() ) { $this->markTestIncomplete( "The article [[UTPage]] does not exist" ); } @@ -32,9 +33,8 @@ class ApiPurgeTest extends ApiTestCase { "Purge request for three articles should give back three results received: " . var_export( $data[0]['purge'], true ) ); $pages = array( 'UTPage' => 'purged', $somePage => 'missing', '%5D' => 'invalid' ); - foreach( $data[0]['purge'] as $v ) { + foreach ( $data[0]['purge'] as $v ) { $this->assertArrayHasKey( $pages[$v['title']], $v ); } } - } diff --git a/tests/phpunit/includes/api/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php index c3eacd5b..472f8c4a 100644 --- a/tests/phpunit/includes/api/ApiTest.php +++ b/tests/phpunit/includes/api/ApiTest.php @@ -3,37 +3,38 @@ /** * @group API * @group Database + * @group medium */ class ApiTest extends ApiTestCase { - function testRequireOnlyOneParameterDefault() { + public function testRequireOnlyOneParameterDefault() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => false ), "filename", "enablechunks" ) ); + "enablechunks" => false ), "filename", "enablechunks" ) ); } /** * @expectedException UsageException */ - function testRequireOnlyOneParameterZero() { + public function testRequireOnlyOneParameterZero() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => 0 ), "filename", "enablechunks" ) ); + "enablechunks" => 0 ), "filename", "enablechunks" ) ); } /** * @expectedException UsageException */ - function testRequireOnlyOneParameterTrue() { + public function testRequireOnlyOneParameterTrue() { $mock = new MockApi(); $this->assertEquals( null, $mock->requireOnlyOneParameter( array( "filename" => "foo.txt", - "enablechunks" => true ), "filename", "enablechunks" ) ); + "enablechunks" => true ), "filename", "enablechunks" ) ); } /** @@ -42,8 +43,7 @@ class ApiTest extends ApiTestCase { * * @expectedException UsageException */ - function testApi() { - + public function testApi() { $api = new ApiMain( new FauxRequest( array( 'action' => 'help', 'format' => 'xml' ) ) ); @@ -61,14 +61,14 @@ class ApiTest extends ApiTestCase { /** * Test result of attempted login with an empty username */ - function testApiLoginNoName() { + public function testApiLoginNoName() { $data = $this->doApiRequest( array( 'action' => 'login', 'lgname' => '', 'lgpassword' => self::$users['sysop']->password, ) ); $this->assertEquals( 'NoName', $data[0]['login']['result'] ); } - function testApiLoginBadPass() { + public function testApiLoginBadPass() { global $wgServer; $user = self::$users['sysop']; @@ -81,8 +81,7 @@ class ApiTest extends ApiTestCase { "action" => "login", "lgname" => $user->username, "lgpassword" => "bad", - ) - ); + ) ); $result = $ret[0]; @@ -92,12 +91,14 @@ class ApiTest extends ApiTestCase { $token = $result["login"]["token"]; - $ret = $this->doApiRequest( array( - "action" => "login", - "lgtoken" => $token, - "lgname" => $user->username, - "lgpassword" => "badnowayinhell", - ), $ret[2] + $ret = $this->doApiRequest( + array( + "action" => "login", + "lgtoken" => $token, + "lgname" => $user->username, + "lgpassword" => "badnowayinhell", + ), + $ret[2] ); $result = $ret[0]; @@ -108,7 +109,7 @@ class ApiTest extends ApiTestCase { $this->assertEquals( "WrongPass", $a ); } - function testApiLoginGoodPass() { + public function testApiLoginGoodPass() { global $wgServer; if ( !isset( $wgServer ) ) { @@ -119,9 +120,9 @@ class ApiTest extends ApiTestCase { $user->user->logOut(); $ret = $this->doApiRequest( array( - "action" => "login", - "lgname" => $user->username, - "lgpassword" => $user->password, + "action" => "login", + "lgname" => $user->username, + "lgpassword" => $user->password, ) ); @@ -133,12 +134,14 @@ class ApiTest extends ApiTestCase { $this->assertEquals( "NeedToken", $a ); $token = $result["login"]["token"]; - $ret = $this->doApiRequest( array( - "action" => "login", - "lgtoken" => $token, - "lgname" => $user->username, - "lgpassword" => $user->password, - ), $ret[2] + $ret = $this->doApiRequest( + array( + "action" => "login", + "lgtoken" => $token, + "lgname" => $user->username, + "lgpassword" => $user->password, + ), + $ret[2] ); $result = $ret[0]; @@ -152,8 +155,8 @@ class ApiTest extends ApiTestCase { /** * @group Broken */ - function testApiGotCookie() { - $this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" ); + public function testApiGotCookie() { + $this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies" ); global $wgServer, $wgScriptPath; @@ -165,8 +168,11 @@ class ApiTest extends ApiTestCase { $req = MWHttpRequest::factory( self::$apiUrl . "?action=login&format=xml", array( "method" => "POST", "postData" => array( - "lgname" => $user->username, - "lgpassword" => $user->password ) ) ); + "lgname" => $user->username, + "lgpassword" => $user->password + ) + ) + ); $req->execute(); libxml_use_internal_errors( true ); @@ -195,27 +201,7 @@ class ApiTest extends ApiTestCase { return $cj; } - /** - * @todo Finish filling me out...what are we trying to test here? - */ - function testApiListPages() { - global $wgServer; - if ( !isset( $wgServer ) ) { - $this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' ); - } - - $ret = $this->doApiRequest( array( - 'action' => 'query', - 'prop' => 'revisions', - 'titles' => 'Main Page', - 'rvprop' => 'timestamp|user|comment|content', - ) ); - - $result = $ret[0]['query']['pages']; - $this->markTestIncomplete( "Somebody needs to finish loving me" ); - } - - function testRunLogin() { + public function testRunLogin() { $sysopUser = self::$users['sysop']; $data = $this->doApiRequest( array( 'action' => 'login', @@ -237,44 +223,37 @@ class ApiTest extends ApiTestCase { $this->assertArrayHasKey( "result", $data[0]['login'] ); $this->assertEquals( "Success", $data[0]['login']['result'] ); $this->assertArrayHasKey( 'lgtoken', $data[0]['login'] ); - + return $data; } - - function testGettingToken() { + + public function testGettingToken() { foreach ( self::$users as $user ) { $this->runTokenTest( $user ); } } function runTokenTest( $user ) { - - $data = $this->getTokenList( $user ); - - $this->assertArrayHasKey( 'query', $data[0] ); - $this->assertArrayHasKey( 'pages', $data[0]['query'] ); - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); + $tokens = $this->getTokenList( $user ); $rights = $user->user->getRights(); - $this->assertArrayHasKey( $key, $data[0]['query']['pages'] ); - $this->assertArrayHasKey( 'edittoken', $data[0]['query']['pages'][$key] ); - $this->assertArrayHasKey( 'movetoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'edittoken', $tokens ); + $this->assertArrayHasKey( 'movetoken', $tokens ); if ( isset( $rights['delete'] ) ) { - $this->assertArrayHasKey( 'deletetoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'deletetoken', $tokens ); } if ( isset( $rights['block'] ) ) { - $this->assertArrayHasKey( 'blocktoken', $data[0]['query']['pages'][$key] ); - $this->assertArrayHasKey( 'unblocktoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'blocktoken', $tokens ); + $this->assertArrayHasKey( 'unblocktoken', $tokens ); } if ( isset( $rights['protect'] ) ) { - $this->assertArrayHasKey( 'protecttoken', $data[0]['query']['pages'][$key] ); + $this->assertArrayHasKey( 'protecttoken', $tokens ); } - return $data; + return $tokens; } } diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php index b84292e3..94ef9c68 100644 --- a/tests/phpunit/includes/api/ApiTestCase.php +++ b/tests/phpunit/includes/api/ApiTestCase.php @@ -1,4 +1,4 @@ -<?php +<?php abstract class ApiTestCase extends MediaWikiLangTestCase { protected static $apiUrl; @@ -8,15 +8,13 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { */ protected $apiContext; - function setUp() { - global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer; + protected function setUp() { + global $wgServer; parent::setUp(); self::$apiUrl = $wgServer . wfScript( 'api' ); - $wgMemc = new EmptyBagOStuff(); - $wgContLang = Language::factory( 'en' ); - $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' ); - $wgRequest = new FauxRequest( array() ); + + ApiQueryInfo::resetTokenCache(); // tokens are invalid because we cleared the session self::$users = array( 'sysop' => new TestUser( @@ -33,21 +31,56 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { ) ); - $wgUser = self::$users['sysop']->user; + $this->setMwGlobals( array( + 'wgMemc' => new EmptyBagOStuff(), + 'wgAuth' => new StubObject( 'wgAuth', 'AuthPlugin' ), + 'wgRequest' => new FauxRequest( array() ), + 'wgUser' => self::$users['sysop']->user, + ) ); $this->apiContext = new ApiTestContext(); + } + + /** + * Edits or creates a page/revision + * @param $pageName string page title + * @param $text string content of the page + * @param $summary string optional summary string for the revision + * @param $defaultNs int optional namespace id + * @return array as returned by WikiPage::doEditContent() + */ + protected function editPage( $pageName, $text, $summary = '', $defaultNs = NS_MAIN ) { + $title = Title::newFromText( $pageName, $defaultNs ); + $page = WikiPage::factory( $title ); + return $page->doEditContent( ContentHandler::makeContent( $text, $title ), $summary ); } - protected function doApiRequest( Array $params, Array $session = null, $appendModule = false, User $user = null ) { + /** + * Does the API request and returns the result. + * + * The returned value is an array containing + * - the result data (array) + * - the request (WebRequest) + * - the session data of the request (array) + * - if $appendModule is true, the Api module $module + * + * @param array $params + * @param array|null $session + * @param bool $appendModule + * @param User|null $user + * + * @return array + */ + protected function doApiRequest( array $params, array $session = null, $appendModule = false, User $user = null ) { global $wgRequest, $wgUser; if ( is_null( $session ) ) { - # re-use existing global session by default + // re-use existing global session by default $session = $wgRequest->getSessionArray(); } - # set up global environment + // set up global environment if ( $user ) { $wgUser = $user; } @@ -55,21 +88,22 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $wgRequest = new FauxRequest( $params, true, $session ); RequestContext::getMain()->setRequest( $wgRequest ); - # set up local environment + // set up local environment $context = $this->apiContext->newTestContext( $wgRequest, $wgUser ); $module = new ApiMain( $context, true ); - # run it! + // run it! $module->execute(); - # construct result + // construct result $results = array( $module->getResultData(), $context->getRequest(), $context->getRequest()->getSessionArray() ); - if( $appendModule ) { + + if ( $appendModule ) { $results[] = $module; } @@ -83,8 +117,10 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { * @param $params Array: key-value API params * @param $session Array|null: session array * @param $user User|null A User object for the context + * @return result of the API call + * @throws Exception in case wsToken is not set in the session */ - protected function doApiRequestWithToken( Array $params, Array $session = null, User $user = null ) { + protected function doApiRequestWithToken( array $params, array $session = null, User $user = null ) { global $wgRequest; if ( $session === null ) { @@ -96,42 +132,67 @@ abstract class ApiTestCase extends MediaWikiLangTestCase { $session['wsEditToken'] = $session['wsToken']; // add token to request parameters $params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX; + return $this->doApiRequest( $params, $session, false, $user ); } else { throw new Exception( "request data not in right format" ); } } - protected function doLogin() { + protected function doLogin( $user = 'sysop' ) { + if ( !array_key_exists( $user, self::$users ) ) { + throw new MWException( "Can not log in to undefined user $user" ); + } + $data = $this->doApiRequest( array( 'action' => 'login', - 'lgname' => self::$users['sysop']->username, - 'lgpassword' => self::$users['sysop']->password ) ); + 'lgname' => self::$users[ $user ]->username, + 'lgpassword' => self::$users[ $user ]->password ) ); $token = $data[0]['login']['token']; - $data = $this->doApiRequest( array( - 'action' => 'login', - 'lgtoken' => $token, - 'lgname' => self::$users['sysop']->username, - 'lgpassword' => self::$users['sysop']->password - ), $data[2] ); + $data = $this->doApiRequest( + array( + 'action' => 'login', + 'lgtoken' => $token, + 'lgname' => self::$users[ $user ]->username, + 'lgpassword' => self::$users[ $user ]->password, + ), + $data[2] + ); return $data; } protected function getTokenList( $user, $session = null ) { $data = $this->doApiRequest( array( - 'action' => 'query', - 'titles' => 'Main Page', - 'intoken' => 'edit|delete|protect|move|block|unblock|watch', - 'prop' => 'info' ), $session, false, $user->user ); - return $data; + 'action' => 'tokens', + 'type' => 'edit|delete|protect|move|block|unblock|watch' + ), $session, false, $user->user ); + + if ( !array_key_exists( 'tokens', $data[0] ) ) { + throw new MWException( 'Api failed to return a token list' ); + } + + return $data[0]['tokens']; + } + + public function testApiTestGroup() { + $groups = PHPUnit_Util_Test::getGroups( get_class( $this ) ); + $constraint = PHPUnit_Framework_Assert::logicalOr( + $this->contains( 'medium' ), + $this->contains( 'large' ) + ); + $this->assertThat( $groups, $constraint, + 'ApiTestCase::setUp can be slow, tests must be "medium" or "large"' + ); } } class UserWrapper { - public $userName, $password, $user; + public $userName; + public $password; + public $user; public function __construct( $userName, $password, $group = '' ) { $this->userName = $userName; @@ -153,10 +214,14 @@ class UserWrapper { } class MockApi extends ApiBase { - public function execute() { } - public function getVersion() { } + public function execute() { + } - public function __construct() { } + public function getVersion() { + } + + public function __construct() { + } public function getAllowedParams() { return array( @@ -182,6 +247,7 @@ class ApiTestContext extends RequestContext { if ( $user !== null ) { $context->setUser( $user ); } + return $context; } } diff --git a/tests/phpunit/includes/api/ApiTestCaseUpload.php b/tests/phpunit/includes/api/ApiTestCaseUpload.php index 39c79547..7e18b6ed 100644 --- a/tests/phpunit/includes/api/ApiTestCaseUpload.php +++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php @@ -8,19 +8,23 @@ abstract class ApiTestCaseUpload extends ApiTestCase { /** * Fixture -- run before every test */ - public function setUp() { - global $wgEnableUploads, $wgEnableAPI; + protected function setUp() { parent::setUp(); - $wgEnableUploads = true; - $wgEnableAPI = true; + $this->setMwGlobals( array( + 'wgEnableUploads' => true, + 'wgEnableAPI' => true, + ) ); + wfSetupSession(); $this->clearFakeUploads(); } - public function tearDown() { + protected function tearDown() { $this->clearTempUpload(); + + parent::tearDown(); } /** @@ -43,7 +47,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase { // see if it now doesn't exist; reload $title = Title::newFromText( $title->getText(), NS_FILE ); } - return ! ( $title && $title instanceof Title && $title->exists() ); + + return !( $title && $title instanceof Title && $title->exists() ); } /** @@ -54,7 +59,6 @@ abstract class ApiTestCaseUpload extends ApiTestCase { return $this->deleteFileByTitle( Title::newFromText( $fileName, NS_FILE ) ); } - /** * Helper function -- given a file on the filesystem, find matching content in the db (and associated articles) and remove them. * @param $filePath String: path to file on the filesystem @@ -66,6 +70,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { foreach ( $dupes as $dupe ) { $success &= $this->deleteFileByTitle( $dupe->getTitle() ); } + return $success; } @@ -81,7 +86,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase { $tmpName = tempnam( wfTempDir(), "" ); if ( !file_exists( $filePath ) ) { throw new Exception( "$filePath doesn't exist!" ); - }; + } if ( !copy( $filePath, $tmpName ) ) { throw new Exception( "couldn't copy $filePath to $tmpName" ); @@ -93,43 +98,43 @@ abstract class ApiTestCaseUpload extends ApiTestCase { throw new Exception( "couldn't stat $tmpName" ); } - $_FILES[ $fieldName ] = array( - 'name' => $fileName, - 'type' => $type, - 'tmp_name' => $tmpName, - 'size' => $size, - 'error' => null + $_FILES[$fieldName] = array( + 'name' => $fileName, + 'type' => $type, + 'tmp_name' => $tmpName, + 'size' => $size, + 'error' => null ); return true; - } - function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ){ + + function fakeUploadChunk( $fieldName, $fileName, $type, & $chunkData ) { $tmpName = tempnam( wfTempDir(), "" ); - // copy the chunk data to temp location: + // copy the chunk data to temp location: if ( !file_put_contents( $tmpName, $chunkData ) ) { throw new Exception( "couldn't copy chunk data to $tmpName" ); } - + clearstatcache(); $size = filesize( $tmpName ); if ( $size === false ) { throw new Exception( "couldn't stat $tmpName" ); } - - $_FILES[ $fieldName ] = array( - 'name' => $fileName, - 'type' => $type, - 'tmp_name' => $tmpName, - 'size' => $size, - 'error' => null + + $_FILES[$fieldName] = array( + 'name' => $fileName, + 'type' => $type, + 'tmp_name' => $tmpName, + 'size' => $size, + 'error' => null ); } function clearTempUpload() { - if( isset( $_FILES['file']['tmp_name'] ) ) { + if ( isset( $_FILES['file']['tmp_name'] ) ) { $tmp = $_FILES['file']['tmp_name']; - if( file_exists( $tmp ) ) { + if ( file_exists( $tmp ) ) { unlink( $tmp ); } } @@ -141,8 +146,4 @@ abstract class ApiTestCaseUpload extends ApiTestCase { function clearFakeUploads() { $_FILES = array(); } - - - - } diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php index 642fed05..1540af55 100644 --- a/tests/phpunit/includes/api/ApiUploadTest.php +++ b/tests/phpunit/includes/api/ApiUploadTest.php @@ -16,7 +16,7 @@ // TODO: port the other Upload tests, and other API tests to this framework -require_once( 'ApiTestCaseUpload.php' ); +require_once 'ApiTestCaseUpload.php'; /** * @group Database @@ -27,12 +27,11 @@ require_once( 'ApiTestCaseUpload.php' ); * This is pretty sucky... needs to be prettified. */ class ApiUploadTest extends ApiTestCaseUpload { - /** * Testing login * XXX this is a funny way of getting session context */ - function testLogin() { + public function testLogin() { $user = self::$users['uploader']; $params = array( @@ -59,8 +58,8 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertArrayHasKey( 'lgtoken', $result['login'] ); $this->assertNotEmpty( $session, 'API Login must return a session' ); - return $session; + return $session; } /** @@ -107,8 +106,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -119,8 +117,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -129,7 +126,7 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; @@ -141,7 +138,7 @@ class ApiUploadTest extends ApiTestCaseUpload { } $this->assertTrue( isset( $result['upload'] ) ); $this->assertEquals( 'Success', $result['upload']['result'] ); - $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] ); + $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertFalse( $exception ); @@ -162,7 +159,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -171,7 +168,7 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; @@ -199,8 +196,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -216,12 +212,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); // first upload .... should succeed - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -238,7 +234,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // second upload with the same name (but different content) - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePaths[1] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -272,8 +268,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -292,16 +287,16 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileNames[0], 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for " . $fileNames[0], + 'text' => "This is the page text for " . $fileNames[0], ); - if (! $this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileNames[0], $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -310,10 +305,9 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertEquals( 'Success', $result['upload']['result'] ); $this->assertFalse( $exception ); - // second upload with the same content (but different name) - if (! $this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) { + if ( !$this->fakeUploadFile( 'file', $fileNames[1], $mimeType, $filePaths[0] ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } @@ -322,12 +316,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filename' => $fileNames[1], 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for " . $fileNames[1], + 'text' => "This is the page text for " . $fileNames[1], ); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; @@ -344,13 +338,13 @@ class ApiUploadTest extends ApiTestCaseUpload { unlink( $filePaths[0] ); } - /** * @depends testLogin */ public function testUploadStash( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere + $this->setMwGlobals( array( + 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + ) ); $extension = 'png'; $mimeType = 'image/png'; @@ -358,8 +352,7 @@ class ApiUploadTest extends ApiTestCaseUpload { try { $randomImageGenerator = new RandomImageGenerator(); $filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() ); - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -370,22 +363,22 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - if (! $this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { + if ( !$this->fakeUploadFile( 'file', $fileName, $mimeType, $filePath ) ) { $this->markTestIncomplete( "Couldn't upload file!\n" ); } $params = array( 'action' => 'upload', - 'stash' => 1, + 'stash' => 1, 'filename' => $fileName, 'file' => 'dummy content', 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName", + 'text' => "This is the page text for $fileName", ); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); // FIXME: leaks a temporary file } catch ( UsageException $e ) { $exception = true; @@ -393,7 +386,7 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertFalse( $exception ); $this->assertTrue( isset( $result['upload'] ) ); $this->assertEquals( 'Success', $result['upload']['result'] ); - $this->assertEquals( $fileSize, ( int )$result['upload']['imageinfo']['size'] ); + $this->assertEquals( $fileSize, (int)$result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); $this->assertEquals( $result['upload']['sessionkey'], $result['upload']['filekey'] ); @@ -408,13 +401,13 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filekey' => $filekey, 'filename' => $fileName, 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName, altered", + 'text' => "This is the page text for $fileName, altered", ); $this->clearFakeUploads(); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -427,15 +420,15 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFilename( $fileName ); unlink( $filePath ); } - - + /** * @depends testLogin */ public function testUploadChunks( $session ) { - global $wgUser; - $wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere - + $this->setMwGlobals( array( + 'wgUser' => self::$users['uploader']->user, // @todo FIXME: still used somewhere + ) ); + $chunkSize = 1048576; // Download a large image file // ( using RandomImageGenerator for large files is not stable ) @@ -444,11 +437,10 @@ class ApiUploadTest extends ApiTestCaseUpload { $filePath = wfTempDir() . '/Oberaargletscher_from_Oberaar.jpg'; try { // Only download if the file is not avaliable in the temp location: - if( !is_file( $filePath ) ){ - copy($url, $filePath); + if ( !is_file( $filePath ) ) { + copy( $url, $filePath ); } - } - catch ( Exception $e ) { + } catch ( Exception $e ) { $this->markTestIncomplete( $e->getMessage() ); } @@ -458,44 +450,44 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->deleteFileByFileName( $fileName ); $this->deleteFileByContent( $filePath ); - // Base upload params: + // Base upload params: $params = array( 'action' => 'upload', - 'stash' => 1, + 'stash' => 1, 'filename' => $fileName, 'filesize' => $fileSize, 'offset' => 0, ); - + // Upload chunks $chunkSessionKey = false; $resultOffset = 0; - // Open the file: - $handle = @fopen ($filePath, "r"); - if( $handle === false ){ + // Open the file: + $handle = @fopen( $filePath, "r" ); + if ( $handle === false ) { $this->markTestIncomplete( "could not open file: $filePath" ); } - while (!feof ($handle)) { + while ( !feof( $handle ) ) { // Get the current chunk $chunkData = @fread( $handle, $chunkSize ); // Upload the current chunk into the $_FILE object: $this->fakeUploadChunk( 'chunk', 'blob', $mimeType, $chunkData ); - + // Check for chunkSessionKey - if( !$chunkSessionKey ){ + if ( !$chunkSessionKey ) { // Upload fist chunk ( and get the session key ) try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } - // Make sure we got a valid chunk continue: + // Make sure we got a valid chunk continue: $this->assertTrue( isset( $result['upload'] ) ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); - // If we don't get a session key mark test incomplete. - if( ! isset( $result['upload']['filekey'] ) ){ + // If we don't get a session key mark test incomplete. + if ( !isset( $result['upload']['filekey'] ) ) { $this->markTestIncomplete( "no filekey provided" ); } $chunkSessionKey = $result['upload']['filekey']; @@ -513,17 +505,17 @@ class ApiUploadTest extends ApiTestCaseUpload { $this->assertEquals( $resultOffset, $params['offset'] ); // Upload current chunk try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $this->markTestIncomplete( $e->getMessage() ); } - // Make sure we got a valid chunk continue: + // Make sure we got a valid chunk continue: $this->assertTrue( isset( $result['upload'] ) ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); - - // Check if we were on the last chunk: - if( $params['offset'] + $chunkSize >= $fileSize ){ + + // Check if we were on the last chunk: + if ( $params['offset'] + $chunkSize >= $fileSize ) { $this->assertEquals( 'Success', $result['upload']['result'] ); break; } else { @@ -531,11 +523,11 @@ class ApiUploadTest extends ApiTestCaseUpload { // update $resultOffset $resultOffset = $result['upload']['offset']; } - } - fclose ($handle); - + } + fclose( $handle ); + // Check that we got a valid file result: - wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n"); + wfDebug( __METHOD__ . " hohoh filesize {$fileSize} info {$result['upload']['imageinfo']['size']}\n\n" ); $this->assertEquals( $fileSize, $result['upload']['imageinfo']['size'] ); $this->assertEquals( $mimeType, $result['upload']['imageinfo']['mime'] ); $this->assertTrue( isset( $result['upload']['filekey'] ) ); @@ -547,12 +539,12 @@ class ApiUploadTest extends ApiTestCaseUpload { 'filekey' => $filekey, 'filename' => $fileName, 'comment' => 'dummy comment', - 'text' => "This is the page text for $fileName, altered", + 'text' => "This is the page text for $fileName, altered", ); $this->clearFakeUploads(); $exception = false; try { - list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session, + list( $result ) = $this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user ); } catch ( UsageException $e ) { $exception = true; @@ -563,7 +555,7 @@ class ApiUploadTest extends ApiTestCaseUpload { // clean up $this->deleteFileByFilename( $fileName ); - // don't remove downloaded temporary file for fast subquent tests. + // don't remove downloaded temporary file for fast subquent tests. //unlink( $filePath ); } } diff --git a/tests/phpunit/includes/api/ApiWatchTest.php b/tests/phpunit/includes/api/ApiWatchTest.php index d2e98152..028ea9ff 100644 --- a/tests/phpunit/includes/api/ApiWatchTest.php +++ b/tests/phpunit/includes/api/ApiWatchTest.php @@ -3,35 +3,29 @@ /** * @group API * @group Database + * @group medium * @todo This test suite is severly broken and need a full review */ class ApiWatchTest extends ApiTestCase { - - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } function getTokens() { - $data = $this->getTokenList( self::$users['sysop'] ); - - $keys = array_keys( $data[0]['query']['pages'] ); - $key = array_pop( $keys ); - $pageinfo = $data[0]['query']['pages'][$key]; - - return $pageinfo; + return $this->getTokenList( self::$users['sysop'] ); } /** */ - function testWatchEdit() { - $pageinfo = $this->getTokens(); + public function testWatchEdit() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'edit', - 'title' => 'UTPage', + 'title' => 'Help:UTPage', // Help namespace is hopefully wikitext 'text' => 'new text', - 'token' => $pageinfo['edittoken'], + 'token' => $tokens['edittoken'], 'watchlist' => 'watch' ) ); $this->assertArrayHasKey( 'edit', $data[0] ); $this->assertArrayHasKey( 'result', $data[0]['edit'] ); @@ -43,9 +37,8 @@ class ApiWatchTest extends ApiTestCase { /** * @depends testWatchEdit */ - function testWatchClear() { - - $pageinfo = $this->getTokens(); + public function testWatchClear() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'query', @@ -59,7 +52,7 @@ class ApiWatchTest extends ApiTestCase { 'action' => 'watch', 'title' => $page['title'], 'unwatch' => true, - 'token' => $pageinfo['watchtoken'] ) ); + 'token' => $tokens['watchtoken'] ) ); } } $data = $this->doApiRequest( array( @@ -74,14 +67,13 @@ class ApiWatchTest extends ApiTestCase { /** */ - function testWatchProtect() { - - $pageinfo = $this->getTokens(); + public function testWatchProtect() { + $tokens = $this->getTokens(); $data = $this->doApiRequest( array( 'action' => 'protect', - 'token' => $pageinfo['protecttoken'], - 'title' => 'UTPage', + 'token' => $tokens['protecttoken'], + 'title' => 'Help:UTPage', 'protections' => 'edit=sysop', 'watchlist' => 'unwatch' ) ); @@ -93,18 +85,17 @@ class ApiWatchTest extends ApiTestCase { /** */ - function testGetRollbackToken() { + public function testGetRollbackToken() { + $this->getTokens(); - $pageinfo = $this->getTokens(); - - if ( !Title::newFromText( 'UTPage' )->exists() ) { - $this->markTestSkipped( "The article [[UTPage]] does not exist" ); //TODO: just create it? + if ( !Title::newFromText( 'Help:UTPage' )->exists() ) { + $this->markTestSkipped( "The article [[Help:UTPage]] does not exist" ); //TODO: just create it? } $data = $this->doApiRequest( array( 'action' => 'query', 'prop' => 'revisions', - 'titles' => 'UTPage', + 'titles' => 'Help:UTPage', 'rvtoken' => 'rollback' ) ); $this->assertArrayHasKey( 'query', $data[0] ); @@ -113,7 +104,7 @@ class ApiWatchTest extends ApiTestCase { $key = array_pop( $keys ); if ( isset( $data[0]['query']['pages'][$key]['missing'] ) ) { - $this->markTestSkipped( "Target page (UTPage) doesn't exist" ); + $this->markTestSkipped( "Target page (Help:UTPage) doesn't exist" ); } $this->assertArrayHasKey( 'pageid', $data[0]['query']['pages'][$key] ); @@ -130,7 +121,7 @@ class ApiWatchTest extends ApiTestCase { * * @depends testGetRollbackToken */ - function testWatchRollback( $data ) { + public function testWatchRollback( $data ) { $keys = array_keys( $data[0]['query']['pages'] ); $key = array_pop( $keys ); $pageinfo = $data[0]['query']['pages'][$key]; @@ -139,38 +130,19 @@ class ApiWatchTest extends ApiTestCase { try { $data = $this->doApiRequest( array( 'action' => 'rollback', - 'title' => 'UTPage', + 'title' => 'Help:UTPage', 'user' => $revinfo['user'], 'token' => $pageinfo['rollbacktoken'], 'watchlist' => 'watch' ) ); $this->assertArrayHasKey( 'rollback', $data[0] ); $this->assertArrayHasKey( 'title', $data[0]['rollback'] ); - } catch( UsageException $ue ) { - if( $ue->getCodeString() == 'onlyauthor' ) { - $this->markTestIncomplete( "Only one author to 'UTPage', cannot test rollback" ); + } catch ( UsageException $ue ) { + if ( $ue->getCodeString() == 'onlyauthor' ) { + $this->markTestIncomplete( "Only one author to 'Help:UTPage', cannot test rollback" ); } else { $this->fail( "Received error '" . $ue->getCodeString() . "'" ); } } } - - /** - */ - function testWatchDelete() { - $pageinfo = $this->getTokens(); - - $data = $this->doApiRequest( array( - 'action' => 'delete', - 'token' => $pageinfo['deletetoken'], - 'title' => 'UTPage' ) ); - $this->assertArrayHasKey( 'delete', $data[0] ); - $this->assertArrayHasKey( 'title', $data[0]['delete'] ); - - $data = $this->doApiRequest( array( - 'action' => 'query', - 'list' => 'watchlist' ) ); - - $this->markTestIncomplete( 'This test needs to verify the deleted article was added to the users watchlist' ); - } } diff --git a/tests/phpunit/includes/api/PrefixUniquenessTest.php b/tests/phpunit/includes/api/PrefixUniquenessTest.php index 69b01ea7..d9be85e3 100644 --- a/tests/phpunit/includes/api/PrefixUniquenessTest.php +++ b/tests/phpunit/includes/api/PrefixUniquenessTest.php @@ -1,14 +1,15 @@ <?php /** - * Checks that all API query modules, core and extensions, have unique prefixes + * Checks that all API query modules, core and extensions, have unique prefixes. + * * @group API */ class PrefixUniquenessTest extends MediaWikiTestCase { public function testPrefixes() { $main = new ApiMain( new FauxRequest() ); $query = new ApiQuery( $main, 'foo', 'bar' ); - $modules = $query->getModules(); + $modules = $query->getModuleManager()->getNamesWithClasses(); $prefixes = array(); foreach ( $modules as $name => $class ) { diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php index 8b6a3849..59756b21 100644 --- a/tests/phpunit/includes/api/RandomImageGenerator.php +++ b/tests/phpunit/includes/api/RandomImageGenerator.php @@ -27,14 +27,14 @@ class RandomImageGenerator { private $dictionaryFile; - private $minWidth = 400 ; - private $maxWidth = 800 ; - private $minHeight = 400 ; - private $maxHeight = 800 ; - private $shapesToDraw = 5 ; + private $minWidth = 400; + private $maxWidth = 800; + private $minHeight = 400; + private $maxHeight = 800; + private $shapesToDraw = 5; /** - * Orientations: 0th row, 0th column, EXIF orientation code, rotation 2x2 matrix that is opposite of orientation + * Orientations: 0th row, 0th column, Exif orientation code, rotation 2x2 matrix that is opposite of orientation * n.b. we do not handle the 'flipped' orientations, which is why there is no entry for 2, 4, 5, or 7. Those * seem to be rare in real images anyway * (we also would need a non-symmetric shape for the images to test those, like a letter F) @@ -76,11 +76,13 @@ class RandomImageGenerator { // find the dictionary file, to generate random names if ( !isset( $this->dictionaryFile ) ) { - foreach ( array( + foreach ( + array( '/usr/share/dict/words', '/usr/dict/words', - __DIR__ . '/words.txt' ) - as $dictionaryFile ) { + __DIR__ . '/words.txt' + ) as $dictionaryFile + ) { if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) { $this->dictionaryFile = $dictionaryFile; break; @@ -103,9 +105,10 @@ class RandomImageGenerator { function writeImages( $number, $format = 'jpg', $dir = null ) { $filenames = $this->getRandomFilenames( $number, $format, $dir ); $imageWriteMethod = $this->getImageWriteMethod( $format ); - foreach( $filenames as $filename ) { + foreach ( $filenames as $filename ) { $this->{$imageWriteMethod}( $this->getImageSpec(), $format, $filename ); } + return $filenames; } @@ -144,7 +147,7 @@ class RandomImageGenerator { $dir = getcwd(); } $filenames = array(); - foreach( $this->getRandomWordPairs( $number ) as $pair ) { + foreach ( $this->getRandomWordPairs( $number ) as $pair ) { $basename = $pair[0] . '_' . $pair[1]; if ( !is_null( $extension ) ) { $basename .= '.' . $extension; @@ -154,7 +157,6 @@ class RandomImageGenerator { } return $filenames; - } @@ -181,20 +183,19 @@ class RandomImageGenerator { } $originX = mt_rand( -1 * $radius, $spec['width'] + $radius ); $originY = mt_rand( -1 * $radius, $spec['height'] + $radius ); - $angle = mt_rand( 0, ( 3.141592/2 ) * $radius ) / $radius; + $angle = mt_rand( 0, ( 3.141592 / 2 ) * $radius ) / $radius; $legDeltaX = round( $radius * sin( $angle ) ); $legDeltaY = round( $radius * cos( $angle ) ); $draw = array(); $draw['fill'] = $this->getRandomColor(); $draw['shape'] = array( - array( 'x' => $originX, 'y' => $originY - $radius ), - array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), - array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), - array( 'x' => $originX, 'y' => $originY - $radius ) + array( 'x' => $originX, 'y' => $originY - $radius ), + array( 'x' => $originX + $legDeltaX, 'y' => $originY + $legDeltaY ), + array( 'x' => $originX - $legDeltaX, 'y' => $originY + $legDeltaY ), + array( 'x' => $originX, 'y' => $originY - $radius ) ); $draws[] = $draw; - } $spec['draws'] = $draws; @@ -214,6 +215,7 @@ class RandomImageGenerator { foreach ( $shape as $point ) { $points[] = $point['x'] . ',' . $point['y']; } + return join( " ", $points ); } @@ -235,12 +237,13 @@ class RandomImageGenerator { $shape = $g->addChild( 'polygon' ); $shape->addAttribute( 'fill', $drawSpec['fill'] ); $shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) ); - }; - if ( ! $fh = fopen( $filename, 'w' ) ) { + } + + if ( !$fh = fopen( $filename, 'w' ) ) { throw new Exception( "couldn't open $filename for writing" ); } fwrite( $fh, $svg->asXML() ); - if ( !fclose($fh) ) { + if ( !fclose( $fh ) ) { throw new Exception( "couldn't close $filename" ); } } @@ -262,7 +265,7 @@ class RandomImageGenerator { */ $orientation = self::$orientations[0]; // default is normal orientation if ( $format == 'jpg' ) { - $orientation = self::$orientations[ array_rand( self::$orientations ) ]; + $orientation = self::$orientations[array_rand( self::$orientations )]; $spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); } @@ -301,7 +304,7 @@ class RandomImageGenerator { /** * Given an image specification, produce rotated version - * This is used when simulating a rotated image capture with EXIF orientation + * This is used when simulating a rotated image capture with Exif orientation * @param $spec Object returned by getImageSpec * @param $matrix 2x2 transformation matrix * @return transformed Spec @@ -321,12 +324,12 @@ class RandomImageGenerator { $tSpec['height'] = abs( $dims['y'] ); $tSpec['fill'] = $spec['fill']; $tSpec['draws'] = array(); - foreach( $spec['draws'] as $draw ) { + foreach ( $spec['draws'] as $draw ) { $tDraw = array( 'fill' => $draw['fill'], 'shape' => array() ); - foreach( $draw['shape'] as $point ) { + foreach ( $draw['shape'] as $point ) { $tPoint = self::matrixMultiply2x2( $matrix, $point['x'], $point['y'] ); $tPoint['x'] += $correctionX; $tPoint['y'] += $correctionY; @@ -334,6 +337,7 @@ class RandomImageGenerator { } $tSpec['draws'][] = $tDraw; } + return $tSpec; } @@ -357,7 +361,7 @@ class RandomImageGenerator { * * Sample command line: * $ convert -size 100x60 xc:rgb(90,87,45) \ - * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ + * -draw 'fill rgb(12,34,56) polygon 41,39 44,57 50,57 41,39' \ * -draw 'fill rgb(99,123,231) circle 59,39 56,57' \ * -draw 'fill rgb(240,12,32) circle 50,21 50,3' filename.png * @@ -370,7 +374,7 @@ class RandomImageGenerator { $args = array(); $args[] = "-size " . wfEscapeShellArg( $spec['width'] . 'x' . $spec['height'] ); $args[] = wfEscapeShellArg( "xc:" . $spec['fill'] ); - foreach( $spec['draws'] as $draw ) { + foreach ( $spec['draws'] as $draw ) { $fill = $draw['fill']; $polygon = self::shapePointsToString( $draw['shape'] ); $drawCommand = "fill $fill polygon $polygon"; @@ -381,6 +385,7 @@ class RandomImageGenerator { $command = wfEscapeShellArg( $wgImageMagickConvertCommand ) . " " . implode( " ", $args ); $retval = null; wfShellExec( $command, $retval ); + return ( $retval === 0 ); } @@ -391,10 +396,11 @@ class RandomImageGenerator { */ public function getRandomColor() { $components = array(); - for ($i = 0; $i <= 2; $i++ ) { + for ( $i = 0; $i <= 2; $i++ ) { $components[] = mt_rand( 0, 255 ); } - return 'rgb(' . join(', ', $components) . ')'; + + return 'rgb(' . join( ', ', $components ) . ')'; } /** @@ -408,13 +414,13 @@ class RandomImageGenerator { // construct pairs of words $pairs = array(); $count = count( $lines ); - for( $i = 0; $i < $count; $i += 2 ) { - $pairs[] = array( $lines[$i], $lines[$i+1] ); + for ( $i = 0; $i < $count; $i += 2 ) { + $pairs[] = array( $lines[$i], $lines[$i + 1] ); } + return $pairs; } - /** * Return N random lines from a file * @@ -438,17 +444,17 @@ class RandomImageGenerator { */ $fh = fopen( $filepath, "r" ); if ( !$fh ) { - throw new Exception( "couldn't open $filepath" ); + throw new Exception( "couldn't open $filepath" ); } $line_number = 0; $max_index = $number_desired - 1; - while( !feof( $fh ) ) { + while ( !feof( $fh ) ) { $line = fgets( $fh ); if ( $line !== false ) { $line_number++; $line = trim( $line ); if ( mt_rand( 0, $line_number ) <= $max_index ) { - $lines[ mt_rand( 0, $max_index ) ] = $line; + $lines[mt_rand( 0, $max_index )] = $line; } } } @@ -459,5 +465,4 @@ class RandomImageGenerator { return $lines; } - } diff --git a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php index 8209f591..a0bbb2dc 100644 --- a/tests/phpunit/includes/api/format/ApiFormatPhpTest.php +++ b/tests/phpunit/includes/api/format/ApiFormatPhpTest.php @@ -3,17 +3,15 @@ /** * @group API * @group Database + * @group medium */ class ApiFormatPhpTest extends ApiFormatTestBase { - function testValidPhpSyntax() { - + public function testValidPhpSyntax() { + $data = $this->apiRequest( 'php', array( 'action' => 'query', 'meta' => 'siteinfo' ) ); - + $this->assertInternalType( 'array', unserialize( $data ) ); - $this->assertGreaterThan( 0, count( (array) $data ) ); - - + $this->assertGreaterThan( 0, count( (array)$data ) ); } - } diff --git a/tests/phpunit/includes/api/format/ApiFormatTestBase.php b/tests/phpunit/includes/api/format/ApiFormatTestBase.php index a0b7b020..153f2cf4 100644 --- a/tests/phpunit/includes/api/format/ApiFormatTestBase.php +++ b/tests/phpunit/includes/api/format/ApiFormatTestBase.php @@ -5,7 +5,7 @@ abstract class ApiFormatTestBase extends ApiTestCase { $data = parent::doApiRequest( $params, $data, true ); $module = $data[3]; - + $printer = $module->createPrinterByName( $format ); $printer->setUnescapeAmps( false ); @@ -14,7 +14,7 @@ abstract class ApiFormatTestBase extends ApiTestCase { ob_start(); $printer->execute(); $out = ob_get_clean(); - + $printer->closePrinter(); return $out; diff --git a/tests/phpunit/includes/api/generateRandomImages.php b/tests/phpunit/includes/api/generateRandomImages.php index ee345623..87f5c4c0 100644 --- a/tests/phpunit/includes/api/generateRandomImages.php +++ b/tests/phpunit/includes/api/generateRandomImages.php @@ -5,12 +5,9 @@ * @file */ -// Evaluate the include path relative to this file -$IP = dirname( dirname( dirname( dirname( __DIR__ ) ) ) ); - // Start up MediaWiki in command-line mode -require_once( "$IP/maintenance/Maintenance.php" ); -require( __DIR__ . "/RandomImageGenerator.php" ); +require_once __DIR__ . "/../../../../maintenance/Maintenance.php"; +require __DIR__ . "/RandomImageGenerator.php"; class GenerateRandomImages extends Maintenance { @@ -46,6 +43,4 @@ class GenerateRandomImages extends Maintenance { } $maintClass = 'GenerateRandomImages'; -require( RUN_MAINTENANCE_IF_MAIN ); - - +require RUN_MAINTENANCE_IF_MAIN; diff --git a/tests/phpunit/includes/api/query/ApiQueryBasicTest.php b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php new file mode 100644 index 00000000..1a2aa832 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryBasicTest.php @@ -0,0 +1,395 @@ +<?php +/** + * + * + * Created on Feb 6, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +require_once 'ApiQueryTestBase.php'; + +/** These tests validate basic functionality of the api query module + * + * @group API + * @group Database + * @group medium + */ +class ApiQueryBasicTest extends ApiQueryTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + if ( Title::newFromText( 'AQBT-All' )->exists() ) { + return; + } + + // Ordering is important, as it will be returned in the same order as stored in the index + $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' ); + $this->editPage( 'AQBT-Categories', '[[Category:AQBT-Cat]]' ); + $this->editPage( 'AQBT-Links', '[[AQBT-All]] [[AQBT-Categories]] [[AQBT-Templates]]' ); + $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' ); + $this->editPage( 'AQBT-T', 'Content', '', NS_TEMPLATE ); + + // Refresh due to the bug with listing transclusions as links if they don't exist + $this->editPage( 'AQBT-All', '[[Category:AQBT-Cat]] [[AQBT-Links]] {{AQBT-T}}' ); + $this->editPage( 'AQBT-Templates', '{{AQBT-T}}' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + private static $links = array( + array( 'prop' => 'links', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'links' => array( + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + ) + ) + ) ) + ); + + private static $templates = array( + array( 'prop' => 'templates', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + ) + ) + ) ) + ); + + private static $categories = array( + array( 'prop' => 'categories', 'titles' => 'AQBT-All' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All', + 'categories' => array( + array( 'ns' => 14, 'title' => 'Category:AQBT-Cat' ), + ) + ) + ) ) + ); + + private static $allpages = array( + array( 'list' => 'allpages', 'apprefix' => 'AQBT-' ), + array( 'allpages' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ), + array( 'pageid' => 3, 'ns' => 0, 'title' => 'AQBT-Links' ), + array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $alllinks = array( + array( 'list' => 'alllinks', 'alprefix' => 'AQBT-' ), + array( 'alllinks' => array( + array( 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'ns' => 0, 'title' => 'AQBT-Categories' ), + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + array( 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $alltransclusions = array( + array( 'list' => 'alltransclusions', 'atprefix' => 'AQBT-' ), + array( 'alltransclusions' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ), + ) ) + ); + + private static $allcategories = array( + array( 'list' => 'allcategories', 'acprefix' => 'AQBT-' ), + array( 'allcategories' => array( + array( '*' => 'AQBT-Cat' ), + ) ) + ); + + private static $backlinks = array( + array( 'list' => 'backlinks', 'bltitle' => 'AQBT-Links' ), + array( 'backlinks' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + ) ) + ); + + private static $embeddedin = array( + array( 'list' => 'embeddedin', 'eititle' => 'Template:AQBT-T' ), + array( 'embeddedin' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 4, 'ns' => 0, 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $categorymembers = array( + array( 'list' => 'categorymembers', 'cmtitle' => 'Category:AQBT-Cat' ), + array( 'categorymembers' => array( + array( 'pageid' => 1, 'ns' => 0, 'title' => 'AQBT-All' ), + array( 'pageid' => 2, 'ns' => 0, 'title' => 'AQBT-Categories' ), + ) ) + ); + + private static $generatorAllpages = array( + array( 'generator' => 'allpages', 'gapprefix' => 'AQBT-' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All' ), + '2' => array( + 'pageid' => 2, + 'ns' => 0, + 'title' => 'AQBT-Categories' ), + '3' => array( + 'pageid' => 3, + 'ns' => 0, + 'title' => 'AQBT-Links' ), + '4' => array( + 'pageid' => 4, + 'ns' => 0, + 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $generatorLinks = array( + array( 'generator' => 'links', 'titles' => 'AQBT-Links' ), + array( 'pages' => array( + '1' => array( + 'pageid' => 1, + 'ns' => 0, + 'title' => 'AQBT-All' ), + '2' => array( + 'pageid' => 2, + 'ns' => 0, + 'title' => 'AQBT-Categories' ), + '4' => array( + 'pageid' => 4, + 'ns' => 0, + 'title' => 'AQBT-Templates' ), + ) ) + ); + + private static $generatorLinksPropLinks = array( + array( 'prop' => 'links' ), + array( 'pages' => array( + '1' => array( 'links' => array( + array( 'ns' => 0, 'title' => 'AQBT-Links' ), + ) ) + ) ) + ); + + private static $generatorLinksPropTemplates = array( + array( 'prop' => 'templates' ), + array( 'pages' => array( + '1' => array( 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ), + '4' => array( 'templates' => array( + array( 'ns' => 10, 'title' => 'Template:AQBT-T' ) ) ), + ) ) + ); + + /** + * Test basic props + */ + public function testProps() { + $this->check( self::$links ); + $this->check( self::$templates ); + $this->check( self::$categories ); + } + + /** + * Test basic lists + */ + public function testLists() { + $this->check( self::$allpages ); + $this->check( self::$alllinks ); + $this->check( self::$alltransclusions ); + // This test is temporarily disabled until a sqlite bug is fixed + // $this->check( self::$allcategories ); + $this->check( self::$backlinks ); + $this->check( self::$embeddedin ); + $this->check( self::$categorymembers ); + } + + /** + * Test basic lists + */ + public function testAllTogether() { + + // All props together + $this->check( $this->merge( + self::$links, + self::$templates, + self::$categories + ) ); + + // All lists together + $this->check( $this->merge( + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers + ) ); + + // All props+lists together + $this->check( $this->merge( + self::$links, + self::$templates, + self::$categories, + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers + ) ); + } + + /** + * Test basic lists + */ + public function testGenerator() { + // generator=allpages + $this->check( self::$generatorAllpages ); + // generator=allpages & list=allpages + $this->check( $this->merge( + self::$generatorAllpages, + self::$allpages ) ); + // generator=links + $this->check( self::$generatorLinks ); + // generator=links & prop=links + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks ) ); + // generator=links & prop=templates + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropTemplates ) ); + // generator=links & prop=links|templates + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks, + self::$generatorLinksPropTemplates ) ); + // generator=links & prop=links|templates & list=allpages|... + $this->check( $this->merge( + self::$generatorLinks, + self::$generatorLinksPropLinks, + self::$generatorLinksPropTemplates, + self::$allpages, + self::$alllinks, + self::$alltransclusions, + // This test is temporarily disabled until a sqlite bug is fixed + // self::$allcategories, + self::$backlinks, + self::$embeddedin, + self::$categorymembers ) ); + } + + /** + * Test bug 51821 + */ + public function testGeneratorRedirects() { + $this->editPage( 'AQBT-Target', 'test' ); + $this->editPage( 'AQBT-Redir', '#REDIRECT [[AQBT-Target]]' ); + $this->check( array( + array( 'generator' => 'backlinks', 'gbltitle' => 'AQBT-Target', 'redirects' => '1' ), + array( + 'redirects' => array( + array( + 'from' => 'AQBT-Redir', + 'to' => 'AQBT-Target', + ) + ), + 'pages' => array( + '6' => array( + 'pageid' => 6, + 'ns' => 0, + 'title' => 'AQBT-Target', + ) + ), + ) + ) ); + } + + /** + * Recursively merges the expected values in the $item into the $all + */ + private function mergeExpected( &$all, $item ) { + foreach ( $item as $k => $v ) { + if ( array_key_exists( $k, $all ) ) { + if ( is_array( $all[$k] ) ) { + $this->mergeExpected( $all[$k], $v ); + } else { + $this->assertEquals( $all[$k], $v ); + } + } else { + $all[$k] = $v; + } + } + } + + /** + * Recursively compare arrays, ignoring mismatches in numeric key and pageids. + * @param $expected array expected values + * @param $result array returned values + */ + private function assertQueryResults( $expected, $result ) { + reset( $expected ); + reset( $result ); + while ( true ) { + $e = each( $expected ); + $r = each( $result ); + // If either of the arrays is shorter, abort. If both are done, success. + $this->assertEquals( (bool)$e, (bool)$r ); + if ( !$e ) { + break; // done + } + // continue only if keys are identical or both keys are numeric + $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) ); + // don't compare pageids + if ( $e['key'] !== 'pageid' ) { + // If values are arrays, compare recursively, otherwise compare with === + if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) { + $this->assertQueryResults( $e['value'], $r['value'] ); + } else { + $this->assertEquals( $e['value'], $r['value'] ); + } + } + } + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php new file mode 100644 index 00000000..4d5ddbae --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinue2Test.php @@ -0,0 +1,68 @@ +<?php +/** + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +require_once 'ApiQueryContinueTestBase.php'; + +/** + * @group API + * @group Database + * @group medium + */ +class ApiQueryContinue2Test extends ApiQueryContinueTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' ); + $this->editPage( 'AQCT73462-A', '**AQCT73462-A** [[AQCT73462-B]] [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-B', '[[AQCT73462-A]] **AQCT73462-B** [[AQCT73462-C]]' ); + $this->editPage( 'AQCT73462-C', '[[AQCT73462-A]] [[AQCT73462-B]] **AQCT73462-C**' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + /** + * @medium + */ + public function testA() { + $this->mVerbose = false; + $mk = function ( $g, $p, $gDir ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT73462-', + 'prop' => 'links', + 'gaplimit' => "$g", + 'pllimit' => "$p", + 'gapdir' => $gDir ? "ascending" : "descending", + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, true ), 1, 'g1p', false ); + $this->checkC( $data, $mk( 1, 1, true ), 6, 'g1p-11t' ); + $this->checkC( $data, $mk( 2, 2, true ), 3, 'g1p-22t' ); + $this->checkC( $data, $mk( 1, 1, false ), 6, 'g1p-11f' ); + $this->checkC( $data, $mk( 2, 2, false ), 3, 'g1p-22f' ); + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTest.php b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php new file mode 100644 index 00000000..f494e9ca --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTest.php @@ -0,0 +1,313 @@ +<?php +/** + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +require_once 'ApiQueryContinueTestBase.php'; + +/** + * These tests validate the new continue functionality of the api query module by + * doing multiple requests with varying parameters, merging the results, and checking + * that the result matches the full data received in one no-limits call. + * + * @group API + * @group Database + * @group medium + */ +class ApiQueryContinueTest extends ApiQueryContinueTestBase { + /** + * Create a set of pages. These must not change, otherwise the tests might give wrong results. + * @see MediaWikiTestCase::addDBData() + */ + function addDBData() { + try { + $this->editPage( 'Template:AQCT-T1', '**Template:AQCT-T1**' ); + $this->editPage( 'Template:AQCT-T2', '**Template:AQCT-T2**' ); + $this->editPage( 'Template:AQCT-T3', '**Template:AQCT-T3**' ); + $this->editPage( 'Template:AQCT-T4', '**Template:AQCT-T4**' ); + $this->editPage( 'Template:AQCT-T5', '**Template:AQCT-T5**' ); + + $this->editPage( 'AQCT-1', '**AQCT-1** {{AQCT-T2}} {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-2', '[[AQCT-1]] **AQCT-2** {{AQCT-T3}} {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-3', '[[AQCT-1]] [[AQCT-2]] **AQCT-3** {{AQCT-T4}} {{AQCT-T5}}' ); + $this->editPage( 'AQCT-4', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] **AQCT-4** {{AQCT-T5}}' ); + $this->editPage( 'AQCT-5', '[[AQCT-1]] [[AQCT-2]] [[AQCT-3]] [[AQCT-4]] **AQCT-5**' ); + } catch ( Exception $e ) { + $this->exceptionFromAddDBData = $e; + } + } + + /** + * Test smart continue - list=allpages + * @medium + */ + public function test1List() { + $this->mVerbose = false; + $mk = function ( $l ) { + return array( + 'list' => 'allpages', + 'apprefix' => 'AQCT-', + 'aplimit' => "$l", + ); + }; + $data = $this->query( $mk( 99 ), 1, '1L', false ); + + // 1 list + $this->checkC( $data, $mk( 1 ), 5, '1L-1' ); + $this->checkC( $data, $mk( 2 ), 3, '1L-2' ); + $this->checkC( $data, $mk( 3 ), 2, '1L-3' ); + $this->checkC( $data, $mk( 4 ), 2, '1L-4' ); + $this->checkC( $data, $mk( 5 ), 1, '1L-5' ); + } + + /** + * Test smart continue - list=allpages|alltransclusions + * @medium + */ + public function test2Lists() { + $this->mVerbose = false; + $mk = function ( $l1, $l2 ) { + return array( + 'list' => 'allpages|alltransclusions', + 'apprefix' => 'AQCT-', + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'aplimit' => "$l1", + 'atlimit' => "$l2", + ); + }; + // 2 lists + $data = $this->query( $mk( 99, 99 ), 1, '2L', false ); + $this->checkC( $data, $mk( 1, 1 ), 5, '2L-11' ); + $this->checkC( $data, $mk( 2, 2 ), 3, '2L-22' ); + $this->checkC( $data, $mk( 3, 3 ), 2, '2L-33' ); + $this->checkC( $data, $mk( 4, 4 ), 2, '2L-44' ); + $this->checkC( $data, $mk( 5, 5 ), 1, '2L-55' ); + } + + /** + * Test smart continue - generator=allpages, prop=links + * @medium + */ + public function testGen1Prop() { + $this->mVerbose = false; + $mk = function ( $g, $p ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links', + 'pllimit' => "$p", + ); + }; + // generator + 1 prop + $data = $this->query( $mk( 99, 99 ), 1, 'G1P', false ); + $this->checkC( $data, $mk( 1, 1 ), 11, 'G1P-11' ); + $this->checkC( $data, $mk( 2, 2 ), 6, 'G1P-22' ); + $this->checkC( $data, $mk( 3, 3 ), 4, 'G1P-33' ); + $this->checkC( $data, $mk( 4, 4 ), 3, 'G1P-44' ); + $this->checkC( $data, $mk( 5, 5 ), 2, 'G1P-55' ); + } + + /** + * Test smart continue - generator=allpages, prop=links|templates + * @medium + */ + public function testGen2Prop() { + $this->mVerbose = false; + $mk = function ( $g, $p1, $p2 ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links|templates', + 'pllimit' => "$p1", + 'tllimit' => "$p2", + ); + }; + // generator + 2 props + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G2P', false ); + $this->checkC( $data, $mk( 1, 1, 1 ), 16, 'G2P-111' ); + $this->checkC( $data, $mk( 2, 2, 2 ), 9, 'G2P-222' ); + $this->checkC( $data, $mk( 3, 3, 3 ), 6, 'G2P-333' ); + $this->checkC( $data, $mk( 4, 4, 4 ), 4, 'G2P-444' ); + $this->checkC( $data, $mk( 5, 5, 5 ), 2, 'G2P-555' ); + $this->checkC( $data, $mk( 5, 1, 1 ), 10, 'G2P-511' ); + $this->checkC( $data, $mk( 4, 2, 2 ), 7, 'G2P-422' ); + $this->checkC( $data, $mk( 2, 3, 3 ), 7, 'G2P-233' ); + $this->checkC( $data, $mk( 2, 4, 4 ), 5, 'G2P-244' ); + $this->checkC( $data, $mk( 1, 5, 5 ), 5, 'G2P-155' ); + } + + /** + * Test smart continue - generator=allpages, prop=links, list=alltransclusions + * @medium + */ + public function testGen1Prop1List() { + $this->mVerbose = false; + $mk = function ( $g, $p, $l ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links', + 'pllimit' => "$p", + 'list' => 'alltransclusions', + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'atlimit' => "$l", + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, 99 ), 1, 'G1P1L', false ); + $this->checkC( $data, $mk( 1, 1, 1 ), 11, 'G1P1L-111' ); + $this->checkC( $data, $mk( 2, 2, 2 ), 6, 'G1P1L-222' ); + $this->checkC( $data, $mk( 3, 3, 3 ), 4, 'G1P1L-333' ); + $this->checkC( $data, $mk( 4, 4, 4 ), 3, 'G1P1L-444' ); + $this->checkC( $data, $mk( 5, 5, 5 ), 2, 'G1P1L-555' ); + $this->checkC( $data, $mk( 5, 5, 1 ), 4, 'G1P1L-551' ); + $this->checkC( $data, $mk( 5, 5, 2 ), 2, 'G1P1L-552' ); + } + + /** + * Test smart continue - generator=allpages, prop=links|templates, + * list=alllinks|alltransclusions, meta=siteinfo + * @medium + */ + public function testGen2Prop2List1Meta() { + $this->mVerbose = false; + $mk = function ( $g, $p1, $p2, $l1, $l2 ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'prop' => 'links|templates', + 'pllimit' => "$p1", + 'tllimit' => "$p2", + 'list' => 'alllinks|alltransclusions', + 'alprefix' => 'AQCT-', + 'alunique' => '', + 'allimit' => "$l1", + 'atprefix' => 'AQCT-', + 'atunique' => '', + 'atlimit' => "$l2", + 'meta' => 'siteinfo', + 'siprop' => 'namespaces', + ); + }; + // generator + 1 prop + 1 list + $data = $this->query( $mk( 99, 99, 99, 99, 99 ), 1, 'G2P2L1M', false ); + $this->checkC( $data, $mk( 1, 1, 1, 1, 1 ), 16, 'G2P2L1M-11111' ); + $this->checkC( $data, $mk( 2, 2, 2, 2, 2 ), 9, 'G2P2L1M-22222' ); + $this->checkC( $data, $mk( 3, 3, 3, 3, 3 ), 6, 'G2P2L1M-33333' ); + $this->checkC( $data, $mk( 4, 4, 4, 4, 4 ), 4, 'G2P2L1M-44444' ); + $this->checkC( $data, $mk( 5, 5, 5, 5, 5 ), 2, 'G2P2L1M-55555' ); + $this->checkC( $data, $mk( 5, 5, 5, 1, 1 ), 4, 'G2P2L1M-55511' ); + $this->checkC( $data, $mk( 5, 5, 5, 2, 2 ), 2, 'G2P2L1M-55522' ); + $this->checkC( $data, $mk( 5, 1, 1, 5, 5 ), 10, 'G2P2L1M-51155' ); + $this->checkC( $data, $mk( 5, 2, 2, 5, 5 ), 5, 'G2P2L1M-52255' ); + } + + /** + * Test smart continue - generator=templates, prop=templates + * @medium + */ + public function testSameGenAndProp() { + $this->mVerbose = false; + $mk = function ( $g, $gDir, $p, $pDir ) { + return array( + 'titles' => 'AQCT-1', + 'generator' => 'templates', + 'gtllimit' => "$g", + 'gtldir' => $gDir ? 'ascending' : 'descending', + 'prop' => 'templates', + 'tllimit' => "$p", + 'tldir' => $pDir ? 'ascending' : 'descending', + ); + }; + // generator + 1 prop + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=P', false ); + + $this->checkC( $data, $mk( 1, true, 1, true ), 4, 'G=P-1t1t' ); + $this->checkC( $data, $mk( 2, true, 2, true ), 2, 'G=P-2t2t' ); + $this->checkC( $data, $mk( 3, true, 3, true ), 2, 'G=P-3t3t' ); + $this->checkC( $data, $mk( 1, true, 3, true ), 4, 'G=P-1t3t' ); + $this->checkC( $data, $mk( 3, true, 1, true ), 2, 'G=P-3t1t' ); + + $this->checkC( $data, $mk( 1, true, 1, false ), 4, 'G=P-1t1f' ); + $this->checkC( $data, $mk( 2, true, 2, false ), 2, 'G=P-2t2f' ); + $this->checkC( $data, $mk( 3, true, 3, false ), 2, 'G=P-3t3f' ); + $this->checkC( $data, $mk( 1, true, 3, false ), 4, 'G=P-1t3f' ); + $this->checkC( $data, $mk( 3, true, 1, false ), 2, 'G=P-3t1f' ); + + $this->checkC( $data, $mk( 1, false, 1, true ), 4, 'G=P-1f1t' ); + $this->checkC( $data, $mk( 2, false, 2, true ), 2, 'G=P-2f2t' ); + $this->checkC( $data, $mk( 3, false, 3, true ), 2, 'G=P-3f3t' ); + $this->checkC( $data, $mk( 1, false, 3, true ), 4, 'G=P-1f3t' ); + $this->checkC( $data, $mk( 3, false, 1, true ), 2, 'G=P-3f1t' ); + + $this->checkC( $data, $mk( 1, false, 1, false ), 4, 'G=P-1f1f' ); + $this->checkC( $data, $mk( 2, false, 2, false ), 2, 'G=P-2f2f' ); + $this->checkC( $data, $mk( 3, false, 3, false ), 2, 'G=P-3f3f' ); + $this->checkC( $data, $mk( 1, false, 3, false ), 4, 'G=P-1f3f' ); + $this->checkC( $data, $mk( 3, false, 1, false ), 2, 'G=P-3f1f' ); + } + + /** + * Test smart continue - generator=allpages, list=allpages + * @medium + */ + public function testSameGenList() { + $this->mVerbose = false; + $mk = function ( $g, $gDir, $l, $pDir ) { + return array( + 'generator' => 'allpages', + 'gapprefix' => 'AQCT-', + 'gaplimit' => "$g", + 'gapdir' => $gDir ? 'ascending' : 'descending', + 'list' => 'allpages', + 'apprefix' => 'AQCT-', + 'aplimit' => "$l", + 'apdir' => $pDir ? 'ascending' : 'descending', + ); + }; + // generator + 1 list + $data = $this->query( $mk( 99, true, 99, true ), 1, 'G=L', false ); + + $this->checkC( $data, $mk( 1, true, 1, true ), 5, 'G=L-1t1t' ); + $this->checkC( $data, $mk( 2, true, 2, true ), 3, 'G=L-2t2t' ); + $this->checkC( $data, $mk( 3, true, 3, true ), 2, 'G=L-3t3t' ); + $this->checkC( $data, $mk( 1, true, 3, true ), 5, 'G=L-1t3t' ); + $this->checkC( $data, $mk( 3, true, 1, true ), 5, 'G=L-3t1t' ); + $this->checkC( $data, $mk( 1, true, 1, false ), 5, 'G=L-1t1f' ); + $this->checkC( $data, $mk( 2, true, 2, false ), 3, 'G=L-2t2f' ); + $this->checkC( $data, $mk( 3, true, 3, false ), 2, 'G=L-3t3f' ); + $this->checkC( $data, $mk( 1, true, 3, false ), 5, 'G=L-1t3f' ); + $this->checkC( $data, $mk( 3, true, 1, false ), 5, 'G=L-3t1f' ); + $this->checkC( $data, $mk( 1, false, 1, true ), 5, 'G=L-1f1t' ); + $this->checkC( $data, $mk( 2, false, 2, true ), 3, 'G=L-2f2t' ); + $this->checkC( $data, $mk( 3, false, 3, true ), 2, 'G=L-3f3t' ); + $this->checkC( $data, $mk( 1, false, 3, true ), 5, 'G=L-1f3t' ); + $this->checkC( $data, $mk( 3, false, 1, true ), 5, 'G=L-3f1t' ); + $this->checkC( $data, $mk( 1, false, 1, false ), 5, 'G=L-1f1f' ); + $this->checkC( $data, $mk( 2, false, 2, false ), 3, 'G=L-2f2f' ); + $this->checkC( $data, $mk( 3, false, 3, false ), 2, 'G=L-3f3f' ); + $this->checkC( $data, $mk( 1, false, 3, false ), 5, 'G=L-1f3f' ); + $this->checkC( $data, $mk( 3, false, 1, false ), 5, 'G=L-3f1f' ); + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php new file mode 100644 index 00000000..fbb1e640 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php @@ -0,0 +1,209 @@ +<?php +/** + * + * + * Created on Jan 1, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +require_once 'ApiQueryTestBase.php'; + +abstract class ApiQueryContinueTestBase extends ApiQueryTestBase { + + /** + * Enable to print in-depth debugging info during the test run + */ + protected $mVerbose = false; + + /** + * Run query() and compare against expected values + */ + protected function checkC( $expected, $params, $expectedCount, $id, $continue = true ) { + $result = $this->query( $params, $expectedCount, $id, $continue ); + $this->assertResult( $expected, $result, $id ); + } + + /** + * Run query in a loop until no more values are available + * @param array $params api parameters + * @param int $expectedCount max number of iterations + * @param string $id unit test id + * @param boolean $useContinue true to use smart continue + * @return mixed: merged results data array + * @throws Exception + */ + protected function query( $params, $expectedCount, $id, $useContinue = true ) { + if ( isset( $params['action'] ) ) { + $this->assertEquals( 'query', $params['action'], 'Invalid query action' ); + } else { + $params['action'] = 'query'; + } + if ( $useContinue && !isset( $params['continue'] ) ) { + $params['continue'] = ''; + } + $count = 0; + $result = array(); + $continue = array(); + do { + $request = array_merge( $params, $continue ); + uksort( $request, function ( $a, $b ) { + // put 'continue' params at the end - lazy method + $a = strpos( $a, 'continue' ) !== false ? 'zzz ' . $a : $a; + $b = strpos( $b, 'continue' ) !== false ? 'zzz ' . $b : $b; + + return strcmp( $a, $b ); + } ); + $reqStr = http_build_query( $request ); + //$reqStr = str_replace( '&', ' & ', $reqStr ); + $this->assertLessThan( $expectedCount, $count, "$id more data: $reqStr" ); + if ( $this->mVerbose ) { + print "$id (#$count): $reqStr\n"; + } + try { + $data = $this->doApiRequest( $request ); + } catch ( Exception $e ) { + throw new Exception( "$id on $count", 0, $e ); + } + $data = $data[0]; + if ( isset( $data['warnings'] ) ) { + $warnings = json_encode( $data['warnings'] ); + $this->fail( "$id Warnings on #$count in $reqStr\n$warnings" ); + } + $this->assertArrayHasKey( 'query', $data, "$id no 'query' on #$count in $reqStr" ); + if ( isset( $data['continue'] ) ) { + $continue = $data['continue']; + unset( $data['continue'] ); + } else { + $continue = array(); + } + if ( $this->mVerbose ) { + $this->printResult( $data ); + } + $this->mergeResult( $result, $data ); + $count++; + if ( empty( $continue ) ) { + // $this->assertEquals( $expectedCount, $count, "$id finished early" ); + if ( $expectedCount > $count ) { + print "***** $id Finished early in $count turns. $expectedCount was expected\n"; + } + + return $result; + } elseif ( !$useContinue ) { + $this->assertFalse( 'Non-smart query must be requested all at once' ); + } + } while ( true ); + } + + private function printResult( $data ) { + $q = $data['query']; + $print = array(); + if ( isset( $q['pages'] ) ) { + foreach ( $q['pages'] as $p ) { + $m = $p['title']; + if ( isset( $p['links'] ) ) { + $m .= '/[' . implode( ',', array_map( + function ( $v ) { + return $v['title']; + }, + $p['links'] ) ) . ']'; + } + if ( isset( $p['categories'] ) ) { + $m .= '/(' . implode( ',', array_map( + function ( $v ) { + return str_replace( 'Category:', '', $v['title'] ); + }, + $p['categories'] ) ) . ')'; + } + $print[] = $m; + } + } + if ( isset( $q['allcategories'] ) ) { + $print[] = '*Cats/(' . implode( ',', array_map( + function ( $v ) { + return $v['*']; + }, + $q['allcategories'] ) ) . ')'; + } + self::GetItems( $q, 'allpages', 'Pages', $print ); + self::GetItems( $q, 'alllinks', 'Links', $print ); + self::GetItems( $q, 'alltransclusions', 'Trnscl', $print ); + print ' ' . implode( ' ', $print ) . "\n"; + } + + private static function GetItems( $q, $moduleName, $name, &$print ) { + if ( isset( $q[$moduleName] ) ) { + $print[] = "*$name/[" . implode( ',', + array_map( function ( $v ) { + return $v['title']; + }, + $q[$moduleName] ) ) . ']'; + } + } + + /** + * Recursively merge the new result returned from the query to the previous results. + * @param mixed $results + * @param mixed $newResult + * @param bool $numericIds If true, treat keys as ids to be merged instead of appending + */ + protected function mergeResult( &$results, $newResult, $numericIds = false ) { + $this->assertEquals( is_array( $results ), is_array( $newResult ), 'Type of result and data do not match' ); + if ( !is_array( $results ) ) { + $this->assertEquals( $results, $newResult, 'Repeated result must be the same as before' ); + } else { + $sort = null; + foreach ( $newResult as $key => $value ) { + if ( !$numericIds && $sort === null ) { + if ( !is_array( $value ) ) { + $sort = false; + } elseif ( array_key_exists( 'title', $value ) ) { + $sort = function ( $a, $b ) { + return strcmp( $a['title'], $b['title'] ); + }; + } else { + $sort = false; + } + } + $keyExists = array_key_exists( $key, $results ); + if ( is_numeric( $key ) ) { + if ( $numericIds ) { + if ( !$keyExists ) { + $results[$key] = $value; + } else { + $this->mergeResult( $results[$key], $value ); + } + } else { + $results[] = $value; + } + } elseif ( !$keyExists ) { + $results[$key] = $value; + } else { + $this->mergeResult( $results[$key], $value, $key === 'pages' ); + } + } + if ( $numericIds ) { + ksort( $results, SORT_NUMERIC ); + } elseif ( $sort !== null && $sort !== false ) { + uasort( $results, $sort ); + } + } + } +} diff --git a/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php b/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php new file mode 100644 index 00000000..1bca2256 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php @@ -0,0 +1,39 @@ +<?php + +/** + * @group API + * @group Database + * @group medium + */ +class ApiQueryRevisionsTest extends ApiTestCase { + + /** + * @group medium + */ + public function testContentComesWithContentModelAndFormat() { + $pageName = 'Help:' . __METHOD__; + $title = Title::newFromText( $pageName ); + $page = WikiPage::factory( $title ); + $page->doEdit( 'Some text', 'inserting content' ); + + $apiResult = $this->doApiRequest( array( + 'action' => 'query', + 'prop' => 'revisions', + 'titles' => $pageName, + 'rvprop' => 'content', + ) ); + $this->assertArrayHasKey( 'query', $apiResult[0] ); + $this->assertArrayHasKey( 'pages', $apiResult[0]['query'] ); + foreach ( $apiResult[0]['query']['pages'] as $page ) { + $this->assertArrayHasKey( 'revisions', $page ); + foreach ( $page['revisions'] as $revision ) { + $this->assertArrayHasKey( 'contentformat', $revision, + 'contentformat should be included when asking content so client knows how to interpret it' + ); + $this->assertArrayHasKey( 'contentmodel', $revision, + 'contentmodel should be included when asking content so client knows how to interpret it' + ); + } + } + } +} diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/query/ApiQueryTest.php index a4b9dc70..f5645555 100644 --- a/tests/phpunit/includes/api/ApiQueryTest.php +++ b/tests/phpunit/includes/api/query/ApiQueryTest.php @@ -3,15 +3,16 @@ /** * @group API * @group Database + * @group medium */ class ApiQueryTest extends ApiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); $this->doLogin(); } - function testTitlesGetNormalized() { + public function testTitlesGetNormalized() { global $wgMetaNamespace; @@ -19,12 +20,11 @@ class ApiQueryTest extends ApiTestCase { 'action' => 'query', 'titles' => 'Project:articleA|article_B' ) ); - $this->assertArrayHasKey( 'query', $data[0] ); $this->assertArrayHasKey( 'normalized', $data[0]['query'] ); // Forge a normalized title - $to = Title::newFromText( $wgMetaNamespace.':ArticleA' ); + $to = Title::newFromText( $wgMetaNamespace . ':ArticleA' ); $this->assertEquals( array( @@ -41,12 +41,11 @@ class ApiQueryTest extends ApiTestCase { ), $data[0]['query']['normalized'][1] ); - } - function testTitlesAreRejectedIfInvalid() { + public function testTitlesAreRejectedIfInvalid() { $title = false; - while( !$title || Title::newFromText( $title )->exists() ) { + while ( !$title || Title::newFromText( $title )->exists() ) { $title = md5( mt_rand( 0, 10000 ) + rand( 0, 999000 ) ); } @@ -64,5 +63,4 @@ class ApiQueryTest extends ApiTestCase { $this->assertArrayHasKey( 'missing', $data[0]['query']['pages'][-2] ); $this->assertArrayHasKey( 'invalid', $data[0]['query']['pages'][-1] ); } - } diff --git a/tests/phpunit/includes/api/query/ApiQueryTestBase.php b/tests/phpunit/includes/api/query/ApiQueryTestBase.php new file mode 100644 index 00000000..8ee8ea96 --- /dev/null +++ b/tests/phpunit/includes/api/query/ApiQueryTestBase.php @@ -0,0 +1,150 @@ +<?php +/** + * + * + * Created on Feb 10, 2013 + * + * Copyright © 2013 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** This class has some common functionality for testing query module + */ +abstract class ApiQueryTestBase extends ApiTestCase { + + const PARAM_ASSERT = <<<STR +Each parameter must be an array of two elements, +first - an array of params to the API call, +and the second array - expected results as returned by the API +STR; + + /** + * Merges all requests parameter + expected values into one + * @param ... list of arrays, each of which contains exactly two + * @return array + */ + protected function merge( /*...*/ ) { + $request = array(); + $expected = array(); + foreach ( func_get_args() as $v ) { + list( $req, $exp ) = $this->validateRequestExpectedPair( $v ); + $request = array_merge_recursive( $request, $req ); + $this->mergeExpected( $expected, $exp ); + } + + return array( $request, $expected ); + } + + /** + * Check that the parameter is a valid two element array, + * with the first element being API request and the second - expected result + */ + private function validateRequestExpectedPair( $v ) { + $this->assertType( 'array', $v, self::PARAM_ASSERT ); + $this->assertEquals( 2, count( $v ), self::PARAM_ASSERT ); + $this->assertArrayHasKey( 0, $v, self::PARAM_ASSERT ); + $this->assertArrayHasKey( 1, $v, self::PARAM_ASSERT ); + $this->assertType( 'array', $v[0], self::PARAM_ASSERT ); + $this->assertType( 'array', $v[1], self::PARAM_ASSERT ); + + return $v; + } + + /** + * Recursively merges the expected values in the $item into the $all + */ + private function mergeExpected( &$all, $item ) { + foreach ( $item as $k => $v ) { + if ( array_key_exists( $k, $all ) ) { + if ( is_array( $all[$k] ) ) { + $this->mergeExpected( $all[$k], $v ); + } else { + $this->assertEquals( $all[$k], $v ); + } + } else { + $all[$k] = $v; + } + } + } + + /** + * Checks that the request's result matches the expected results. + * @param $values array is a two element array( request, expected_results ) + * @throws Exception + */ + protected function check( $values ) { + list( $req, $exp ) = $this->validateRequestExpectedPair( $values ); + if ( !array_key_exists( 'action', $req ) ) { + $req['action'] = 'query'; + } + foreach ( $req as &$val ) { + if ( is_array( $val ) ) { + $val = implode( '|', array_unique( $val ) ); + } + } + $result = $this->doApiRequest( $req ); + $this->assertResult( array( 'query' => $exp ), $result[0], $req ); + } + + protected function assertResult( $exp, $result, $message = '' ) { + try { + $this->assertResultRecursive( $exp, $result ); + } catch ( Exception $e ) { + if ( is_array( $message ) ) { + $message = http_build_query( $message ); + } + print "\nRequest: $message\n"; + print "\nExpected:\n"; + print_r( $exp ); + print "\nResult:\n"; + print_r( $result ); + throw $e; // rethrow it + } + } + + /** + * Recursively compare arrays, ignoring mismatches in numeric key and pageids. + * @param $expected array expected values + * @param $result array returned values + */ + private function assertResultRecursive( $expected, $result ) { + reset( $expected ); + reset( $result ); + while ( true ) { + $e = each( $expected ); + $r = each( $result ); + // If either of the arrays is shorter, abort. If both are done, success. + $this->assertEquals( (bool)$e, (bool)$r ); + if ( !$e ) { + break; // done + } + // continue only if keys are identical or both keys are numeric + $this->assertTrue( $e['key'] === $r['key'] || ( is_numeric( $e['key'] ) && is_numeric( $r['key'] ) ) ); + // don't compare pageids + if ( $e['key'] !== 'pageid' ) { + // If values are arrays, compare recursively, otherwise compare with === + if ( is_array( $e['value'] ) && is_array( $r['value'] ) ) { + $this->assertResultRecursive( $e['value'], $r['value'] ); + } else { + $this->assertEquals( $e['value'], $r['value'] ); + } + } + } + } +} diff --git a/tests/phpunit/includes/cache/GenderCacheTest.php b/tests/phpunit/includes/cache/GenderCacheTest.php index a8b987e2..ce2db5d7 100644 --- a/tests/phpunit/includes/cache/GenderCacheTest.php +++ b/tests/phpunit/includes/cache/GenderCacheTest.php @@ -6,7 +6,7 @@ */ class GenderCacheTest extends MediaWikiLangTestCase { - function setUp() { + protected function setUp() { global $wgDefaultUserOptions; parent::setUp(); //ensure the correct default gender @@ -15,7 +15,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { function addDBData() { $user = User::newFromName( 'UTMale' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTMalePassword' ); } @@ -24,7 +24,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { $user->saveSettings(); $user = User::newFromName( 'UTFemale' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTFemalePassword' ); } @@ -33,7 +33,7 @@ class GenderCacheTest extends MediaWikiLangTestCase { $user->saveSettings(); $user = User::newFromName( 'UTDefaultGender' ); - if( $user->getID() == 0 ) { + if ( $user->getID() == 0 ) { $user->addToDatabase(); $user->setPassword( 'UTDefaultGenderPassword' ); } @@ -45,9 +45,10 @@ class GenderCacheTest extends MediaWikiLangTestCase { /** * test usernames * - * @dataProvider dataUserName + * @dataProvider provideUserGenders + * @covers GenderCache::getGenderOf */ - function testUserName( $username, $expectedGender ) { + public function testUserName( $username, $expectedGender ) { $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf( $username ); $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); @@ -56,16 +57,17 @@ class GenderCacheTest extends MediaWikiLangTestCase { /** * genderCache should work with user objects, too * - * @dataProvider dataUserName + * @dataProvider provideUserGenders + * @covers GenderCache::getGenderOf */ - function testUserObjects( $username, $expectedGender ) { + public function testUserObjects( $username, $expectedGender ) { $genderCache = GenderCache::singleton(); $user = User::newFromName( $username ); $gender = $genderCache->getGenderOf( $user ); $this->assertEquals( $gender, $expectedGender, "GenderCache normal" ); } - function dataUserName() { + public static function provideUserGenders() { return array( array( 'UTMale', 'male' ), array( 'UTFemale', 'female' ), @@ -81,15 +83,16 @@ class GenderCacheTest extends MediaWikiLangTestCase { * test strip of subpages to avoid unnecessary queries * against the never existing username * - * @dataProvider dataStripSubpages + * @dataProvider provideStripSubpages + * @covers GenderCache::getGenderOf */ - function testStripSubpages( $pageWithSubpage, $expectedGender ) { + public function testStripSubpages( $pageWithSubpage, $expectedGender ) { $genderCache = GenderCache::singleton(); $gender = $genderCache->getGenderOf( $pageWithSubpage ); $this->assertEquals( $gender, $expectedGender, "GenderCache must strip of subpages" ); } - function dataStripSubpages() { + public static function provideStripSubpages() { return array( array( 'UTMale/subpage', 'male' ), array( 'UTFemale/subpage', 'female' ), diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php new file mode 100644 index 00000000..803acf73 --- /dev/null +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -0,0 +1,128 @@ +<?php + +/** + * @group Database + * @group Cache + * @covers MessageCache + */ +class MessageCacheTest extends MediaWikiLangTestCase { + + protected function setUp() { + parent::setUp(); + $this->configureLanguages(); + MessageCache::singleton()->enable(); + } + + /** + * Helper function -- setup site language for testing + */ + protected function configureLanguages() { + // for the test, we need the content language to be anything but English, + // let's choose e.g. German (de) + $langCode = 'de'; + $langObj = Language::factory( $langCode ); + + $this->setMwGlobals( array( + 'wgLanguageCode' => $langCode, + 'wgLang' => $langObj, + 'wgContLang' => $langObj, + ) ); + } + + function addDBData() { + $this->configureLanguages(); + + // Set up messages and fallbacks ab -> ru -> de + $this->makePage( 'FallbackLanguageTest-Full', 'ab' ); + $this->makePage( 'FallbackLanguageTest-Full', 'ru' ); + $this->makePage( 'FallbackLanguageTest-Full', 'de' ); + + // Fallbacks where ab does not exist + $this->makePage( 'FallbackLanguageTest-Partial', 'ru' ); + $this->makePage( 'FallbackLanguageTest-Partial', 'de' ); + + // Fallback to the content language + $this->makePage( 'FallbackLanguageTest-ContLang', 'de' ); + + // Add customizations for an existing message. + $this->makePage( 'sunday', 'ru' ); + + // Full key tests -- always want russian + $this->makePage( 'MessageCacheTest-FullKeyTest', 'ab' ); + $this->makePage( 'MessageCacheTest-FullKeyTest', 'ru' ); + + // In content language -- get base if no derivative + $this->makePage( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none', false ); + } + + /** + * Helper function for addDBData -- adds a simple page to the database + * + * @param string $title Title of page to be created + * @param string $lang Language and content of the created page + * @param string|null $content Content of the created page, or null for a generic string + * @param bool $createSubPage Set to false if a root page should be created + */ + protected function makePage( $title, $lang, $content = null, $createSubPage = true ) { + global $wgContLang; + + if ( $content === null ) { + $content = $lang; + } + if ( $lang !== $wgContLang->getCode() || $createSubPage ) { + $title = "$title/$lang"; + } + + $title = Title::newFromText( $title, NS_MEDIAWIKI ); + $wikiPage = new WikiPage( $title ); + $contentHandler = ContentHandler::makeContent( $content, $title ); + $wikiPage->doEditContent( $contentHandler, "$lang translation test case" ); + } + + /** + * Test message fallbacks, bug #1495 + * + * @dataProvider provideMessagesForFallback + */ + public function testMessageFallbacks( $message, $lang, $expectedContent ) { + $result = MessageCache::singleton()->get( $message, true, $lang ); + $this->assertEquals( $expectedContent, $result, "Message fallback failed." ); + } + + function provideMessagesForFallback() { + return array( + array( 'FallbackLanguageTest-Full', 'ab', 'ab' ), + array( 'FallbackLanguageTest-Partial', 'ab', 'ru' ), + array( 'FallbackLanguageTest-ContLang', 'ab', 'de' ), + array( 'FallbackLanguageTest-None', 'ab', false ), + + // Existing message with customizations on the fallbacks + array( 'sunday', 'ab', 'амҽыш' ), + + // bug 46579 + array( 'FallbackLanguageTest-NoDervContLang', 'de', 'de/none' ), + // UI language different from content language should only use de/none as last option + array( 'FallbackLanguageTest-NoDervContLang', 'fit', 'de/none' ), + ); + } + + /** + * There's a fallback case where the message key is given as fully qualified -- this + * should ignore the passed $lang and use the language from the key + * + * @dataProvider provideMessagesForFullKeys + */ + public function testFullKeyBehaviour( $message, $lang, $expectedContent ) { + $result = MessageCache::singleton()->get( $message, true, $lang, true ); + $this->assertEquals( $expectedContent, $result, "Full key message fallback failed." ); + } + + function provideMessagesForFullKeys() { + return array( + array( 'MessageCacheTest-FullKeyTest/ru', 'ru', 'ru' ), + array( 'MessageCacheTest-FullKeyTest/ru', 'ab', 'ru' ), + array( 'MessageCacheTest-FullKeyTest/ru/foo', 'ru', false ), + ); + } + +} diff --git a/tests/phpunit/includes/cache/ProcessCacheLRUTest.php b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php index 30bfb124..d3793d83 100644 --- a/tests/phpunit/includes/cache/ProcessCacheLRUTest.php +++ b/tests/phpunit/includes/cache/ProcessCacheLRUTest.php @@ -24,7 +24,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { */ function fillCache( &$cache, $numEntries ) { // Fill cache with three values - for( $i=1; $i<=$numEntries; $i++) { + for ( $i = 1; $i <= $numEntries; $i++ ) { $cache->set( "cache-key-$i", "prop-$i", "value-$i" ); } } @@ -36,10 +36,10 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { function getExpectedCache( $cacheMaxEntries, $entryToFill ) { $expected = array(); - if( $entryToFill === 0 ) { + if ( $entryToFill === 0 ) { # The cache is empty! return array(); - } elseif( $entryToFill <= $cacheMaxEntries ) { + } elseif ( $entryToFill <= $cacheMaxEntries ) { # Cache is not fully filled $firstKey = 1; } else { @@ -47,21 +47,22 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $firstKey = 1 + $entryToFill - $cacheMaxEntries; } - $lastKey = $entryToFill; + $lastKey = $entryToFill; - for( $i=$firstKey; $i<=$lastKey; $i++ ) { + for ( $i = $firstKey; $i <= $lastKey; $i++ ) { $expected["cache-key-$i"] = array( "prop-$i" => "value-$i" ); } + return $expected; } /** * Highlight diff between assertEquals and assertNotSame */ - function testPhpUnitArrayEquality() { + public function testPhpUnitArrayEquality() { $one = array( 'A' => 1, 'B' => 2 ); $two = array( 'B' => 2, 'A' => 1 ); - $this->assertEquals( $one, $two ); // == + $this->assertEquals( $one, $two ); // == $this->assertNotSame( $one, $two ); // === } @@ -69,14 +70,14 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * @dataProvider provideInvalidConstructorArg * @expectedException MWException */ - function testConstructorGivenInvalidValue( $maxSize ) { - $c = new ProcessCacheLRUTestable( $maxSize ); + public function testConstructorGivenInvalidValue( $maxSize ) { + new ProcessCacheLRUTestable( $maxSize ); } /** * Value which are forbidden by the constructor */ - function provideInvalidConstructorArg() { + public static function provideInvalidConstructorArg() { return array( array( null ), array( array() ), @@ -87,7 +88,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testAddAndGetAKey() { + public function testAddAndGetAKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -98,7 +99,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { $this->assertEquals( 'value1', $oneCache->get( 'cache-key', 'prop1' ) ); } - function testDeleteOldKey() { + public function testDeleteOldKey() { $oneCache = new ProcessCacheLRUTestable( 1 ); $this->assertCacheEmpty( $oneCache ); @@ -116,37 +117,35 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * @param $cacheMaxEntries Maximum entry the created cache will hold * @param $entryToFill Number of entries to insert in the created cache. */ - function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { + public function testFillingCache( $cacheMaxEntries, $entryToFill, $msg = '' ) { $cache = new ProcessCacheLRUTestable( $cacheMaxEntries ); - $this->fillCache( $cache, $entryToFill); + $this->fillCache( $cache, $entryToFill ); $this->assertSame( $this->getExpectedCache( $cacheMaxEntries, $entryToFill ), $cache->getCache(), "Filling a $cacheMaxEntries entries cache with $entryToFill entries" ); - } /** * Provider for testFillingCache */ - function provideCacheFilling() { + public static function provideCacheFilling() { // ($cacheMaxEntries, $entryToFill, $msg='') return array( - array( 1, 0 ), - array( 1, 1 ), - array( 1, 2 ), # overflow + array( 1, 0 ), + array( 1, 1 ), + array( 1, 2 ), # overflow array( 5, 33 ), # overflow ); - } /** * Create a cache with only one remaining entry then update * the first inserted entry. Should bump it to the top. */ - function testReplaceExistingKeyShouldBumpEntryToTop() { + public function testReplaceExistingKeyShouldBumpEntryToTop() { $maxEntries = 3; $cache = new ProcessCacheLRUTestable( $maxEntries ); @@ -165,9 +164,9 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testRecentlyAccessedKeyStickIn() { + public function testRecentlyAccessedKeyStickIn() { $cache = new ProcessCacheLRUTestable( 2 ); - $cache->set( 'first' , 'prop1', 'value1' ); + $cache->set( 'first', 'prop1', 'value1' ); $cache->set( 'second', 'prop2', 'value2' ); // Get first @@ -184,7 +183,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { * Given a cache having 1,2,3 as key, updating 2 should bump 2 to * the top of the queue with the new value: 1,3,2* (* = updated). */ - function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { + public function testReplaceExistingKeyInAFullCacheShouldBumpToTop() { $maxEntries = 3; $cache = new ProcessCacheLRUTestable( $maxEntries ); @@ -205,7 +204,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ); } - function testBumpExistingKeyToTop() { + public function testBumpExistingKeyToTop() { $cache = new ProcessCacheLRUTestable( 3 ); $this->fillCache( $cache, 3 ); @@ -219,9 +218,7 @@ class ProcessCacheLRUTest extends MediaWikiTestCase { ), $cache->getCache() ); - } - } /** @@ -233,6 +230,7 @@ class ProcessCacheLRUTestable extends ProcessCacheLRU { public function getCache() { return $this->cache; } + public function getEntriesCount() { return count( $this->cache ); } diff --git a/tests/phpunit/includes/content/ContentHandlerTest.php b/tests/phpunit/includes/content/ContentHandlerTest.php new file mode 100644 index 00000000..aedf594d --- /dev/null +++ b/tests/phpunit/includes/content/ContentHandlerTest.php @@ -0,0 +1,451 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * + * @note Declare that we are using the database, because otherwise we'll fail in the "databaseless" test run. + * This is because the LinkHolderArray used by the parser needs database access. + * + */ +class ContentHandlerTest extends MediaWikiTestCase { + + public function setUp() { + global $wgContLang; + parent::setUp(); + + $this->setMwGlobals( array( + 'wgExtraNamespaces' => array( + 12312 => 'Dummy', + 12313 => 'Dummy_talk', + ), + // The below tests assume that namespaces not mentioned here (Help, User, MediaWiki, ..) + // default to CONTENT_MODEL_WIKITEXT. + 'wgNamespaceContentModels' => array( + 12312 => 'testing', + ), + 'wgContentHandlers' => array( + CONTENT_MODEL_WIKITEXT => 'WikitextContentHandler', + CONTENT_MODEL_JAVASCRIPT => 'JavaScriptContentHandler', + CONTENT_MODEL_CSS => 'CssContentHandler', + CONTENT_MODEL_TEXT => 'TextContentHandler', + 'testing' => 'DummyContentHandlerForTesting', + ), + ) ); + + // Reset namespace cache + MWNamespace::getCanonicalNamespaces( true ); + $wgContLang->resetNamespaces(); + } + + public function tearDown() { + global $wgContLang; + + // Reset namespace cache + MWNamespace::getCanonicalNamespaces( true ); + $wgContLang->resetNamespaces(); + + parent::tearDown(); + } + + public static function dataGetDefaultModelFor() { + return array( + array( 'Help:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'Help:Foo/bar.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo.js', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'User:Foo/bar.css', CONTENT_MODEL_CSS ), + array( 'User talk:Foo/bar.css', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.js.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'User:Foo/bar.xxx', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.js', CONTENT_MODEL_JAVASCRIPT ), + array( 'MediaWiki:Foo.css', CONTENT_MODEL_CSS ), + array( 'MediaWiki:Foo.JS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.CSS', CONTENT_MODEL_WIKITEXT ), + array( 'MediaWiki:Foo.css.xxx', CONTENT_MODEL_WIKITEXT ), + ); + } + + /** + * @dataProvider dataGetDefaultModelFor + * @covers ContentHandler::getDefaultModelFor + */ + public function testGetDefaultModelFor( $title, $expectedModelId ) { + $title = Title::newFromText( $title ); + $this->assertEquals( $expectedModelId, ContentHandler::getDefaultModelFor( $title ) ); + } + + /** + * @dataProvider dataGetDefaultModelFor + * @covers ContentHandler::getForTitle + */ + public function testGetForTitle( $title, $expectedContentModel ) { + $title = Title::newFromText( $title ); + $handler = ContentHandler::getForTitle( $title ); + $this->assertEquals( $expectedContentModel, $handler->getModelID() ); + } + + public static function dataGetLocalizedName() { + return array( + array( null, null ), + array( "xyzzy", null ), + + // XXX: depends on content language + array( CONTENT_MODEL_JAVASCRIPT, '/javascript/i' ), + ); + } + + /** + * @dataProvider dataGetLocalizedName + * @covers ContentHandler::getLocalizedName + */ + public function testGetLocalizedName( $id, $expected ) { + $name = ContentHandler::getLocalizedName( $id ); + + if ( $expected ) { + $this->assertNotNull( $name, "no name found for content model $id" ); + $this->assertTrue( preg_match( $expected, $name ) > 0, + "content model name for #$id did not match pattern $expected" + ); + } else { + $this->assertEquals( $id, $name, "localization of unknown model $id should have " + . "fallen back to use the model id directly." + ); + } + } + + public static function dataGetPageLanguage() { + global $wgLanguageCode; + + return array( + array( "Main", $wgLanguageCode ), + array( "Dummy:Foo", $wgLanguageCode ), + array( "MediaWiki:common.js", 'en' ), + array( "User:Foo/common.js", 'en' ), + array( "MediaWiki:common.css", 'en' ), + array( "User:Foo/common.css", 'en' ), + array( "User:Foo", $wgLanguageCode ), + + array( CONTENT_MODEL_JAVASCRIPT, 'javascript' ), + ); + } + + /** + * @dataProvider dataGetPageLanguage + * @covers ContentHandler::getPageLanguage + */ + public function testGetPageLanguage( $title, $expected ) { + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + + $expected = wfGetLangObj( $expected ); + + $handler = ContentHandler::getForTitle( $title ); + $lang = $handler->getPageLanguage( $title ); + + $this->assertEquals( $expected->getCode(), $lang->getCode() ); + } + + public static function dataGetContentText_Null() { + return array( + array( 'fail' ), + array( 'serialize' ), + array( 'ignore' ), + ); + } + + /** + * @dataProvider dataGetContentText_Null + * @covers ContentHandler::getContentText + */ + public function testGetContentText_Null( $contentHandlerTextFallback ) { + $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback ); + + $content = null; + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( '', $text ); + } + + public static function dataGetContentText_TextContent() { + return array( + array( 'fail' ), + array( 'serialize' ), + array( 'ignore' ), + ); + } + + /** + * @dataProvider dataGetContentText_TextContent + * @covers ContentHandler::getContentText + */ + public function testGetContentText_TextContent( $contentHandlerTextFallback ) { + $this->setMwGlobals( 'wgContentHandlerTextFallback', $contentHandlerTextFallback ); + + $content = new WikitextContent( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( $content->getNativeData(), $text ); + } + + /** + * ContentHandler::getContentText should have thrown an exception for non-text Content object + * @expectedException MWException + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_fail() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'fail' ); + + $content = new DummyContentForTesting( "hello world" ); + + ContentHandler::getContentText( $content ); + } + + /** + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_serialize() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'serialize' ); + + $content = new DummyContentForTesting( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertEquals( $content->serialize(), $text ); + } + + /** + * @covers ContentHandler::getContentText + */ + public function testGetContentText_NonTextContent_ignore() { + $this->setMwGlobals( 'wgContentHandlerTextFallback', 'ignore' ); + + $content = new DummyContentForTesting( "hello world" ); + + $text = ContentHandler::getContentText( $content ); + $this->assertNull( $text ); + } + + /* + public static function makeContent( $text, Title $title, $modelId = null, $format = null ) {} + */ + + public static function dataMakeContent() { + return array( + array( 'hallo', 'Help:Test', null, null, CONTENT_MODEL_WIKITEXT, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', null, null, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', null, null, "testing", 'hallo', false ), + + array( 'hallo', 'Help:Test', null, CONTENT_FORMAT_WIKITEXT, CONTENT_MODEL_WIKITEXT, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', null, CONTENT_FORMAT_JAVASCRIPT, CONTENT_MODEL_JAVASCRIPT, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', null, "testing", "testing", 'hallo', false ), + + array( 'hallo', 'Help:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ), + array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, 'hallo', false ), + array( serialize( 'hallo' ), 'Dummy:Test', CONTENT_MODEL_CSS, null, CONTENT_MODEL_CSS, serialize( 'hallo' ), false ), + + array( 'hallo', 'Help:Test', CONTENT_MODEL_WIKITEXT, "testing", null, null, true ), + array( 'hallo', 'MediaWiki:Test.js', CONTENT_MODEL_CSS, "testing", null, null, true ), + array( 'hallo', 'Dummy:Test', CONTENT_MODEL_JAVASCRIPT, "testing", null, null, true ), + ); + } + + /** + * @dataProvider dataMakeContent + * @covers ContentHandler::makeContent + */ + public function testMakeContent( $data, $title, $modelId, $format, $expectedModelId, $expectedNativeData, $shouldFail ) { + $title = Title::newFromText( $title ); + + try { + $content = ContentHandler::makeContent( $data, $title, $modelId, $format ); + + if ( $shouldFail ) { + $this->fail( "ContentHandler::makeContent should have failed!" ); + } + + $this->assertEquals( $expectedModelId, $content->getModel(), 'bad model id' ); + $this->assertEquals( $expectedNativeData, $content->getNativeData(), 'bads native data' ); + } catch ( MWException $ex ) { + if ( !$shouldFail ) { + $this->fail( "ContentHandler::makeContent failed unexpectedly: " . $ex->getMessage() ); + } else { + // dummy, so we don't get the "test did not perform any assertions" message. + $this->assertTrue( true ); + } + } + } + + /* + public function testSupportsSections() { + $this->markTestIncomplete( "not yet implemented" ); + } + */ + + /** + * @covers ContentHandler::runLegacyHooks + */ + public function testRunLegacyHooks() { + Hooks::register( 'testRunLegacyHooks', __CLASS__ . '::dummyHookHandler' ); + + $content = new WikitextContent( 'test text' ); + $ok = ContentHandler::runLegacyHooks( 'testRunLegacyHooks', array( 'foo', &$content, 'bar' ), false ); + + $this->assertTrue( $ok, "runLegacyHooks should have returned true" ); + $this->assertEquals( "TEST TEXT", $content->getNativeData() ); + } + + public static function dummyHookHandler( $foo, &$text, $bar ) { + if ( $text === null || $text === false ) { + return false; + } + + $text = strtoupper( $text ); + + return true; + } +} + +class DummyContentHandlerForTesting extends ContentHandler { + + public function __construct( $dataModel ) { + parent::__construct( $dataModel, array( "testing" ) ); + } + + /** + * Serializes Content object of the type supported by this ContentHandler. + * + * @param Content $content the Content object to serialize + * @param null $format the desired serialization format + * @return String serialized form of the content + */ + public function serializeContent( Content $content, $format = null ) { + return $content->serialize(); + } + + /** + * Unserializes a Content object of the type supported by this ContentHandler. + * + * @param $blob String serialized form of the content + * @param null $format the format used for serialization + * @return Content the Content object created by deserializing $blob + */ + public function unserializeContent( $blob, $format = null ) { + $d = unserialize( $blob ); + + return new DummyContentForTesting( $d ); + } + + /** + * Creates an empty Content object of the type supported by this ContentHandler. + * + */ + public function makeEmptyContent() { + return new DummyContentForTesting( '' ); + } +} + +class DummyContentForTesting extends AbstractContent { + + public function __construct( $data ) { + parent::__construct( "testing" ); + + $this->data = $data; + } + + public function serialize( $format = null ) { + return serialize( $this->data ); + } + + /** + * @return String a string representing the content in a way useful for building a full text search index. + * If no useful representation exists, this method returns an empty string. + */ + public function getTextForSearchIndex() { + return ''; + } + + /** + * @return String the wikitext to include when another page includes this content, or false if the content is not + * includable in a wikitext page. + */ + public function getWikitextForTransclusion() { + return false; + } + + /** + * Returns a textual representation of the content suitable for use in edit summaries and log messages. + * + * @param int $maxlength Maximum length of the summary text. + * @return string The summary text. + */ + public function getTextForSummary( $maxlength = 250 ) { + return ''; + } + + /** + * Returns native represenation of the data. Interpretation depends on the data model used, + * as given by getDataModel(). + * + * @return mixed the native representation of the content. Could be a string, a nested array + * structure, an object, a binary blob... anything, really. + */ + public function getNativeData() { + return $this->data; + } + + /** + * returns the content's nominal size in bogo-bytes. + * + * @return int + */ + public function getSize() { + return strlen( $this->data ); + } + + /** + * Return a copy of this Content object. The following must be true for the object returned + * if $copy = $original->copy() + * + * * get_class($original) === get_class($copy) + * * $original->getModel() === $copy->getModel() + * * $original->equals( $copy ) + * + * If and only if the Content object is imutable, the copy() method can and should + * return $this. That is, $copy === $original may be true, but only for imutable content + * objects. + * + * @return Content. A copy of this object. + */ + public function copy() { + return $this; + } + + /** + * Returns true if this content is countable as a "real" wiki page, provided + * that it's also in a countable location (e.g. a current revision in the main namespace). + * + * @param boolean $hasLinks if it is known whether this content contains links, provide this information here, + * to avoid redundant parsing to find out. + * @return boolean + */ + public function isCountable( $hasLinks = null ) { + return false; + } + + /** + * @param Title $title + * @param null $revId + * @param null|ParserOptions $options + * @param boolean $generateHtml whether to generate Html (default: true). If false, + * the result of calling getText() on the ParserOutput object returned by + * this method is undefined. + * + * @return ParserOutput + */ + public function getParserOutput( Title $title, $revId = null, ParserOptions $options = null, $generateHtml = true ) { + return new ParserOutput( $this->getNativeData() ); + } +} diff --git a/tests/phpunit/includes/content/CssContentTest.php b/tests/phpunit/includes/content/CssContentTest.php new file mode 100644 index 00000000..bd6d41fe --- /dev/null +++ b/tests/phpunit/includes/content/CssContentTest.php @@ -0,0 +1,87 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class CssContentTest extends MediaWikiTestCase { + + protected function setUp() { + parent::setUp(); + + // Anon user + $user = new User(); + $user->setName( '127.0.0.1' ); + + $this->setMwGlobals( array( + 'wgUser' => $user, + 'wgTextModelsToParse' => array( + CONTENT_MODEL_CSS, + ) + ) ); + } + + public function newContent( $text ) { + return new CssContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'MediaWiki:Test.css', + null, + "hello <world>\n", + "<pre class=\"mw-code mw-css\" dir=\"ltr\">\nhello <world>\n\n</pre>" + ), + array( + 'MediaWiki:Test.css', + null, + "/* hello [[world]] */\n", + "<pre class=\"mw-code mw-css\" dir=\"ltr\">\n/* hello [[world]] */\n\n</pre>", + array( + 'Links' => array( + array( 'World' => 0 ) + ) + ) + ), + + // TODO: more...? + ); + } + + /** + * @covers CssContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( CONTENT_MODEL_CSS, $content->getModel() ); + } + + /** + * @covers CssContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( CONTENT_MODEL_CSS, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new CssContent( 'hallo' ), null, false ), + array( new CssContent( 'hallo' ), new CssContent( 'hallo' ), true ), + array( new CssContent( 'hallo' ), new WikitextContent( 'hallo' ), false ), + array( new CssContent( 'hallo' ), new CssContent( 'HALLO' ), false ), + ); + } + + /** + * @dataProvider dataEquals + * @covers CssContent::equals + */ + public function testEquals( Content $a, Content $b = null, $equal = false ) { + $this->assertEquals( $equal, $a->equals( $b ) ); + } +} diff --git a/tests/phpunit/includes/content/JavaScriptContentTest.php b/tests/phpunit/includes/content/JavaScriptContentTest.php new file mode 100644 index 00000000..c8616ff0 --- /dev/null +++ b/tests/phpunit/includes/content/JavaScriptContentTest.php @@ -0,0 +1,287 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class JavaScriptContentTest extends TextContentTest { + + public function newContent( $text ) { + return new JavaScriptContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'MediaWiki:Test.js', + null, + "hello <world>\n", + "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello <world>\n\n</pre>" + ), + array( + 'MediaWiki:Test.js', + null, + "hello(); // [[world]]\n", + "<pre class=\"mw-code mw-js\" dir=\"ltr\">\nhello(); // [[world]]\n\n</pre>", + array( + 'Links' => array( + array( 'World' => 0 ) + ) + ) + ), + + // TODO: more...? + ); + } + + // XXX: Unused function + public static function dataGetSection() { + return array( + array( WikitextContentTest::$sections, + '0', + null + ), + array( WikitextContentTest::$sections, + '2', + null + ), + array( WikitextContentTest::$sections, + '8', + null + ), + ); + } + + // XXX: Unused function + public static function dataReplaceSection() { + return array( + array( WikitextContentTest::$sections, + '0', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + '', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + '2', + "== TEST ==\nmore fun", + null, + null + ), + array( WikitextContentTest::$sections, + '8', + 'No more', + null, + null + ), + array( WikitextContentTest::$sections, + 'new', + 'No more', + 'New', + null + ), + ); + } + + /** + * @covers JavaScriptContent::addSectionHeader + */ + public function testAddSectionHeader() { + $content = $this->newContent( 'hello world' ); + $c = $content->addSectionHeader( 'test' ); + + $this->assertTrue( $content->equals( $c ) ); + } + + // XXX: currently, preSaveTransform is applied to scripts. this may change or become optional. + public static function dataPreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + ), + array( " Foo \n ", + " Foo", + ), + ); + } + + public static function dataPreloadTransform() { + return array( + array( 'hello this is ~~~', + 'hello this is ~~~', + ), + array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + ), + ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + null, + ), + array( '#REDIRECT Test', + null, + ), + array( '* #REDIRECT [[Test]]', + null, + ), + ); + } + + /** + * @todo Test needs database! + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + false + ), + array( 'Foo', + null, + 'link', + false + ), + array( 'Foo [[bar]]', + null, + 'link', + false + ), + array( 'Foo', + true, + 'link', + false + ), + array( 'Foo [[bar]]', + false, + 'link', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'any', + true + ), + array( '#REDIRECT [[bar]]', + true, + 'comma', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'link', + false + ), + ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + '[[hel...', + ), + ); + } + + /** + * @covers JavaScriptContent::matchMagicWord + */ + public function testMatchMagicWord() { + $mw = MagicWord::get( "staticredirect" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" ); + $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word, since it's not wikitext" ); + } + + /** + * @covers JavaScriptContent::updateRedirect + */ + public function testUpdateRedirect() { + $target = Title::newFromText( "testUpdateRedirect_target" ); + + $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $newContent = $content->updateRedirect( $target ); + + $this->assertTrue( $content->equals( $newContent ), "content should be unchanged since it's not wikitext" ); + } + + /** + * @covers JavaScriptContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getModel() ); + } + + /** + * @covers JavaScriptContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_JAVASCRIPT, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new JavaScriptContent( "hallo" ), null, false ), + array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "hallo" ), true ), + array( new JavaScriptContent( "hallo" ), new CssContent( "hallo" ), false ), + array( new JavaScriptContent( "hallo" ), new JavaScriptContent( "HALLO" ), false ), + ); + } +} diff --git a/tests/phpunit/includes/content/TextContentTest.php b/tests/phpunit/includes/content/TextContentTest.php new file mode 100644 index 00000000..a1f099f3 --- /dev/null +++ b/tests/phpunit/includes/content/TextContentTest.php @@ -0,0 +1,458 @@ +<?php + +/** + * @group ContentHandler + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class TextContentTest extends MediaWikiLangTestCase { + protected $context; + + protected function setUp() { + parent::setUp(); + + // Anon user + $user = new User(); + $user->setName( '127.0.0.1' ); + + $this->setMwGlobals( array( + 'wgUser' => $user, + 'wgTextModelsToParse' => array( + CONTENT_MODEL_WIKITEXT, + CONTENT_MODEL_CSS, + CONTENT_MODEL_JAVASCRIPT, + ), + 'wgUseTidy' => false, + 'wgAlwaysUseTidy' => false, + ) ); + + $this->context = new RequestContext( new FauxRequest() ); + $this->context->setTitle( Title::newFromText( 'Test' ) ); + $this->context->setUser( $user ); + } + + public function newContent( $text ) { + return new TextContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + 'TextContentTest_testGetParserOutput', + CONTENT_MODEL_TEXT, + "hello ''world'' & [[stuff]]\n", "hello ''world'' & [[stuff]]", + array( + 'Links' => array() + ) + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetParserOutput + * @covers TextContent::getParserOutput + */ + public function testGetParserOutput( $title, $model, $text, $expectedHtml, $expectedFields = null ) { + $title = Title::newFromText( $title ); + $content = ContentHandler::makeContent( $text, $title, $model ); + + $po = $content->getParserOutput( $title ); + + $html = $po->getText(); + $html = preg_replace( '#<!--.*?-->#sm', '', $html ); // strip comments + + $this->assertEquals( $expectedHtml, trim( $html ) ); + + if ( $expectedFields ) { + foreach ( $expectedFields as $field => $exp ) { + $f = 'get' . ucfirst( $field ); + $v = call_user_func( array( $po, $f ) ); + + if ( is_array( $exp ) ) { + $this->assertArrayEquals( $exp, $v ); + } else { + $this->assertEquals( $exp, $v ); + } + } + } + + // TODO: assert more properties + } + + public static function dataPreSaveTransform() { + return array( + array( + #0: no signature resolution + 'hello this is ~~~', + 'hello this is ~~~', + ), + array( + #1: rtrim + " Foo \n ", + ' Foo', + ), + ); + } + + /** + * @dataProvider dataPreSaveTransform + * @covers TextContent::preSaveTransform + */ + public function testPreSaveTransform( $text, $expected ) { + global $wgContLang; + + $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang ); + + $content = $this->newContent( $text ); + $content = $content->preSaveTransform( $this->context->getTitle(), $this->context->getUser(), $options ); + + $this->assertEquals( $expected, $content->getNativeData() ); + } + + public static function dataPreloadTransform() { + return array( + array( + 'hello this is ~~~', + 'hello this is ~~~', + ), + ); + } + + /** + * @dataProvider dataPreloadTransform + * @covers TextContent::preloadTransform + */ + public function testPreloadTransform( $text, $expected ) { + global $wgContLang; + $options = ParserOptions::newFromUserAndLang( $this->context->getUser(), $wgContLang ); + + $content = $this->newContent( $text ); + $content = $content->preloadTransform( $this->context->getTitle(), $options ); + + $this->assertEquals( $expected, $content->getNativeData() ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + null, + ), + ); + } + + /** + * @dataProvider dataGetRedirectTarget + * @covers TextContent::getRedirectTarget + */ + public function testGetRedirectTarget( $text, $expected ) { + $content = $this->newContent( $text ); + $t = $content->getRedirectTarget(); + + if ( is_null( $expected ) ) { + $this->assertNull( $t, "text should not have generated a redirect target: $text" ); + } else { + $this->assertEquals( $expected, $t->getPrefixedText() ); + } + } + + /** + * @dataProvider dataGetRedirectTarget + * @covers TextContent::isRedirect + */ + public function testIsRedirect( $text, $expected ) { + $content = $this->newContent( $text ); + + $this->assertEquals( !is_null( $expected ), $content->isRedirect() ); + } + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + false + ), + ); + } + + /** + * @dataProvider dataIsCountable + * @group Database + * @covers TextContent::isCountable + */ + public function testIsCountable( $text, $hasLinks, $mode, $expected ) { + $this->setMwGlobals( 'wgArticleCountMethod', $mode ); + + $content = $this->newContent( $text ); + + $v = $content->isCountable( $hasLinks, $this->context->getTitle() ); + + $this->assertEquals( $expected, $v, 'isCountable() returned unexpected value ' . var_export( $v, true ) + . ' instead of ' . var_export( $expected, true ) . " in mode `$mode` for text \"$text\"" ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + '[[hel...', + ), + ); + } + + /** + * @dataProvider dataGetTextForSummary + * @covers TextContent::getTextForSummary + */ + public function testGetTextForSummary( $text, $maxlength, $expected ) { + $content = $this->newContent( $text ); + + $this->assertEquals( $expected, $content->getTextForSummary( $maxlength ) ); + } + + /** + * @covers TextContent::getTextForSearchIndex + */ + public function testGetTextForSearchIndex() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getTextForSearchIndex() ); + } + + /** + * @covers TextContent::copy + */ + public function testCopy() { + $content = $this->newContent( 'hello world.' ); + $copy = $content->copy(); + + $this->assertTrue( $content->equals( $copy ), 'copy must be equal to original' ); + $this->assertEquals( 'hello world.', $copy->getNativeData() ); + } + + /** + * @covers TextContent::getSize + */ + public function testGetSize() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 12, $content->getSize() ); + } + + /** + * @covers TextContent::getNativeData + */ + public function testGetNativeData() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getNativeData() ); + } + + /** + * @covers TextContent::getWikitextForTransclusion + */ + public function testGetWikitextForTransclusion() { + $content = $this->newContent( 'hello world.' ); + + $this->assertEquals( 'hello world.', $content->getWikitextForTransclusion() ); + } + + /** + * @covers TextContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_TEXT, $content->getModel() ); + } + + /** + * @covers TextContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_TEXT, $content->getContentHandler()->getModelID() ); + } + + public static function dataIsEmpty() { + return array( + array( '', true ), + array( ' ', false ), + array( '0', false ), + array( 'hallo welt.', false ), + ); + } + + /** + * @dataProvider dataIsEmpty + * @covers TextContent::isEmpty + */ + public function testIsEmpty( $text, $empty ) { + $content = $this->newContent( $text ); + + $this->assertEquals( $empty, $content->isEmpty() ); + } + + public static function dataEquals() { + return array( + array( new TextContent( "hallo" ), null, false ), + array( new TextContent( "hallo" ), new TextContent( "hallo" ), true ), + array( new TextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ), + array( new TextContent( "hallo" ), new WikitextContent( "hallo" ), false ), + array( new TextContent( "hallo" ), new TextContent( "HALLO" ), false ), + ); + } + + /** + * @dataProvider dataEquals + * @covers TextContent::equals + */ + public function testEquals( Content $a, Content $b = null, $equal = false ) { + $this->assertEquals( $equal, $a->equals( $b ) ); + } + + public static function dataGetDeletionUpdates() { + return array( + array( "TextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_TEXT, "hello ''world''\n", + array() + ), + array( "TextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_TEXT, "hello [[world test 21344]]\n", + array() + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetDeletionUpdates + * @covers TextContent::getDeletionUpdates + */ + public function testDeletionUpdates( $title, $model, $text, $expectedStuff ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + + $content = ContentHandler::makeContent( $text, $title, $model ); + + $page = WikiPage::factory( $title ); + $page->doEditContent( $content, '' ); + + $updates = $content->getDeletionUpdates( $page ); + + // make updates accessible by class name + foreach ( $updates as $update ) { + $class = get_class( $update ); + $updates[$class] = $update; + } + + if ( !$expectedStuff ) { + $this->assertTrue( true ); // make phpunit happy + return; + } + + foreach ( $expectedStuff as $class => $fieldValues ) { + $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" ); + + $update = $updates[$class]; + + foreach ( $fieldValues as $field => $value ) { + $v = $update->$field; #if the field doesn't exist, just crash and burn + $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" ); + } + } + + $page->doDeleteArticle( '' ); + } + + public static function provideConvert() { + return array( + array( // #0 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_WIKITEXT, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_CSS, + 'lossless', + 'Hallo Welt' + ), + array( // #1 + 'Hallo Welt', + CONTENT_MODEL_JAVASCRIPT, + 'lossless', + 'Hallo Welt' + ), + ); + } + + /** + * @dataProvider provideConvert + * @covers TextContent::convert + */ + public function testConvert( $text, $model, $lossy, $expectedNative ) { + $content = $this->newContent( $text ); + + $converted = $content->convert( $model, $lossy ); + + if ( $expectedNative === false ) { + $this->assertFalse( $converted, "conversion to $model was expected to fail!" ); + } else { + $this->assertInstanceOf( 'Content', $converted ); + $this->assertEquals( $expectedNative, $converted->getNativeData() ); + } + } +} diff --git a/tests/phpunit/includes/content/WikitextContentHandlerTest.php b/tests/phpunit/includes/content/WikitextContentHandlerTest.php new file mode 100644 index 00000000..75a72784 --- /dev/null +++ b/tests/phpunit/includes/content/WikitextContentHandlerTest.php @@ -0,0 +1,227 @@ +<?php + +/** + * @group ContentHandler + */ +class WikitextContentHandlerTest extends MediaWikiLangTestCase { + + /** + * @var ContentHandler + */ + var $handler; + + public function setUp() { + parent::setUp(); + + $this->handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT ); + } + + /** + * @covers WikitextContentHandler::serializeContent + */ + public function testSerializeContent() { + $content = new WikitextContent( 'hello world' ); + + $this->assertEquals( 'hello world', $this->handler->serializeContent( $content ) ); + $this->assertEquals( 'hello world', $this->handler->serializeContent( $content, CONTENT_FORMAT_WIKITEXT ) ); + + try { + $this->handler->serializeContent( $content, 'dummy/foo' ); + $this->fail( "serializeContent() should have failed on unknown format" ); + } catch ( MWException $e ) { + // ok, as expected + } + } + + /** + * @covers WikitextContentHandler::unserializeContent + */ + public function testUnserializeContent() { + $content = $this->handler->unserializeContent( 'hello world' ); + $this->assertEquals( 'hello world', $content->getNativeData() ); + + $content = $this->handler->unserializeContent( 'hello world', CONTENT_FORMAT_WIKITEXT ); + $this->assertEquals( 'hello world', $content->getNativeData() ); + + try { + $this->handler->unserializeContent( 'hello world', 'dummy/foo' ); + $this->fail( "unserializeContent() should have failed on unknown format" ); + } catch ( MWException $e ) { + // ok, as expected + } + } + + /** + * @covers WikitextContentHandler::makeEmptyContent + */ + public function testMakeEmptyContent() { + $content = $this->handler->makeEmptyContent(); + + $this->assertTrue( $content->isEmpty() ); + $this->assertEquals( '', $content->getNativeData() ); + } + + public static function dataIsSupportedFormat() { + return array( + array( null, true ), + array( CONTENT_FORMAT_WIKITEXT, true ), + array( 99887766, false ), + ); + } + + /** + * @dataProvider provideMakeRedirectContent + * @param Title|string $title Title object or string for Title::newFromText() + * @param string $expected Serialized form of the content object built + * @covers WikitextContentHandler::makeRedirectContent + */ + public function testMakeRedirectContent( $title, $expected ) { + global $wgContLang; + $wgContLang->resetNamespaces(); + + if ( is_string( $title ) ) { + $title = Title::newFromText( $title ); + } + $content = $this->handler->makeRedirectContent( $title ); + $this->assertEquals( $expected, $content->serialize() ); + } + + public static function provideMakeRedirectContent() { + return array( + array( 'Hello', '#REDIRECT [[Hello]]' ), + array( 'Template:Hello', '#REDIRECT [[Template:Hello]]' ), + array( 'Hello#section', '#REDIRECT [[Hello#section]]' ), + array( 'user:john_doe#section', '#REDIRECT [[User:John doe#section]]' ), + array( 'MEDIAWIKI:FOOBAR', '#REDIRECT [[MediaWiki:FOOBAR]]' ), + array( 'Category:Foo', '#REDIRECT [[:Category:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'en:Foo' ), '#REDIRECT [[en:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'Foo', '', 'en' ), '#REDIRECT [[:en:Foo]]' ), + array( Title::makeTitle( NS_MAIN, 'Bar', 'fragment', 'google' ), '#REDIRECT [[google:Bar#fragment]]' ), + ); + } + + /** + * @dataProvider dataIsSupportedFormat + * @covers WikitextContentHandler::isSupportedFormat + */ + public function testIsSupportedFormat( $format, $supported ) { + $this->assertEquals( $supported, $this->handler->isSupportedFormat( $format ) ); + } + + public static function dataMerge3() { + return array( + array( + "first paragraph + + second paragraph\n", + + "FIRST paragraph + + second paragraph\n", + + "first paragraph + + SECOND paragraph\n", + + "FIRST paragraph + + SECOND paragraph\n", + ), + + array( "first paragraph + second paragraph\n", + + "Bla bla\n", + + "Blubberdibla\n", + + false, + ), + ); + } + + /** + * @dataProvider dataMerge3 + * @covers WikitextContentHandler::merge3 + */ + public function testMerge3( $old, $mine, $yours, $expected ) { + $this->checkHasDiff3(); + + // test merge + $oldContent = new WikitextContent( $old ); + $myContent = new WikitextContent( $mine ); + $yourContent = new WikitextContent( $yours ); + + $merged = $this->handler->merge3( $oldContent, $myContent, $yourContent ); + + $this->assertEquals( $expected, $merged ? $merged->getNativeData() : $merged ); + } + + public static function dataGetAutosummary() { + return array( + array( + 'Hello there, world!', + '#REDIRECT [[Foo]]', + 0, + '/^Redirected page .*Foo/' + ), + + array( + null, + 'Hello world!', + EDIT_NEW, + '/^Created page .*Hello/' + ), + + array( + 'Hello there, world!', + '', + 0, + '/^Blanked/' + ), + + array( + 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut + labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et + ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', + 'Hello world!', + 0, + '/^Replaced .*Hello/' + ), + + array( + 'foo', + 'bar', + 0, + '/^$/' + ), + ); + } + + /** + * @dataProvider dataGetAutosummary + * @covers WikitextContentHandler::getAutosummary + */ + public function testGetAutosummary( $old, $new, $flags, $expected ) { + $oldContent = is_null( $old ) ? null : new WikitextContent( $old ); + $newContent = is_null( $new ) ? null : new WikitextContent( $new ); + + $summary = $this->handler->getAutosummary( $oldContent, $newContent, $flags ); + + $this->assertTrue( (bool)preg_match( $expected, $summary ), "Autosummary didn't match expected pattern $expected: $summary" ); + } + + /** + * @todo Text case requires database, should be done by a test class in the Database group + */ + /* + public function testGetAutoDeleteReason( Title $title, &$hasHistory ) {} + */ + + /** + * @todo Text case requires database, should be done by a test class in the Database group + */ + /* + public function testGetUndoContent( Revision $current, Revision $undo, Revision $undoafter = null ) {} + */ +} diff --git a/tests/phpunit/includes/content/WikitextContentTest.php b/tests/phpunit/includes/content/WikitextContentTest.php new file mode 100644 index 00000000..9f20073d --- /dev/null +++ b/tests/phpunit/includes/content/WikitextContentTest.php @@ -0,0 +1,404 @@ +<?php + +/** + * @group ContentHandler + * + * @group Database + * ^--- needed, because we do need the database to test link updates + */ +class WikitextContentTest extends TextContentTest { + static $sections = "Intro + +== stuff == +hello world + +== test == +just a test + +== foo == +more stuff +"; + + public function newContent( $text ) { + return new WikitextContent( $text ); + } + + public static function dataGetParserOutput() { + return array( + array( + "WikitextContentTest_testGetParserOutput", + CONTENT_MODEL_WIKITEXT, + "hello ''world''\n", + "<p>hello <i>world</i>\n</p>" + ), + // TODO: more...? + ); + } + + public static function dataGetSecondaryDataUpdates() { + return array( + array( "WikitextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_WIKITEXT, "hello ''world''\n", + array( + 'LinksUpdate' => array( + 'mRecursive' => true, + 'mLinks' => array() + ) + ) + ), + array( "WikitextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n", + array( + 'LinksUpdate' => array( + 'mRecursive' => true, + 'mLinks' => array( + array( 'World_test_21344' => 0 ) + ) + ) + ) + ), + // TODO: more...? + ); + } + + /** + * @dataProvider dataGetSecondaryDataUpdates + * @group Database + * @covers WikitextContent::getSecondaryDataUpdates + */ + public function testGetSecondaryDataUpdates( $title, $model, $text, $expectedStuff ) { + $ns = $this->getDefaultWikitextNS(); + $title = Title::newFromText( $title, $ns ); + + $content = ContentHandler::makeContent( $text, $title, $model ); + + $page = WikiPage::factory( $title ); + $page->doEditContent( $content, '' ); + + $updates = $content->getSecondaryDataUpdates( $title ); + + // make updates accessible by class name + foreach ( $updates as $update ) { + $class = get_class( $update ); + $updates[$class] = $update; + } + + foreach ( $expectedStuff as $class => $fieldValues ) { + $this->assertArrayHasKey( $class, $updates, "missing an update of type $class" ); + + $update = $updates[$class]; + + foreach ( $fieldValues as $field => $value ) { + $v = $update->$field; #if the field doesn't exist, just crash and burn + $this->assertEquals( $value, $v, "unexpected value for field $field in instance of $class" ); + } + } + + $page->doDeleteArticle( '' ); + } + + public static function dataGetSection() { + return array( + array( WikitextContentTest::$sections, + "0", + "Intro" + ), + array( WikitextContentTest::$sections, + "2", + "== test == +just a test" + ), + array( WikitextContentTest::$sections, + "8", + false + ), + ); + } + + /** + * @dataProvider dataGetSection + * @covers WikitextContent::getSection + */ + public function testGetSection( $text, $sectionId, $expectedText ) { + $content = $this->newContent( $text ); + + $sectionContent = $content->getSection( $sectionId ); + if ( is_object( $sectionContent ) ) { + $sectionText = $sectionContent->getNativeData(); + } else { + $sectionText = $sectionContent; + } + + $this->assertEquals( $expectedText, $sectionText ); + } + + public static function dataReplaceSection() { + return array( + array( WikitextContentTest::$sections, + "0", + "No more", + null, + trim( preg_replace( '/^Intro/sm', 'No more', WikitextContentTest::$sections ) ) + ), + array( WikitextContentTest::$sections, + "", + "No more", + null, + "No more" + ), + array( WikitextContentTest::$sections, + "2", + "== TEST ==\nmore fun", + null, + trim( preg_replace( '/^== test ==.*== foo ==/sm', "== TEST ==\nmore fun\n\n== foo ==", WikitextContentTest::$sections ) ) + ), + array( WikitextContentTest::$sections, + "8", + "No more", + null, + WikitextContentTest::$sections + ), + array( WikitextContentTest::$sections, + "new", + "No more", + "New", + trim( WikitextContentTest::$sections ) . "\n\n\n== New ==\n\nNo more" + ), + ); + } + + /** + * @dataProvider dataReplaceSection + * @covers WikitextContent::replaceSection + */ + public function testReplaceSection( $text, $section, $with, $sectionTitle, $expected ) { + $content = $this->newContent( $text ); + $c = $content->replaceSection( $section, $this->newContent( $with ), $sectionTitle ); + + $this->assertEquals( $expected, is_null( $c ) ? null : $c->getNativeData() ); + } + + /** + * @covers WikitextContent::addSectionHeader + */ + public function testAddSectionHeader() { + $content = $this->newContent( 'hello world' ); + $content = $content->addSectionHeader( 'test' ); + + $this->assertEquals( "== test ==\n\nhello world", $content->getNativeData() ); + } + + public static function dataPreSaveTransform() { + return array( + array( 'hello this is ~~~', + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + ), + array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + ), + array( // rtrim + " Foo \n ", + " Foo", + ), + ); + } + + public static function dataPreloadTransform() { + return array( + array( 'hello this is ~~~', + "hello this is ~~~", + ), + array( 'hello \'\'this\'\' is <noinclude>foo</noinclude><includeonly>bar</includeonly>', + 'hello \'\'this\'\' is bar', + ), + ); + } + + public static function dataGetRedirectTarget() { + return array( + array( '#REDIRECT [[Test]]', + 'Test', + ), + array( '#REDIRECT Test', + null, + ), + array( '* #REDIRECT [[Test]]', + null, + ), + ); + } + + public static function dataGetTextForSummary() { + return array( + array( "hello\nworld.", + 16, + 'hello world.', + ), + array( 'hello world.', + 8, + 'hello...', + ), + array( '[[hello world]].', + 8, + 'hel...', + ), + ); + } + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getRedirectChain() { + $text = $this->getNativeData(); + return Title::newFromRedirectArray( $text ); + } + */ + + /** + * @todo Test needs database! Should be done by a test class in the Database group. + */ + /* + public function getUltimateRedirectTarget() { + $text = $this->getNativeData(); + return Title::newFromRedirectRecurse( $text ); + } + */ + + public static function dataIsCountable() { + return array( + array( '', + null, + 'any', + true + ), + array( 'Foo', + null, + 'any', + true + ), + array( 'Foo', + null, + 'comma', + false + ), + array( 'Foo, bar', + null, + 'comma', + true + ), + array( 'Foo', + null, + 'link', + false + ), + array( 'Foo [[bar]]', + null, + 'link', + true + ), + array( 'Foo', + true, + 'link', + true + ), + array( 'Foo [[bar]]', + false, + 'link', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'any', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'comma', + false + ), + array( '#REDIRECT [[bar]]', + true, + 'link', + false + ), + ); + } + + /** + * @covers WikitextContent::matchMagicWord + */ + public function testMatchMagicWord() { + $mw = MagicWord::get( "staticredirect" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]\n__STATICREDIRECT__" ); + $this->assertTrue( $content->matchMagicWord( $mw ), "should have matched magic word" ); + + $content = $this->newContent( "#REDIRECT [[FOO]]" ); + $this->assertFalse( $content->matchMagicWord( $mw ), "should not have matched magic word" ); + } + + /** + * @covers WikitextContent::updateRedirect + */ + public function testUpdateRedirect() { + $target = Title::newFromText( "testUpdateRedirect_target" ); + + // test with non-redirect page + $content = $this->newContent( "hello world." ); + $newContent = $content->updateRedirect( $target ); + + $this->assertTrue( $content->equals( $newContent ), "content should be unchanged" ); + + // test with actual redirect + $content = $this->newContent( "#REDIRECT [[Someplace]]" ); + $newContent = $content->updateRedirect( $target ); + + $this->assertFalse( $content->equals( $newContent ), "content should have changed" ); + $this->assertTrue( $newContent->isRedirect(), "new content should be a redirect" ); + + $this->assertEquals( $target->getFullText(), $newContent->getRedirectTarget()->getFullText() ); + } + + /** + * @covers WikitextContent::getModel + */ + public function testGetModel() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getModel() ); + } + + /** + * @covers WikitextContent::getContentHandler + */ + public function testGetContentHandler() { + $content = $this->newContent( "hello world." ); + + $this->assertEquals( CONTENT_MODEL_WIKITEXT, $content->getContentHandler()->getModelID() ); + } + + public static function dataEquals() { + return array( + array( new WikitextContent( "hallo" ), null, false ), + array( new WikitextContent( "hallo" ), new WikitextContent( "hallo" ), true ), + array( new WikitextContent( "hallo" ), new JavaScriptContent( "hallo" ), false ), + array( new WikitextContent( "hallo" ), new TextContent( "hallo" ), false ), + array( new WikitextContent( "hallo" ), new WikitextContent( "HALLO" ), false ), + ); + } + + public static function dataGetDeletionUpdates() { + return array( + array( "WikitextContentTest_testGetSecondaryDataUpdates_1", + CONTENT_MODEL_WIKITEXT, "hello ''world''\n", + array( 'LinksDeletionUpdate' => array() ) + ), + array( "WikitextContentTest_testGetSecondaryDataUpdates_2", + CONTENT_MODEL_WIKITEXT, "hello [[world test 21344]]\n", + array( 'LinksDeletionUpdate' => array() ) + ), + // @todo more...? + ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php new file mode 100644 index 00000000..ba63c091 --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseMysqlBaseTest.php @@ -0,0 +1,209 @@ +<?php +/** + * Holds tests for DatabaseMysqlBase MediaWiki class. + * + * @section LICENSE + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @author Antoine Musso + * @author Bryan Davis + * @copyright © 2013 Antoine Musso + * @copyright © 2013 Bryan Davis + * @copyright © 2013 Wikimedia Foundation Inc. + */ + +/** + * Fake class around abstract class so we can call concrete methods. + */ +class FakeDatabaseMysqlBase extends DatabaseMysqlBase { + // From DatabaseBase + protected function closeConnection() {} + protected function doQuery( $sql ) {} + + // From DatabaseMysql + protected function mysqlConnect( $realServer ) {} + protected function mysqlFreeResult( $res ) {} + protected function mysqlFetchObject( $res ) {} + protected function mysqlFetchArray( $res ) {} + protected function mysqlNumRows( $res ) {} + protected function mysqlNumFields( $res ) {} + protected function mysqlFieldName( $res, $n ) {} + protected function mysqlDataSeek( $res, $row ) {} + protected function mysqlError( $conn = null ) {} + protected function mysqlFetchField( $res, $n ) {} + protected function mysqlPing() {} + + // From interface DatabaseType + function insertId() {} + function lastErrno() {} + function affectedRows() {} + function getServerVersion() {} +} + +class DatabaseMysqlBaseTest extends MediaWikiTestCase { + + /** + * @dataProvider provideDiapers + * @covers DatabaseMysqlBase::addIdentifierQuotes + */ + public function testAddIdentifierQuotes( $expected, $in ) { + $db = new FakeDatabaseMysqlBase(); + $quoted = $db->addIdentifierQuotes( $in ); + $this->assertEquals($expected, $quoted); + } + + + /** + * Feeds testAddIdentifierQuotes + * + * Named per bug 20281 convention. + */ + function provideDiapers() { + return array( + // Format: expected, input + array( '``', '' ), + + // Yeah I really hate loosely typed PHP idiocies nowadays + array( '``', null ), + + // Dear codereviewer, guess what addIdentifierQuotes() + // will return with thoses: + array( '``', false ), + array( '`1`', true ), + + // We never know what could happen + array( '`0`', 0 ), + array( '`1`', 1 ), + + // Whatchout! Should probably use something more meaningful + array( "`'`", "'" ), # single quote + array( '`"`', '"' ), # double quote + array( '````', '`' ), # backtick + array( '`’`', '’' ), # apostrophe (look at your encyclopedia) + + // sneaky NUL bytes are lurking everywhere + array( '``', "\0" ), + array( '`xyzzy`', "\0x\0y\0z\0z\0y\0" ), + + // unicode chars + array( + self::createUnicodeString( '`\u0001a\uFFFFb`' ), + self::createUnicodeString( '\u0001a\uFFFFb' ) + ), + array( + self::createUnicodeString( '`\u0001\uFFFF`' ), + self::createUnicodeString( '\u0001\u0000\uFFFF\u0000' ) + ), + array( '`☃`', '☃' ), + array( '`メインページ`', 'メインページ' ), + array( '`Басты_бет`', 'Басты_бет' ), + + // Real world: + array( '`Alix`', 'Alix' ), # while( ! $recovered ) { sleep(); } + array( '`Backtick: ```', 'Backtick: `' ), + array( '`This is a test`', 'This is a test' ), + ); + } + + private static function createUnicodeString($str) { + return json_decode( '"' . $str . '"' ); + } + + function getMockForViews() { + $db = $this->getMockBuilder( 'DatabaseMysql' ) + ->disableOriginalConstructor() + ->setMethods( array( 'fetchRow', 'query' ) ) + ->getMock(); + + $db->expects( $this->any() ) + ->method( 'query' ) + ->with( $this->anything() ) + ->will( + $this->returnValue( null ) + ); + + $db->expects( $this->any() ) + ->method( 'fetchRow' ) + ->with( $this->anything() ) + ->will( $this->onConsecutiveCalls( + array( 'Tables_in_' => 'view1' ), + array( 'Tables_in_' => 'view2' ), + array( 'Tables_in_' => 'myview' ), + false # no more rows + )); + return $db; + } + /** + * @covers DatabaseMysqlBase::listViews + */ + function testListviews() { + $db = $this->getMockForViews(); + + // The first call populate an internal cache of views + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews() ); + + // Prefix filtering + $this->assertEquals( array( 'view1', 'view2' ), + $db->listViews( 'view' ) ); + $this->assertEquals( array( 'myview' ), + $db->listViews( 'my' ) ); + $this->assertEquals( array(), + $db->listViews( 'UNUSED_PREFIX' ) ); + $this->assertEquals( array( 'view1', 'view2', 'myview'), + $db->listViews( '' ) ); + } + + /** + * @covers DatabaseMysqlBase::isView + * @dataProvider provideViewExistanceChecks + */ + function testIsView( $isView, $viewName ) { + $db = $this->getMockForViews(); + + switch( $isView ) { + case true: + $this->assertTrue( $db->isView( $viewName ), + "$viewName should be considered a view" ); + break; + + case false: + $this->assertFalse( $db->isView( $viewName ), + "$viewName has not been defined as a view" ); + break; + } + + } + + function provideViewExistanceChecks() { + return array( + // format: whether it is a view, view name + array( true, 'view1' ), + array( true, 'view2' ), + array( true, 'myview' ), + + array( false, 'user' ), + + array( false, 'view10' ), + array( false, 'my' ), + array( false, 'OH_MY_GOD' ), # they killed kenny! + ); + } + +} diff --git a/tests/phpunit/includes/db/DatabaseSQLTest.php b/tests/phpunit/includes/db/DatabaseSQLTest.php index e37cd445..bdd567e7 100644 --- a/tests/phpunit/includes/db/DatabaseSQLTest.php +++ b/tests/phpunit/includes/db/DatabaseSQLTest.php @@ -2,34 +2,44 @@ /** * Test the abstract database layer - * Using Mysql for the sql at the moment TODO - * - * @group Database + * This is a non DBMS depending test. */ class DatabaseSQLTest extends MediaWikiTestCase { - public function setUp() { - // TODO support other DBMS or find another way to do it - if( $this->db->getType() !== 'mysql' ) { - $this->markTestSkipped( 'No mysql database' ); - } + /** + * @var DatabaseTestHelper + */ + private $database; + + protected function setUp() { + parent::setUp(); + $this->database = new DatabaseTestHelper( __CLASS__ ); + } + + protected function assertLastSql( $sqlText ) { + $this->assertEquals( + $this->database->getLastSqls(), + $sqlText + ); } /** - * @dataProvider dataSelectSQLText + * @dataProvider provideSelect + * @covers DatabaseBase::select */ - function testSelectSQLText( $sql, $sqlText ) { - $this->assertEquals( trim( $this->db->selectSQLText( - isset( $sql['tables'] ) ? $sql['tables'] : array(), - isset( $sql['fields'] ) ? $sql['fields'] : array(), + public function testSelect( $sql, $sqlText ) { + $this->database->select( + $sql['tables'], + $sql['fields'], isset( $sql['conds'] ) ? $sql['conds'] : array(), __METHOD__, isset( $sql['options'] ) ? $sql['options'] : array(), isset( $sql['join_conds'] ) ? $sql['join_conds'] : array() - ) ), $sqlText ); + ); + $this->assertLastSql( $sqlText ); } - function dataSelectSQLText() { + public static function provideSelect() { return array( array( array( @@ -37,9 +47,9 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'fields' => array( 'field', 'alias' => 'field2' ), 'conds' => array( 'alias' => 'text' ), ), - "SELECT field,field2 AS alias " . - "FROM `unittest_table` " . - "WHERE alias = 'text'" + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text'" ), array( array( @@ -48,11 +58,11 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'conds' => array( 'alias' => 'text' ), 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), ), - "SELECT field,field2 AS alias " . - "FROM `unittest_table` " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" + "SELECT field,field2 AS alias " . + "FROM table " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" ), array( array( @@ -62,13 +72,13 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'ORDER BY' => 'field' ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "ORDER BY field " . - "LIMIT 1" + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "ORDER BY field " . + "LIMIT 1" ), array( array( @@ -78,13 +88,13 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'GROUP BY' => 'field', 'HAVING' => 'COUNT(*) > 1' ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field HAVING COUNT(*) > 1 " . - "LIMIT 1" + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field HAVING COUNT(*) > 1 " . + "LIMIT 1" ), array( array( @@ -94,29 +104,466 @@ class DatabaseSQLTest extends MediaWikiTestCase { 'options' => array( 'LIMIT' => 1, 'GROUP BY' => array( 'field', 'field2' ), 'HAVING' => array( 'COUNT(*) > 1', 'field' => 1 ) ), 'join_conds' => array( 't2' => array( 'LEFT JOIN', 'tid = t2.id' - )), + ) ), + ), + "SELECT tid,field,field2 AS alias,t2.id " . + "FROM table LEFT JOIN table2 t2 ON ((tid = t2.id)) " . + "WHERE alias = 'text' " . + "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . + "LIMIT 1" + ), + array( + array( + 'tables' => array( 'table' ), + 'fields' => array( 'alias' => 'field' ), + 'conds' => array( 'alias' => array( 1, 2, 3, 4 ) ), + ), + "SELECT field AS alias " . + "FROM table " . + "WHERE alias IN ('1','2','3','4')" + ), + ); + } + + /** + * @dataProvider provideUpdate + * @covers DatabaseBase::update + */ + public function testUpdate( $sql, $sqlText ) { + $this->database->update( + $sql['table'], + $sql['values'], + $sql['conds'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpdate() { + return array( + array( + array( + 'table' => 'table', + 'values' => array( 'field' => 'text', 'field2' => 'text2' ), + 'conds' => array( 'alias' => 'text' ), + ), + "UPDATE table " . + "SET field = 'text'" . + ",field2 = 'text2' " . + "WHERE alias = 'text'" + ), + array( + array( + 'table' => 'table', + 'values' => array( 'field = other', 'field2' => 'text2' ), + 'conds' => array( 'id' => '1' ), + ), + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2' " . + "WHERE id = '1'" + ), + array( + array( + 'table' => 'table', + 'values' => array( 'field = other', 'field2' => 'text2' ), + 'conds' => '*', + ), + "UPDATE table " . + "SET field = other" . + ",field2 = 'text2'" + ), + ); + } + + /** + * @dataProvider provideDelete + * @covers DatabaseBase::delete + */ + public function testDelete( $sql, $sqlText ) { + $this->database->delete( + $sql['table'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDelete() { + return array( + array( + array( + 'table' => 'table', + 'conds' => array( 'alias' => 'text' ), + ), + "DELETE FROM table " . + "WHERE alias = 'text'" + ), + array( + array( + 'table' => 'table', + 'conds' => '*', + ), + "DELETE FROM table" + ), + ); + } + + /** + * @dataProvider provideUpsert + * @covers DatabaseBase::upsert + */ + public function testUpsert( $sql, $sqlText ) { + $this->database->upsert( + $sql['table'], + $sql['rows'], + $sql['uniqueIndexes'], + $sql['set'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideUpsert() { + return array( + array( + array( + 'table' => 'upsert_table', + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + 'uniqueIndexes' => array( 'field' ), + 'set' => array( 'field' => 'set' ), + ), + "BEGIN; " . + "UPDATE upsert_table " . + "SET field = 'set' " . + "WHERE ((field = 'text')); " . + "INSERT IGNORE INTO upsert_table " . + "(field,field2) " . + "VALUES ('text','text2'); " . + "COMMIT" + ), + ); + } + + /** + * @dataProvider provideDeleteJoin + * @covers DatabaseBase::deleteJoin + */ + public function testDeleteJoin( $sql, $sqlText ) { + $this->database->deleteJoin( + $sql['delTable'], + $sql['joinTable'], + $sql['delVar'], + $sql['joinVar'], + $sql['conds'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideDeleteJoin() { + return array( + array( + array( + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => array( 'alias' => 'text' ), + ), + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join WHERE alias = 'text'" . + ")" + ), + array( + array( + 'delTable' => 'table', + 'joinTable' => 'table_join', + 'delVar' => 'field', + 'joinVar' => 'field_join', + 'conds' => '*', + ), + "DELETE FROM table " . + "WHERE field IN (" . + "SELECT field_join FROM table_join " . + ")" + ), + ); + } + + /** + * @dataProvider provideInsert + * @covers DatabaseBase::insert + */ + public function testInsert( $sql, $sqlText ) { + $this->database->insert( + $sql['table'], + $sql['rows'], + __METHOD__, + isset( $sql['options'] ) ? $sql['options'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideInsert() { + return array( + array( + array( + 'table' => 'table', + 'rows' => array( 'field' => 'text', 'field2' => 2 ), + ), + "INSERT INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ), + array( + array( + 'table' => 'table', + 'rows' => array( 'field' => 'text', 'field2' => 2 ), + 'options' => 'IGNORE', + ), + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES ('text','2')" + ), + array( + array( + 'table' => 'table', + 'rows' => array( + array( 'field' => 'text', 'field2' => 2 ), + array( 'field' => 'multi', 'field2' => 3 ), + ), + 'options' => 'IGNORE', + ), + "INSERT IGNORE INTO table " . + "(field,field2) " . + "VALUES " . + "('text','2')," . + "('multi','3')" + ), + ); + } + + /** + * @dataProvider provideInsertSelect + * @covers DatabaseBase::insertSelect + */ + public function testInsertSelect( $sql, $sqlText ) { + $this->database->insertSelect( + $sql['destTable'], + $sql['srcTable'], + $sql['varMap'], + $sql['conds'], + __METHOD__, + isset( $sql['insertOptions'] ) ? $sql['insertOptions'] : array(), + isset( $sql['selectOptions'] ) ? $sql['selectOptions'] : array() + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideInsertSelect() { + return array( + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => '*', + ), + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table" + ), + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => array( 'field' => 2 ), + ), + "INSERT INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2'" + ), + array( + array( + 'destTable' => 'insert_table', + 'srcTable' => 'select_table', + 'varMap' => array( 'field_insert' => 'field_select', 'field' => 'field2' ), + 'conds' => array( 'field' => 2 ), + 'insertOptions' => 'IGNORE', + 'selectOptions' => array( 'ORDER BY' => 'field' ), ), - "SELECT tid,field,field2 AS alias,t2.id " . - "FROM `unittest_table` LEFT JOIN `unittest_table2` `t2` ON ((tid = t2.id)) " . - "WHERE alias = 'text' " . - "GROUP BY field,field2 HAVING (COUNT(*) > 1) AND field = '1' " . - "LIMIT 1" + "INSERT IGNORE INTO insert_table " . + "(field_insert,field) " . + "SELECT field_select,field2 " . + "FROM select_table " . + "WHERE field = '2' " . + "ORDER BY field" ), ); } /** - * @dataProvider dataConditional + * @dataProvider provideReplace + * @covers DatabaseBase::replace */ - function testConditional( $sql, $sqlText ) { - $this->assertEquals( trim( $this->db->conditional( + public function testReplace( $sql, $sqlText ) { + $this->database->replace( + $sql['table'], + $sql['uniqueIndexes'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideReplace() { + return array( + array( + array( + 'table' => 'replace_table', + 'uniqueIndexes' => array( 'field' ), + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + ), + "DELETE FROM replace_table " . + "WHERE ( field='text' ); " . + "INSERT INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), + 'rows' => array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( array( 'md_module', 'md_skin' ) ), + 'rows' => array( + array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), array( + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ), + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' AND md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' AND md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array( 'md_module', 'md_skin' ), + 'rows' => array( + array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), array( + 'md_module' => 'module2', + 'md_skin' => 'skin2', + 'md_deps' => 'deps2', + ), + ), + ), + "DELETE FROM module_deps " . + "WHERE ( md_module='module' ) OR ( md_skin='skin' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps'); " . + "DELETE FROM module_deps " . + "WHERE ( md_module='module2' ) OR ( md_skin='skin2' ); " . + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module2','skin2','deps2')" + ), + array( + array( + 'table' => 'module_deps', + 'uniqueIndexes' => array(), + 'rows' => array( + 'md_module' => 'module', + 'md_skin' => 'skin', + 'md_deps' => 'deps', + ), + ), + "INSERT INTO module_deps " . + "(md_module,md_skin,md_deps) " . + "VALUES ('module','skin','deps')" + ), + ); + } + + /** + * @dataProvider provideNativeReplace + * @covers DatabaseBase::nativeReplace + */ + public function testNativeReplace( $sql, $sqlText ) { + $this->database->nativeReplace( + $sql['table'], + $sql['rows'], + __METHOD__ + ); + $this->assertLastSql( $sqlText ); + } + + public static function provideNativeReplace() { + return array( + array( + array( + 'table' => 'replace_table', + 'rows' => array( 'field' => 'text', 'field2' => 'text2' ), + ), + "REPLACE INTO replace_table " . + "(field,field2) " . + "VALUES ('text','text2')" + ), + ); + } + + /** + * @dataProvider provideConditional + * @covers DatabaseBase::conditional + */ + public function testConditional( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->conditional( $sql['conds'], $sql['true'], $sql['false'] ) ), $sqlText ); } - function dataConditional() { + public static function provideConditional() { return array( array( array( @@ -144,4 +591,131 @@ class DatabaseSQLTest extends MediaWikiTestCase { ), ); } -}
\ No newline at end of file + + /** + * @dataProvider provideBuildConcat + * @covers DatabaseBase::buildConcat + */ + public function testBuildConcat( $stringList, $sqlText ) { + $this->assertEquals( trim( $this->database->buildConcat( + $stringList + ) ), $sqlText ); + } + + public static function provideBuildConcat() { + return array( + array( + array( 'field', 'field2' ), + "CONCAT(field,field2)" + ), + array( + array( "'test'", 'field2' ), + "CONCAT('test',field2)" + ), + ); + } + + /** + * @dataProvider provideBuildLike + * @covers DatabaseBase::buildLike + */ + public function testBuildLike( $array, $sqlText ) { + $this->assertEquals( trim( $this->database->buildLike( + $array + ) ), $sqlText ); + } + + public static function provideBuildLike() { + return array( + array( + 'text', + "LIKE 'text'" + ), + array( + array( 'text', new LikeMatch( '%' ) ), + "LIKE 'text%'" + ), + array( + array( 'text', new LikeMatch( '%' ), 'text2' ), + "LIKE 'text%text2'" + ), + array( + array( 'text', new LikeMatch( '_' ) ), + "LIKE 'text_'" + ), + ); + } + + /** + * @dataProvider provideUnionQueries + * @covers DatabaseBase::unionQueries + */ + public function testUnionQueries( $sql, $sqlText ) { + $this->assertEquals( trim( $this->database->unionQueries( + $sql['sqls'], + $sql['all'] + ) ), $sqlText ); + } + + public static function provideUnionQueries() { + return array( + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL' ), + 'all' => true, + ), + "(RAW SQL) UNION ALL (RAW2SQL)" + ), + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL' ), + 'all' => false, + ), + "(RAW SQL) UNION (RAW2SQL)" + ), + array( + array( + 'sqls' => array( 'RAW SQL', 'RAW2SQL', 'RAW3SQL' ), + 'all' => false, + ), + "(RAW SQL) UNION (RAW2SQL) UNION (RAW3SQL)" + ), + ); + } + + /** + * @covers DatabaseBase::commit + */ + public function testTransactionCommit() { + $this->database->begin( __METHOD__ ); + $this->database->commit( __METHOD__ ); + $this->assertLastSql( 'BEGIN; COMMIT' ); + } + + /** + * @covers DatabaseBase::rollback + */ + public function testTransactionRollback() { + $this->database->begin( __METHOD__ ); + $this->database->rollback( __METHOD__ ); + $this->assertLastSql( 'BEGIN; ROLLBACK' ); + } + + /** + * @covers DatabaseBase::dropTable + */ + public function testDropTable() { + $this->database->setExistingTables( array( 'table' ) ); + $this->database->dropTable( 'table', __METHOD__ ); + $this->assertLastSql( 'DROP TABLE table' ); + } + + /** + * @covers DatabaseBase::dropTable + */ + public function testDropNonExistingTable() { + $this->assertFalse( + $this->database->dropTable( 'non_existing', __METHOD__ ) + ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php index d226598b..70ee9465 100644 --- a/tests/phpunit/includes/db/DatabaseSqliteTest.php +++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php @@ -3,16 +3,20 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone { var $lastQuery; - function __construct( ) { + function __construct() { parent::__construct( ':memory:' ); } function query( $sql, $fname = '', $tempIgnore = false ) { $this->lastQuery = $sql; + return true; } - function replaceVars( $s ) { + /** + * Override parent visibility to public + */ + public function replaceVars( $s ) { return parent::replaceVars( $s ); } } @@ -20,11 +24,18 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone { /** * @group sqlite * @group Database + * @group medium */ class DatabaseSqliteTest extends MediaWikiTestCase { + + /** + * @var MockDatabaseSqlite + */ var $db; - public function setUp() { + protected function setUp() { + parent::setUp(); + if ( !Sqlite::isPresent() ) { $this->markTestSkipped( 'No SQLite support detected' ); } @@ -42,8 +53,8 @@ class DatabaseSqliteTest extends MediaWikiTestCase { private function assertResultIs( $expected, $res ) { $this->assertNotNull( $res ); $i = 0; - foreach( $res as $row ) { - foreach( $expected[$i] as $key => $value ) { + foreach ( $res as $row ) { + foreach ( $expected[$i] as $key => $value ) { $this->assertTrue( isset( $row->$key ) ); $this->assertEquals( $value, $row->$key ); } @@ -52,44 +63,97 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( count( $expected ), $i, 'Unexpected number of rows' ); } + public static function provideAddQuotes() { + return array( + array( // #0: empty + '', "''" + ), + array( // #1: simple + 'foo bar', "'foo bar'" + ), + array( // #2: including quote + 'foo\'bar', "'foo''bar'" + ), + array( // #3: including \0 (must be represented as hex, per https://bugs.php.net/bug.php?id=63419) + "x\0y", + "x'780079'", + ), + array( // #4: blob object (must be represented as hex) + new Blob( "hello" ), + "x'68656c6c6f'", + ), + ); + } + + /** + * @dataProvider provideAddQuotes() + * @covers DatabaseSqlite::addQuotes + */ + public function testAddQuotes( $value, $expected ) { + // check quoting + $db = new DatabaseSqliteStandalone( ':memory:' ); + $this->assertEquals( $expected, $db->addQuotes( $value ), 'string not quoted as expected' ); + + // ok, quoting works as expected, now try a round trip. + $re = $db->query( 'select ' . $db->addQuotes( $value ) ); + + $this->assertTrue( $re !== false, 'query failed' ); + + if ( $row = $re->fetchRow() ) { + if ( $value instanceof Blob ) { + $value = $value->fetch(); + } + + $this->assertEquals( $value, $row[0], 'string mangled by the database' ); + } else { + $this->fail( 'query returned no result' ); + } + } + + /** + * @covers DatabaseSqlite::replaceVars + */ public function testReplaceVars() { $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" ); $this->assertEquals( "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", + . "foo_bar TEXT, foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", $this->replaceVars( "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, foo_bar char(13), foo_name varchar(255) binary NOT NULL DEFAULT '', foo_int tinyint ( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;" ) - ); + ); $this->assertEquals( "CREATE TABLE foo ( foo1 REAL, foo2 REAL, foo3 REAL );", $this->replaceVars( "CREATE TABLE foo ( foo1 FLOAT, foo2 DOUBLE( 1,10), foo3 DOUBLE PRECISION );" ) - ); + ); $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );", $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" ) - ); + ); $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );", $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ), 'Table name changed' - ); + ); $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", - $this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) - ); + $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY NOT NULL AUTO_INCREMENT );" ) + ); $this->assertEquals( "CREATE TABLE foo ( foobar INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL );", - $this->replaceVars("CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) - ); + $this->replaceVars( "CREATE TABLE foo ( foobar INT PRIMARY KEY AUTO_INCREMENT NOT NULL );" ) + ); $this->assertEquals( "CREATE TABLE enums( enum1 TEXT, myenum TEXT)", $this->replaceVars( "CREATE TABLE enums( enum1 ENUM('A', 'B'), myenum ENUM ('X', 'Y'))" ) - ); + ); $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42", $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42" ) - ); + ); } + /** + * @covers DatabaseSqlite::tableName + */ public function testTableName() { // @todo Moar! $db = new DatabaseSqliteStandalone( ':memory:' ); @@ -100,6 +164,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $this->assertEquals( 'foobar', $db->tableName( 'bar' ) ); } + /** + * @covers DatabaseSqlite::duplicateTableStructure + */ public function testDuplicateTableStructure() { $db = new DatabaseSqliteStandalone( ':memory:' ); $db->query( 'CREATE TABLE foo(foo, barfoo)' ); @@ -121,6 +188,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { ); } + /** + * @covers DatabaseSqlite::duplicateTableStructure + */ public function testDuplicateTableStructureVirtual() { $db = new DatabaseSqliteStandalone( ':memory:' ); if ( $db->getFulltextSearchModule() != 'FTS3' ) { @@ -141,6 +211,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { ); } + /** + * @covers DatabaseSqlite::deleteJoin + */ public function testDeleteJoin() { $db = new DatabaseSqliteStandalone( ':memory:' ); $db->query( 'CREATE TABLE a (a_1)', __METHOD__ ); @@ -180,10 +253,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase { /** * Runs upgrades of older databases and compares results with current schema - * @todo: currently only checks list of tables + * @todo Currently only checks list of tables */ public function testUpgrades() { - global $IP, $wgVersion; + global $IP, $wgVersion, $wgProfileToDatabase; // Versions tested $versions = array( @@ -202,6 +275,9 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $currentDB = new DatabaseSqliteStandalone( ':memory:' ); $currentDB->sourceFile( "$IP/maintenance/tables.sql" ); + if ( $wgProfileToDatabase ) { + $currentDB->sourceFile( "$IP/maintenance/sqlite/archives/patch-profiling.sql" ); + } $currentTables = $this->getTables( $currentDB ); sort( $currentTables ); @@ -250,13 +326,19 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } } + /** + * @covers DatabaseSqlite::insertId + */ public function testInsertIdType() { $db = new DatabaseSqliteStandalone( ':memory:' ); - $this->assertInstanceOf( 'ResultWrapper', - $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ), "Database creationg" ); - $this->assertTrue( $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ), - "Insertion worked" ); - $this->assertEquals( "integer", gettype( $db->insertId() ), "Actual typecheck" ); + + $databaseCreation = $db->query( 'CREATE TABLE a ( a_1 )', __METHOD__ ); + $this->assertInstanceOf( 'ResultWrapper', $databaseCreation, "Database creation" ); + + $insertion = $db->insert( 'a', array( 'a_1' => 10 ), __METHOD__ ); + $this->assertTrue( $insertion, "Insertion worked" ); + + $this->assertInternalType( 'integer', $db->insertId(), "Actual typecheck" ); $this->assertTrue( $db->close(), "closing database" ); } @@ -272,12 +354,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" ); $updater = DatabaseUpdater::newForDB( $db, false, $maint ); $updater->doUpdates( array( 'core' ) ); + return $db; } private function getTables( $db ) { $list = array_flip( $db->listTables() ); $excluded = array( + 'external_user', // removed from core in 1.22 'math', // moved out of core in 1.18 'trackbacks', // removed from core in 1.19 'searchindex', @@ -293,6 +377,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { } $list = array_flip( $list ); sort( $list ); + return $list; } @@ -304,6 +389,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $cols[$col->name] = $col; } ksort( $cols ); + return $cols; } @@ -321,6 +407,15 @@ class DatabaseSqliteTest extends MediaWikiTestCase { $indexes[$index->name] = $index; } ksort( $indexes ); + return $indexes; } + + public function testCaseInsensitiveLike() { + // TODO: Test this for all databases + $db = new DatabaseSqliteStandalone( ':memory:' ); + $res = $db->query( 'SELECT "a" LIKE "A" AS a' ); + $row = $res->fetchRow(); + $this->assertFalse( (bool)$row['a'] ); + } } diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php index 379ffb17..301fc990 100644 --- a/tests/phpunit/includes/db/DatabaseTest.php +++ b/tests/phpunit/includes/db/DatabaseTest.php @@ -5,20 +5,28 @@ * @group DatabaseBase */ class DatabaseTest extends MediaWikiTestCase { - var $db, $functionTest = false; + /** + * @var DatabaseBase + */ + var $db; + var $functionTest = false; - function setUp() { + protected function setUp() { + parent::setUp(); $this->db = wfGetDB( DB_MASTER ); } - function tearDown() { + protected function tearDown() { + parent::tearDown(); if ( $this->functionTest ) { $this->dropFunctions(); $this->functionTest = false; } } - - function testAddQuotesNull() { + /** + * @covers DatabaseBase::dropTable + */ + public function testAddQuotesNull() { $check = "NULL"; if ( $this->db->getType() === 'sqlite' || $this->db->getType() === 'oracle' ) { $check = "''"; @@ -26,7 +34,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $this->db->addQuotes( null ) ); } - function testAddQuotesInt() { + public function testAddQuotesInt() { # returning just "1234" should be ok too, though... # maybe $this->assertEquals( @@ -34,20 +42,20 @@ class DatabaseTest extends MediaWikiTestCase { $this->db->addQuotes( 1234 ) ); } - function testAddQuotesFloat() { + public function testAddQuotesFloat() { # returning just "1234.5678" would be ok too, though $this->assertEquals( "'1234.5678'", $this->db->addQuotes( 1234.5678 ) ); } - function testAddQuotesString() { + public function testAddQuotesString() { $this->assertEquals( "'string'", $this->db->addQuotes( 'string' ) ); } - function testAddQuotesStringQuote() { + public function testAddQuotesStringQuote() { $check = "'string''s cause trouble'"; if ( $this->db->getType() === 'mysql' ) { $check = "'string\'s cause trouble'"; @@ -82,36 +90,46 @@ class DatabaseTest extends MediaWikiTestCase { $quote = ''; } elseif ( $this->db->getType() === 'mysql' ) { $quote = '`'; + } elseif ( $this->db->getType() === 'oracle' ) { + $quote = '/*Q*/'; } else { $quote = '"'; } if ( $database !== null ) { - $database = $quote . $database . $quote . '.'; + if ( $this->db->getType() === 'oracle' ) { + $database = $quote . $database . '.'; + } else { + $database = $quote . $database . $quote . '.'; + } } if ( $prefix === null ) { $prefix = $this->dbPrefix(); } - return $database . $quote . $prefix . $table . $quote; + if ( $this->db->getType() === 'oracle' ) { + return strtoupper($database . $quote . $prefix . $table); + } else { + return $database . $quote . $prefix . $table . $quote; + } } - function testTableNameLocal() { + public function testTableNameLocal() { $this->assertEquals( $this->prefixAndQuote( 'tablename' ), $this->db->tableName( 'tablename' ) ); } - function testTableNameRawLocal() { + public function testTableNameRawLocal() { $this->assertEquals( $this->prefixAndQuote( 'tablename', null, null, 'raw' ), $this->db->tableName( 'tablename', 'raw' ) ); } - function testTableNameShared() { + public function testTableNameShared() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_' ), $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_' ) @@ -123,7 +141,7 @@ class DatabaseTest extends MediaWikiTestCase { ); } - function testTableNameRawShared() { + public function testTableNameRawShared() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'sharedatabase', 'sh_', 'raw' ), $this->getSharedTableName( 'tablename', 'sharedatabase', 'sh_', 'raw' ) @@ -135,21 +153,21 @@ class DatabaseTest extends MediaWikiTestCase { ); } - function testTableNameForeign() { + public function testTableNameForeign() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'databasename', '' ), $this->db->tableName( 'databasename.tablename' ) ); } - function testTableNameRawForeign() { + public function testTableNameRawForeign() { $this->assertEquals( $this->prefixAndQuote( 'tablename', 'databasename', '', 'raw' ), $this->db->tableName( 'databasename.tablename', 'raw' ) ); } - function testFillPreparedEmpty() { + public function testFillPreparedEmpty() { $sql = $this->db->fillPrepared( 'SELECT * FROM interwiki', array() ); $this->assertEquals( @@ -157,7 +175,7 @@ class DatabaseTest extends MediaWikiTestCase { $sql ); } - function testFillPreparedQuestion() { + public function testFillPreparedQuestion() { $sql = $this->db->fillPrepared( 'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?', array( 4, "Snicker's_paradox" ) ); @@ -169,7 +187,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $sql ); } - function testFillPreparedBang() { + public function testFillPreparedBang() { $sql = $this->db->fillPrepared( 'SELECT user_id FROM ! WHERE user_name=?', array( '"user"', "Slash's Dot" ) ); @@ -181,7 +199,7 @@ class DatabaseTest extends MediaWikiTestCase { $this->assertEquals( $check, $sql ); } - function testFillPreparedRaw() { + public function testFillPreparedRaw() { $sql = $this->db->fillPrepared( "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'", array( '"user"', "Slash's Dot" ) ); @@ -190,10 +208,7 @@ class DatabaseTest extends MediaWikiTestCase { $sql ); } - /** - * @group Broken - */ - function testStoredFunctions() { + public function testStoredFunctions() { if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) { $this->markTestSkipped( 'MySQL or Postgres required' ); } @@ -207,9 +222,13 @@ class DatabaseTest extends MediaWikiTestCase { private function dropFunctions() { $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function' - . ( $this->db->getType() == 'postgres' ? '()' : '' ) + . ( $this->db->getType() == 'postgres' ? '()' : '' ) ); } -} - + public function testUnknownTableCorruptsResults() { + $res = $this->db->select( 'page', '*', array( 'page_id' => 1 ) ); + $this->assertFalse( $this->db->tableExists( 'foobarbaz' ) ); + $this->assertInternalType( 'int', $res->numRows() ); + } +} diff --git a/tests/phpunit/includes/db/DatabaseTestHelper.php b/tests/phpunit/includes/db/DatabaseTestHelper.php new file mode 100644 index 00000000..790f273c --- /dev/null +++ b/tests/phpunit/includes/db/DatabaseTestHelper.php @@ -0,0 +1,166 @@ +<?php + +/** + * Helper for testing the methods from the DatabaseBase class + * @since 1.22 + */ +class DatabaseTestHelper extends DatabaseBase { + + /** + * __CLASS__ of the test suite, + * used to determine, if the function name is passed every time to query() + */ + protected $testName = array(); + + /** + * Array of lastSqls passed to query(), + * This is an array since some methods in DatabaseBase can do more than one + * query. Cleared when calling getLastSqls(). + */ + protected $lastSqls = array(); + + /** + * Array of tables to be considered as existing by tableExist() + * Use setExistingTables() to alter. + */ + protected $tablesExists; + + public function __construct( $testName ) { + $this->testName = $testName; + } + + /** + * Returns SQL queries grouped by '; ' + * Clear the list of queries that have been done so far. + */ + public function getLastSqls() { + $lastSqls = implode( '; ', $this->lastSqls ); + $this->lastSqls = array(); + + return $lastSqls; + } + + public function setExistingTables( $tablesExists ) { + $this->tablesExists = (array)$tablesExists; + } + + protected function addSql( $sql ) { + // clean up spaces before and after some words and the whole string + $this->lastSqls[] = trim( preg_replace( + '/\s{2,}(?=FROM|WHERE|GROUP BY|ORDER BY|LIMIT)|(?<=SELECT|INSERT|UPDATE)\s{2,}/', + ' ', $sql + ) ); + } + + protected function checkFunctionName( $fname ) { + if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) { + throw new MWException( 'function name does not start with test class. ' . + $fname . ' vs. ' . $this->testName . '. ' . + 'Please provide __METHOD__ to database methods.' ); + } + } + + function strencode( $s ) { + // Choose apos to avoid handling of escaping double quotes in quoted text + return str_replace( "'", "\'", $s ); + } + + public function addIdentifierQuotes( $s ) { + // no escaping to avoid handling of double quotes in quoted text + return $s; + } + + public function query( $sql, $fname = '', $tempIgnore = false ) { + $this->checkFunctionName( $fname ); + $this->addSql( $sql ); + + return parent::query( $sql, $fname, $tempIgnore ); + } + + public function tableExists( $table, $fname = __METHOD__ ) { + $this->checkFunctionName( $fname ); + + return in_array( $table, (array)$this->tablesExists ); + } + + // Redeclare parent method to make it public + public function nativeReplace( $table, $rows, $fname ) { + return parent::nativeReplace( $table, $rows, $fname ); + } + + function getType() { + return 'test'; + } + + function open( $server, $user, $password, $dbName ) { + return false; + } + + function fetchObject( $res ) { + return false; + } + + function fetchRow( $res ) { + return false; + } + + function numRows( $res ) { + return -1; + } + + function numFields( $res ) { + return -1; + } + + function fieldName( $res, $n ) { + return 'test'; + } + + function insertId() { + return -1; + } + + function dataSeek( $res, $row ) { + /* nop */ + } + + function lastErrno() { + return -1; + } + + function lastError() { + return 'test'; + } + + function fieldInfo( $table, $field ) { + return false; + } + + function indexInfo( $table, $index, $fname = 'Database::indexInfo' ) { + return false; + } + + function affectedRows() { + return -1; + } + + function getSoftwareLink() { + return 'test'; + } + + function getServerVersion() { + return 'test'; + } + + function getServerInfo() { + return 'test'; + } + + protected function closeConnection() { + return false; + } + + protected function doQuery( $sql ) { + return array(); + } +} diff --git a/tests/phpunit/includes/db/ORMRowTest.php b/tests/phpunit/includes/db/ORMRowTest.php index 9dcaf2b3..27d4d0e8 100644 --- a/tests/phpunit/includes/db/ORMRowTest.php +++ b/tests/phpunit/includes/db/ORMRowTest.php @@ -43,19 +43,19 @@ abstract class ORMRowTest extends \MediaWikiTestCase { * @since 1.20 * @return string */ - protected abstract function getRowClass(); + abstract protected function getRowClass(); /** * @since 1.20 * @return IORMTable */ - protected abstract function getTableInstance(); + abstract protected function getTableInstance(); /** * @since 1.20 * @return array */ - public abstract function constructorTestProvider(); + abstract public function constructorTestProvider(); /** * @since 1.20 @@ -76,6 +76,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase { */ protected function getRowInstance( array $data, $loadDefaults ) { $class = $this->getRowClass(); + return new $class( $this->getTableInstance(), $data, $loadDefaults ); } @@ -136,7 +137,7 @@ abstract class ORMRowTest extends \MediaWikiTestCase { /** * @dataProvider constructorTestProvider */ - public function testSave( array $data, $loadDefaults ) { + public function testSaveAndRemove( array $data, $loadDefaults ) { $item = $this->getRowInstance( $data, $loadDefaults ); $this->assertTrue( $item->save() ); @@ -151,15 +152,6 @@ abstract class ORMRowTest extends \MediaWikiTestCase { $this->assertEquals( $id, $item->getId() ); $this->verifyFields( $item, $data ); - } - - /** - * @dataProvider constructorTestProvider - */ - public function testRemove( array $data, $loadDefaults ) { - $item = $this->getRowInstance( $data, $loadDefaults ); - - $this->assertTrue( $item->save() ); $this->assertTrue( $item->remove() ); @@ -231,4 +223,4 @@ abstract class ORMRowTest extends \MediaWikiTestCase { // TODO: test all of the methods! -}
\ No newline at end of file +} diff --git a/tests/phpunit/includes/db/ORMTableTest.php b/tests/phpunit/includes/db/ORMTableTest.php new file mode 100644 index 00000000..e583d1bc --- /dev/null +++ b/tests/phpunit/includes/db/ORMTableTest.php @@ -0,0 +1,146 @@ +<?php +/** + * Abstract class to construct tests for ORMTable deriving classes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Test + * + * @group ORM + * @group Database + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + * @author Daniel Kinzler + */ +class ORMTableTest extends MediaWikiTestCase { + + /** + * @since 1.21 + * @return string + */ + protected function getTableClass() { + return 'PageORMTableForTesting'; + } + + /** + * @since 1.21 + * @return IORMTable + */ + public function getTable() { + $class = $this->getTableClass(); + + return $class::singleton(); + } + + /** + * @since 1.21 + * @return string + */ + public function getRowClass() { + return $this->getTable()->getRowClass(); + } + + /** + * @since 1.21 + */ + public function testSingleton() { + $class = $this->getTableClass(); + + $this->assertInstanceOf( $class, $class::singleton() ); + $this->assertTrue( $class::singleton() === $class::singleton() ); + } + + /** + * @since 1.21 + */ + public function testIgnoreErrorsOverride() { + $table = $this->getTable(); + + $db = $table->getReadDbConnection(); + $db->ignoreErrors( true ); + + try { + $table->rawSelect( "this is invalid" ); + $this->fail( "An invalid query should trigger a DBQueryError even if ignoreErrors is enabled." ); + } catch ( DBQueryError $ex ) { + $this->assertTrue( true, "just making phpunit happy" ); + } + + $db->ignoreErrors( false ); + } +} + +/** + * Dummy ORM table for testing, reading Title objects from the page table. + * + * @since 1.21 + */ + +class PageORMTableForTesting extends ORMTable { + + /** + * @see ORMTable::getName + * + * @return string + */ + public function getName() { + return 'page'; + } + + /** + * @see ORMTable::getRowClass + * + * @return string + */ + public function getRowClass() { + return 'Title'; + } + + /** + * @see ORMTable::newRow + * + * @return IORMRow + */ + public function newRow( array $data, $loadDefaults = false ) { + return Title::makeTitle( $data['namespace'], $data['title'] ); + } + + /** + * @see ORMTable::getFields + * + * @return array + */ + public function getFields() { + return array( + 'id' => 'int', + 'namespace' => 'int', + 'title' => 'str', + ); + } + + /** + * @see ORMTable::getFieldPrefix + * + * @return string + */ + protected function getFieldPrefix() { + return 'page_'; + } +} diff --git a/tests/phpunit/includes/db/TestORMRowTest.php b/tests/phpunit/includes/db/TestORMRowTest.php index afd1cb80..f65642b8 100644 --- a/tests/phpunit/includes/db/TestORMRowTest.php +++ b/tests/phpunit/includes/db/TestORMRowTest.php @@ -58,35 +58,62 @@ class TestORMRowTest extends ORMRowTest { return TestORMTable::singleton(); } - public function setUp() { + protected function setUp() { parent::setUp(); $dbw = wfGetDB( DB_MASTER ); $isSqlite = $GLOBALS['wgDBtype'] === 'sqlite'; + $isPostgres = $GLOBALS['wgDBtype'] === 'postgres'; $idField = $isSqlite ? 'INTEGER' : 'INT unsigned'; $primaryKey = $isSqlite ? 'PRIMARY KEY AUTOINCREMENT' : 'auto_increment PRIMARY KEY'; - $dbw->query( - 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( - test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', - test_name VARCHAR(255) NOT NULL, - test_age TINYINT unsigned NOT NULL, - test_height FLOAT NOT NULL, - test_awesome TINYINT unsigned NOT NULL, - test_stuff BLOB NOT NULL, - test_moarstuff BLOB NOT NULL, - test_time varbinary(14) NOT NULL - );' - ); + if ( $isPostgres ) { + $dbw->query( + 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . "( + test_id serial PRIMARY KEY, + test_name TEXT NOT NULL DEFAULT '', + test_age INTEGER NOT NULL DEFAULT 0, + test_height REAL NOT NULL DEFAULT 0, + test_awesome INTEGER NOT NULL DEFAULT 0, + test_stuff BYTEA, + test_moarstuff BYTEA, + test_time TIMESTAMPTZ + );", + __METHOD__ + ); + } else { + $dbw->query( + 'CREATE TABLE IF NOT EXISTS ' . $dbw->tableName( 'orm_test' ) . '( + test_id ' . $idField . ' NOT NULL ' . $primaryKey . ', + test_name VARCHAR(255) NOT NULL, + test_age TINYINT unsigned NOT NULL, + test_height FLOAT NOT NULL, + test_awesome TINYINT unsigned NOT NULL, + test_stuff BLOB NOT NULL, + test_moarstuff BLOB NOT NULL, + test_time varbinary(14) NOT NULL + );', + __METHOD__ + ); + } + } + + protected function tearDown() { + $dbw = wfGetDB( DB_MASTER ); + $dbw->dropTable( 'orm_test', __METHOD__ ); + + parent::tearDown(); } public function constructorTestProvider() { + $dbw = wfGetDB( DB_MASTER ); return array( array( array( 'name' => 'Foobar', + 'time' => $dbw->timestamp( '20120101020202' ), 'age' => 42, 'height' => 9000.1, 'awesome' => true, @@ -98,9 +125,25 @@ class TestORMRowTest extends ORMRowTest { ); } + /** + * @since 1.21 + * @return array + */ + protected function getMockValues() { + return array( + 'id' => 1, + 'str' => 'foobar4645645', + 'int' => 42, + 'float' => 4.2, + 'bool' => '', + 'array' => array( 42, 'foobar' ), + 'blob' => new stdClass() + ); + } } -class TestORMRow extends ORMRow {} +class TestORMRow extends ORMRow { +} class TestORMTable extends ORMTable { @@ -155,7 +198,7 @@ class TestORMTable extends ORMTable { 'awesome' => 'bool', 'stuff' => 'array', 'moarstuff' => 'blob', - 'time' => 'int', // TS_MW + 'time' => 'str', // TS_MW ); } @@ -169,6 +212,4 @@ class TestORMTable extends ORMTable { protected function getFieldPrefix() { return 'test_'; } - - } diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php index 246b2918..6926b1c8 100644 --- a/tests/phpunit/includes/debug/MWDebugTest.php +++ b/tests/phpunit/includes/debug/MWDebugTest.php @@ -3,10 +3,11 @@ class MWDebugTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); // Make sure MWDebug class is enabled static $MWDebugEnabled = false; - if( !$MWDebugEnabled ) { + if ( !$MWDebugEnabled ) { MWDebug::init(); $MWDebugEnabled = true; } @@ -15,33 +16,36 @@ class MWDebugTest extends MediaWikiTestCase { wfSuppressWarnings(); } - function tearDown() { + protected function tearDown() { wfRestoreWarnings(); + parent::tearDown(); } - function testAddLog() { + public function testAddLog() { MWDebug::log( 'logging a string' ); - $this->assertEquals( array( array( - 'msg' => 'logging a string', - 'type' => 'log', - 'caller' => __METHOD__ , + $this->assertEquals( + array( array( + 'msg' => 'logging a string', + 'type' => 'log', + 'caller' => __METHOD__, ) ), MWDebug::getLog() ); } - function testAddWarning() { + public function testAddWarning() { MWDebug::warning( 'Warning message' ); - $this->assertEquals( array( array( - 'msg' => 'Warning message', - 'type' => 'warn', - 'caller' => 'MWDebugTest::testAddWarning', + $this->assertEquals( + array( array( + 'msg' => 'Warning message', + 'type' => 'warn', + 'caller' => 'MWDebugTest::testAddWarning', ) ), MWDebug::getLog() ); } - function testAvoidDuplicateDeprecations() { + public function testAvoidDuplicateDeprecations() { MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); @@ -52,7 +56,7 @@ class MWDebugTest extends MediaWikiTestCase { ); } - function testAvoidNonConsecutivesDuplicateDeprecations() { + public function testAvoidNonConsecutivesDuplicateDeprecations() { MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' ); MWDebug::warning( 'some warning' ); MWDebug::log( 'we could have logged something too' ); diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filebackend/FileBackendTest.php index a2dc5c6c..fcfa724f 100644 --- a/tests/phpunit/includes/filerepo/FileBackendTest.php +++ b/tests/phpunit/includes/filebackend/FileBackendTest.php @@ -6,14 +6,21 @@ * @group medium */ class FileBackendTest extends MediaWikiTestCase { - private $backend, $multiBackend; + + /** @var FileBackend */ + private $backend; + /** @var FileBackendMultiWrite */ + private $multiBackend; + /** @var FSFileBackend */ + public $singleBackend; private $filesToPrune = array(); private static $backendToUse; - function setUp() { + protected function setUp() { global $wgFileBackends; parent::setUp(); - $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand(); + $uniqueId = time() . '-' . mt_rand(); + $tmpPrefix = wfTempDir() . '/filebackend-unittest-' . $uniqueId; if ( $this->getCliArg( 'use-filebackend=' ) ) { if ( self::$backendToUse ) { $this->singleBackend = self::$backendToUse; @@ -36,32 +43,34 @@ class FileBackendTest extends MediaWikiTestCase { } } else { $this->singleBackend = new FSFileBackend( array( - 'name' => 'localtesting', + 'name' => 'localtesting', 'lockManager' => 'fsLockManager', #'parallelize' => 'implicit', + 'wikiId' => wfWikiID() . $uniqueId, 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" ) ) ); } $this->multiBackend = new FileBackendMultiWrite( array( - 'name' => 'localtesting', + 'name' => 'localtesting', 'lockManager' => 'fsLockManager', 'parallelize' => 'implicit', - 'backends' => array( + 'wikiId' => wfWikiId() . $uniqueId, + 'backends' => array( array( - 'name' => 'localmutlitesting1', - 'class' => 'FSFileBackend', - 'lockManager' => 'nullLockManager', + 'name' => 'localmultitesting1', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti1-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti1-cont2" ), 'isMultiMaster' => false ), array( - 'name' => 'localmutlitesting2', - 'class' => 'FSFileBackend', - 'lockManager' => 'nullLockManager', + 'name' => 'localmultitesting2', + 'class' => 'FSFileBackend', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1", 'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ), @@ -72,7 +81,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->filesToPrune = array(); } - private function baseStorePath() { + private static function baseStorePath() { return 'mwstore://localtesting'; } @@ -82,13 +91,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testIsStoragePath + * @covers FileBackend::isStoragePath */ public function testIsStoragePath( $path, $isStorePath ) { $this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ), "FileBackend::isStoragePath on path '$path'" ); } - function provider_testIsStoragePath() { + public static function provider_testIsStoragePath() { return array( array( 'mwstore://', true ), array( 'mwstore://backend', true ), @@ -106,13 +116,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testSplitStoragePath + * @covers FileBackend::splitStoragePath */ public function testSplitStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::splitStoragePath( $path ), "FileBackend::splitStoragePath on path '$path'" ); } - function provider_testSplitStoragePath() { + public static function provider_testSplitStoragePath() { return array( array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ), array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ), @@ -130,39 +141,41 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_normalizeStoragePath + * @covers FileBackend::normalizeStoragePath */ public function testNormalizeStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ), "FileBackend::normalizeStoragePath on path '$path'" ); } - function provider_normalizeStoragePath() { + public static function provider_normalizeStoragePath() { return array( array( 'mwstore://backend/container', 'mwstore://backend/container' ), array( 'mwstore://backend/container/', 'mwstore://backend/container' ), array( 'mwstore://backend/container/path', 'mwstore://backend/container/path' ), array( 'mwstore://backend/container//path', 'mwstore://backend/container/path' ), array( 'mwstore://backend/container///path', 'mwstore://backend/container/path' ), - array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj', + array( 'mwstore://backend/container///path//to///obj', 'mwstore://backend/container/path/to/obj' ), array( 'mwstore://', null ), array( 'mwstore://backend', null ), array( 'mwstore://backend//container/path', null ), array( 'mwstore://backend//container//path', null ), array( 'mwstore:///', null ), array( 'mwstore:/', null ), - array( 'mwstore:', null ), ) + array( 'mwstore:', null ), ); } /** * @dataProvider provider_testParentStoragePath + * @covers FileBackend::parentStoragePath */ public function testParentStoragePath( $path, $res ) { $this->assertEquals( $res, FileBackend::parentStoragePath( $path ), "FileBackend::parentStoragePath on path '$path'" ); } - function provider_testParentStoragePath() { + public static function provider_testParentStoragePath() { return array( array( 'mwstore://backend/container/path/to/obj', 'mwstore://backend/container/path/to' ), array( 'mwstore://backend/container/path/to', 'mwstore://backend/container/path' ), @@ -177,13 +190,14 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testExtensionFromPath + * @covers FileBackend::extensionFromPath */ public function testExtensionFromPath( $path, $res ) { $this->assertEquals( $res, FileBackend::extensionFromPath( $path ), "FileBackend::extensionFromPath on path '$path'" ); } - function provider_testExtensionFromPath() { + public static function provider_testExtensionFromPath() { return array( array( 'mwstore://backend/container/path.txt', 'txt' ), array( 'mwstore://backend/container/path.svg.png', 'png' ), @@ -210,6 +224,9 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } + /** + * @covers FileBackend::doOperation + */ private function doTestStore( $op ) { $backendName = $this->backendClass(); @@ -248,11 +265,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $dest ) ); } - public function provider_testStore() { + public static function provider_testStore() { $cases = array(); $tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath(); - $toPath = $this->baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; + $toPath = self::baseStorePath() . '/unittest-cont1/e/fun/obj1.txt'; $op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath ); $cases[] = array( $op, // operation @@ -281,6 +298,7 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testCopy + * @covers FileBackend::doOperation */ public function testCopy( $op ) { $this->backend = $this->singleBackend; @@ -302,6 +320,20 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $source ) ) ); $this->prepare( array( 'dir' => dirname( $dest ) ) ); + if ( isset( $op['ignoreMissingSource'] ) ) { + $status = $this->backend->doOperation( $op ); + $this->assertGoodStatus( $status, + "Move from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not exist ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest does not exist ($backendName)." ); + + return; // done + } + $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); $this->assertGoodStatus( $status, @@ -337,11 +369,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source, $dest ) ); } - public function provider_testCopy() { + public static function provider_testCopy() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -366,11 +398,28 @@ class FileBackendTest extends MediaWikiTestCase { $dest, // dest ); + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source + $dest, // dest + ); + return $cases; } /** * @dataProvider provider_testMove + * @covers FileBackend::doOperation */ public function testMove( $op ) { $this->backend = $this->singleBackend; @@ -392,6 +441,20 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $source ) ) ); $this->prepare( array( 'dir' => dirname( $dest ) ) ); + if ( isset( $op['ignoreMissingSource'] ) ) { + $status = $this->backend->doOperation( $op ); + $this->assertGoodStatus( $status, + "Move from $source to $dest succeeded without warnings ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Move from $source to $dest has proper 'success' field in Status ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ), + "Source file $source does not exist ($backendName)." ); + $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $dest ) ), + "Destination file $dest does not exist ($backendName)." ); + + return; // done + } + $status = $this->backend->doOperation( array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); $this->assertGoodStatus( $status, @@ -428,11 +491,11 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source, $dest ) ); } - public function provider_testMove() { + public static function provider_testMove() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/file.txt'; - $dest = $this->baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/file.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/fileMoved.txt'; $op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest ); $cases[] = array( @@ -457,11 +520,28 @@ class FileBackendTest extends MediaWikiTestCase { $dest, // dest ); + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + $source, // source + $dest, // dest + ); + + $op2 = $op; + $op2['ignoreMissingSource'] = true; + $cases[] = array( + $op2, // operation + self::baseStorePath() . '/unittest-cont-bad/e/file.txt', // source + $dest, // dest + ); + return $cases; } /** * @dataProvider provider_testDelete + * @covers FileBackend::doOperation */ public function testDelete( $op, $withSource, $okStatus ) { $this->backend = $this->singleBackend; @@ -515,10 +595,10 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertBackendPathsConsistent( array( $source ) ); } - public function provider_testDelete() { + public static function provider_testDelete() { $cases = array(); - $source = $this->baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; + $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; $op = array( 'op' => 'delete', 'src' => $source ); $cases[] = array( @@ -540,11 +620,88 @@ class FileBackendTest extends MediaWikiTestCase { true // succeeds ); + $op['ignoreMissingSource'] = true; + $op['src'] = self::baseStorePath() . '/unittest-cont-bad/e/file.txt'; + $cases[] = array( + $op, // operation + false, // without source + true // succeeds + ); + + return $cases; + } + + /** + * @dataProvider provider_testDescribe + * @covers FileBackend::doOperation + */ + public function testDescribe( $op, $withSource, $okStatus ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestDescribe( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestDescribe( $op, $withSource, $okStatus ); + $this->tearDownFiles(); + } + + private function doTestDescribe( $op, $withSource, $okStatus ) { + $backendName = $this->backendClass(); + + $source = $op['src']; + $this->prepare( array( 'dir' => dirname( $source ) ) ); + + if ( $withSource ) { + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) ); + $this->assertGoodStatus( $status, + "Creation of file at $source succeeded ($backendName)." ); + } + + $status = $this->backend->doOperation( $op ); + if ( $okStatus ) { + $this->assertGoodStatus( $status, + "Describe of file at $source succeeded without warnings ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Describe of file at $source succeeded ($backendName)." ); + $this->assertEquals( array( 0 => true ), $status->success, + "Describe of file at $source has proper 'success' field in Status ($backendName)." ); + } else { + $this->assertEquals( false, $status->isOK(), + "Describe of file at $source failed ($backendName)." ); + } + + $this->assertBackendPathsConsistent( array( $source ) ); + } + + public static function provider_testDescribe() { + $cases = array(); + + $source = self::baseStorePath() . '/unittest-cont1/e/myfacefile.txt'; + + $op = array( 'op' => 'describe', 'src' => $source, + 'headers' => array( 'X-Content-Length' => '91.3', 'Content-Old-Header' => '' ), + 'disposition' => 'inline' ); + $cases[] = array( + $op, // operation + true, // with source + true // succeeds + ); + + $cases[] = array( + $op, // operation + false, // without source + false // fails + ); + return $cases; } /** * @dataProvider provider_testCreate + * @covers FileBackend::doOperation */ public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) { $this->backend = $this->singleBackend; @@ -611,10 +768,10 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testCreate */ - public function provider_testCreate() { + public static function provider_testCreate() { $cases = array(); - $dest = $this->baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; + $dest = self::baseStorePath() . '/unittest-cont2/a/myspacefile.txt'; $op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest ); $cases[] = array( @@ -665,6 +822,9 @@ class FileBackendTest extends MediaWikiTestCase { return $cases; } + /** + * @covers FileBackend::doQuickOperations + */ public function testDoQuickOperations() { $this->backend = $this->singleBackend; $this->doTestDoQuickOperations(); @@ -678,38 +838,72 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestDoQuickOperations() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $files = array( "$base/unittest-cont1/e/fileA.a", "$base/unittest-cont1/e/fileB.a", "$base/unittest-cont1/e/fileC.a" ); - $ops = array(); + $createOps = array(); $purgeOps = array(); foreach ( $files as $path ) { $status = $this->prepare( array( 'dir' => dirname( $path ) ) ); $this->assertGoodStatus( $status, "Preparing $path succeeded without warnings ($backendName)." ); - $ops[] = array( 'op' => 'create', 'dst' => $path, 'content' => mt_rand(0,50000) ); + $createOps[] = array( 'op' => 'create', 'dst' => $path, 'content' => mt_rand( 0, 50000 ) ); + $copyOps[] = array( 'op' => 'copy', 'src' => $path, 'dst' => "$path-2" ); + $moveOps[] = array( 'op' => 'move', 'src' => "$path-2", 'dst' => "$path-3" ); $purgeOps[] = array( 'op' => 'delete', 'src' => $path ); + $purgeOps[] = array( 'op' => 'delete', 'src' => "$path-3" ); } $purgeOps[] = array( 'op' => 'null' ); - $status = $this->backend->doQuickOperations( $ops ); - $this->assertGoodStatus( $status, - "Creation of source files succeeded ($backendName)." ); + $this->assertGoodStatus( + $this->backend->doQuickOperations( $createOps ), + "Creation of source files succeeded ($backendName)." ); foreach ( $files as $file ) { $this->assertTrue( $this->backend->fileExists( array( 'src' => $file ) ), "File $file exists." ); } - $status = $this->backend->doQuickOperations( $purgeOps ); - $this->assertGoodStatus( $status, - "Quick deletion of source files succeeded ($backendName)." ); + $this->assertGoodStatus( + $this->backend->doQuickOperations( $copyOps ), + "Quick copy of source files succeeded ($backendName)." ); + foreach ( $files as $file ) { + $this->assertTrue( $this->backend->fileExists( array( 'src' => "$file-2" ) ), + "File $file-2 exists." ); + } + $this->assertGoodStatus( + $this->backend->doQuickOperations( $moveOps ), + "Quick move of source files succeeded ($backendName)." ); + foreach ( $files as $file ) { + $this->assertTrue( $this->backend->fileExists( array( 'src' => "$file-3" ) ), + "File $file-3 move in." ); + $this->assertFalse( $this->backend->fileExists( array( 'src' => "$file-2" ) ), + "File $file-2 moved away." ); + } + + $this->assertGoodStatus( + $this->backend->quickCopy( array( 'src' => $files[0], 'dst' => $files[0] ) ), + "Copy of file {$files[0]} over itself succeeded ($backendName)." ); + $this->assertTrue( $this->backend->fileExists( array( 'src' => $files[0] ) ), + "File {$files[0]} still exists." ); + + $this->assertGoodStatus( + $this->backend->quickMove( array( 'src' => $files[0], 'dst' => $files[0] ) ), + "Move of file {$files[0]} over itself succeeded ($backendName)." ); + $this->assertTrue( $this->backend->fileExists( array( 'src' => $files[0] ) ), + "File {$files[0]} still exists." ); + + $this->assertGoodStatus( + $this->backend->doQuickOperations( $purgeOps ), + "Quick deletion of source files succeeded ($backendName)." ); foreach ( $files as $file ) { $this->assertFalse( $this->backend->fileExists( array( 'src' => $file ) ), "File $file purged." ); + $this->assertFalse( $this->backend->fileExists( array( 'src' => "$file-3" ) ), + "File $file-3 purged." ); } } @@ -722,6 +916,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->backend = $this->singleBackend; $this->tearDownFiles(); $this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ); + $this->filesToPrune[] = $op['dst']; # avoid file leaking $this->tearDownFiles(); $this->backend = $this->multiBackend; @@ -740,8 +935,8 @@ class FileBackendTest extends MediaWikiTestCase { foreach ( $srcs as $i => $source ) { $this->prepare( array( 'dir' => dirname( $source ) ) ); $ops[] = array( - 'op' => 'create', // operation - 'dst' => $source, // source + 'op' => 'create', // operation + 'dst' => $source, // source 'content' => $srcsContent[$i] ); $expContent .= $srcsContent[$i]; @@ -794,22 +989,22 @@ class FileBackendTest extends MediaWikiTestCase { } } - function provider_testConcatenate() { + public static function provider_testConcatenate() { $cases = array(); $rand = mt_rand( 0, 2000000000 ) . time(); $dest = wfTempDir() . "/randomfile!$rand.txt"; $srcs = array( - $this->baseStorePath() . '/unittest-cont1/e/file1.txt', - $this->baseStorePath() . '/unittest-cont1/e/file2.txt', - $this->baseStorePath() . '/unittest-cont1/e/file3.txt', - $this->baseStorePath() . '/unittest-cont1/e/file4.txt', - $this->baseStorePath() . '/unittest-cont1/e/file5.txt', - $this->baseStorePath() . '/unittest-cont1/e/file6.txt', - $this->baseStorePath() . '/unittest-cont1/e/file7.txt', - $this->baseStorePath() . '/unittest-cont1/e/file8.txt', - $this->baseStorePath() . '/unittest-cont1/e/file9.txt', - $this->baseStorePath() . '/unittest-cont1/e/file10.txt' + self::baseStorePath() . '/unittest-cont1/e/file1.txt', + self::baseStorePath() . '/unittest-cont1/e/file2.txt', + self::baseStorePath() . '/unittest-cont1/e/file3.txt', + self::baseStorePath() . '/unittest-cont1/e/file4.txt', + self::baseStorePath() . '/unittest-cont1/e/file5.txt', + self::baseStorePath() . '/unittest-cont1/e/file6.txt', + self::baseStorePath() . '/unittest-cont1/e/file7.txt', + self::baseStorePath() . '/unittest-cont1/e/file8.txt', + self::baseStorePath() . '/unittest-cont1/e/file9.txt', + self::baseStorePath() . '/unittest-cont1/e/file10.txt' ); $content = array( 'egfage', @@ -846,6 +1041,7 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testGetFileStat + * @covers FileBackend::getFileStat */ public function testGetFileStat( $path, $content, $alreadyExists ) { $this->backend = $this->singleBackend; @@ -908,10 +1104,10 @@ class FileBackendTest extends MediaWikiTestCase { } } - function provider_testGetFileStat() { + public static function provider_testGetFileStat() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents", true ); $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "", true ); $cases[] = array( "$base/unittest-cont1/e/b/some-diff_file.txt", null, false ); @@ -921,6 +1117,8 @@ class FileBackendTest extends MediaWikiTestCase { /** * @dataProvider provider_testGetFileContents + * @covers FileBackend::getFileContents + * @covers FileBackend::getFileContentsMulti */ public function testGetFileContents( $source, $content ) { $this->backend = $this->singleBackend; @@ -937,35 +1135,50 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetFileContents( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); - - $status = $this->backend->doOperation( - array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertGoodStatus( $status, - "Creation of file at $source succeeded ($backendName)." ); - $this->assertEquals( true, $status->isOK(), - "Creation of file at $source succeeded with OK status ($backendName)." ); - - $newContents = $this->backend->getFileContents( array( 'src' => $source, 'latest' => 1 ) ); - $this->assertNotEquals( false, $newContents, - "Read of file at $source succeeded ($backendName)." ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } - $this->assertEquals( $content, $newContents, - "Contents read match data at $source ($backendName)." ); + if ( is_array( $source ) ) { + $contents = $this->backend->getFileContentsMulti( array( 'srcs' => $source ) ); + foreach ( $contents as $path => $data ) { + $this->assertNotEquals( false, $data, "Contents of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $data, "Contents of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $contents ), "Contents in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $contents ), "Contents array size correct ($backendName)." ); + } else { + $data = $this->backend->getFileContents( array( 'src' => $source ) ); + $this->assertNotEquals( false, $data, "Contents of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $data, "Contents of $source is correct ($backendName)." ); + } } - function provider_testGetFileContents() { + public static function provider_testGetFileContents() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/b/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/b/some-other_file.txt", "more file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); return $cases; } /** * @dataProvider provider_testGetLocalCopy + * @covers FileBackend::getLocalCopy */ public function testGetLocalCopy( $source, $content ) { $this->backend = $this->singleBackend; @@ -982,33 +1195,60 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetLocalCopy( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); - - $status = $this->backend->doOperation( - array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); - $this->assertGoodStatus( $status, - "Creation of file at $source succeeded ($backendName)." ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } - $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); - $this->assertNotNull( $tmpFile, - "Creation of local copy of $source succeeded ($backendName)." ); + if ( is_array( $source ) ) { + $tmpFiles = $this->backend->getLocalCopyMulti( array( 'srcs' => $source ) ); + foreach ( $tmpFiles as $path => $tmpFile ) { + $this->assertNotNull( $tmpFile, + "Creation of local copy of $path succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $contents, "Local copy of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $tmpFiles ), "Local copies in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $tmpFiles ), "Local copies array size correct ($backendName)." ); + } else { + $tmpFile = $this->backend->getLocalCopy( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $contents, "Local copy of $source is correct ($backendName)." ); + } - $contents = file_get_contents( $tmpFile->getPath() ); - $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + $obj = new stdClass(); + $tmpFile->bind( $obj ); } - function provider_testGetLocalCopy() { + public static function provider_testGetLocalCopy() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); return $cases; } /** * @dataProvider provider_testGetLocalReference + * @covers FileBackend::getLocalReference */ public function testGetLocalReference( $source, $content ) { $this->backend = $this->singleBackend; @@ -1025,32 +1265,133 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetLocalReference( $source, $content ) { $backendName = $this->backendClass(); - $this->prepare( array( 'dir' => dirname( $source ) ) ); + $srcs = (array)$source; + $content = (array)$content; + foreach ( $srcs as $i => $src ) { + $this->prepare( array( 'dir' => dirname( $src ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content[$i], 'dst' => $src ) ); + $this->assertGoodStatus( $status, + "Creation of file at $src succeeded ($backendName)." ); + } + + if ( is_array( $source ) ) { + $tmpFiles = $this->backend->getLocalReferenceMulti( array( 'srcs' => $source ) ); + foreach ( $tmpFiles as $path => $tmpFile ) { + $this->assertNotNull( $tmpFile, + "Creation of local copy of $path succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local ref of $path exists ($backendName)." ); + $this->assertEquals( current( $content ), $contents, "Local ref of $path is correct ($backendName)." ); + next( $content ); + } + $this->assertEquals( $source, array_keys( $tmpFiles ), "Local refs in right order ($backendName)." ); + $this->assertEquals( count( $source ), count( $tmpFiles ), "Local refs array size correct ($backendName)." ); + } else { + $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); + $this->assertNotNull( $tmpFile, + "Creation of local copy of $source succeeded ($backendName)." ); + $contents = file_get_contents( $tmpFile->getPath() ); + $this->assertNotEquals( false, $contents, "Local ref of $source exists ($backendName)." ); + $this->assertEquals( $content[0], $contents, "Local ref of $source is correct ($backendName)." ); + } + } + + public static function provider_testGetLocalReference() { + $cases = array(); + + $base = self::baseStorePath(); + $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); + $cases[] = array( + array( "$base/unittest-cont1/e/a/x.txt", "$base/unittest-cont1/e/a/y.txt", + "$base/unittest-cont1/e/a/z.txt" ), + array( "contents xx", "contents xy", "contents xz" ) + ); + + return $cases; + } + + /** + * @covers FileBackend::getLocalCopy + * @covers FileBackend::getLocalReference + */ + public function testGetLocalCopyAndReference404() { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopyAndReference404(); + $this->tearDownFiles(); - $status = $this->create( array( 'content' => $content, 'dst' => $source ) ); + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetLocalCopyAndReference404(); + $this->tearDownFiles(); + } + + public function doTestGetLocalCopyAndReference404() { + $backendName = $this->backendClass(); + + $base = self::baseStorePath(); + + $tmpFile = $this->backend->getLocalCopy( array( + 'src' => "$base/unittest-cont1/not-there" ) ); + $this->assertEquals( null, $tmpFile, "Local copy of not existing file is null ($backendName)." ); + + $tmpFile = $this->backend->getLocalReference( array( + 'src' => "$base/unittest-cont1/not-there" ) ); + $this->assertEquals( null, $tmpFile, "Local ref of not existing file is null ($backendName)." ); + } + + /** + * @dataProvider provider_testGetFileHttpUrl + * @covers FileBackend::getFileHttpUrl + */ + public function testGetFileHttpUrl( $source, $content ) { + $this->backend = $this->singleBackend; + $this->tearDownFiles(); + $this->doTestGetFileHttpUrl( $source, $content ); + $this->tearDownFiles(); + + $this->backend = $this->multiBackend; + $this->tearDownFiles(); + $this->doTestGetFileHttpUrl( $source, $content ); + $this->tearDownFiles(); + } + + private function doTestGetFileHttpUrl( $source, $content ) { + $backendName = $this->backendClass(); + + $this->prepare( array( 'dir' => dirname( $source ) ) ); + $status = $this->backend->doOperation( + array( 'op' => 'create', 'content' => $content, 'dst' => $source ) ); $this->assertGoodStatus( $status, "Creation of file at $source succeeded ($backendName)." ); - $tmpFile = $this->backend->getLocalReference( array( 'src' => $source ) ); - $this->assertNotNull( $tmpFile, - "Creation of local copy of $source succeeded ($backendName)." ); + $url = $this->backend->getFileHttpUrl( array( 'src' => $source ) ); - $contents = file_get_contents( $tmpFile->getPath() ); - $this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." ); + if ( $url !== null ) { // supported + $data = Http::request( "GET", $url ); + $this->assertEquals( $content, $data, + "HTTP GET of URL has right contents ($backendName)." ); + } } - function provider_testGetLocalReference() { + public static function provider_testGetFileHttpUrl() { $cases = array(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $cases[] = array( "$base/unittest-cont1/e/a/z/some_file.txt", "some file contents" ); $cases[] = array( "$base/unittest-cont1/e/a/some-other_file.txt", "more file contents" ); + $cases[] = array( "$base/unittest-cont1/e/a/\$odd&.txt", "test file contents" ); return $cases; } /** * @dataProvider provider_testPrepareAndClean + * @covers FileBackend::prepare + * @covers FileBackend::clean */ public function testPrepareAndClean( $path, $isOK ) { $this->backend = $this->singleBackend; @@ -1062,8 +1403,9 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } - function provider_testPrepareAndClean() { - $base = $this->baseStorePath(); + public static function provider_testPrepareAndClean() { + $base = self::baseStorePath(); + return array( array( "$base/unittest-cont1/e/a/z/some_file1.txt", true ), array( "$base/unittest-cont2/a/z/some_file2.txt", true ), @@ -1108,11 +1450,16 @@ class FileBackendTest extends MediaWikiTestCase { $this->tearDownFiles(); } + /** + * @covers FileBackend::clean + */ private function doTestRecursiveClean() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $dirs = array( + "$base/unittest-cont1", + "$base/unittest-cont1/e", "$base/unittest-cont1/e/a", "$base/unittest-cont1/e/a/b", "$base/unittest-cont1/e/a/b/c", @@ -1150,8 +1497,11 @@ class FileBackendTest extends MediaWikiTestCase { } } - // @TODO: testSecure + // @todo testSecure + /** + * @covers FileBackend::doOperations + */ public function testDoOperations() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1165,7 +1515,7 @@ class FileBackendTest extends MediaWikiTestCase { } private function doTestDoOperations() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileA = "$base/unittest-cont1/e/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; @@ -1184,6 +1534,8 @@ class FileBackendTest extends MediaWikiTestCase { $this->prepare( array( 'dir' => dirname( $fileD ) ) ); $status = $this->backend->doOperations( array( + array( 'op' => 'describe', 'src' => $fileA, + 'headers' => array( 'X-Content-Length' => '91.3' ), 'disposition' => 'inline' ), array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC, 'overwrite' => 1 ), // Now: A:<A>, B:<B>, C:<A>, D:<empty> (file:<orginal contents>) array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileA, 'overwriteSame' => 1 ), @@ -1214,7 +1566,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertGoodStatus( $status, "Operation batch succeeded" ); $this->assertEquals( true, $status->isOK(), "Operation batch succeeded" ); - $this->assertEquals( 13, count( $status->success ), + $this->assertEquals( 14, count( $status->success ), "Operation batch has correct success array" ); $this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ), @@ -1237,6 +1589,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileC" ); } + /** + * @covers FileBackend::doOperations + */ public function testDoOperationsPipeline() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1251,7 +1606,7 @@ class FileBackendTest extends MediaWikiTestCase { // concurrency orientated private function doTestDoOperationsPipeline() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; $fileBContents = 'g-jmq3gpqgt3qtg q3GT '; @@ -1336,6 +1691,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileC" ); } + /** + * @covers FileBackend::doOperations + */ public function testDoOperationsFailing() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1349,7 +1707,7 @@ class FileBackendTest extends MediaWikiTestCase { } private function doTestDoOperationsFailing() { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $fileA = "$base/unittest-cont2/a/b/fileA.txt"; $fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq'; @@ -1410,6 +1768,9 @@ class FileBackendTest extends MediaWikiTestCase { "Correct file SHA-1 of $fileA" ); } + /** + * @covers FileBackend::getFileList + */ public function testGetFileList() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1424,7 +1785,7 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetFileList() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); // Should have no errors $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont-notexists" ) ); @@ -1458,7 +1819,7 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( true, $status->isOK(), "Creation of files succeeded with OK status ($backendName)." ); - // Expected listing + // Expected listing at root $expected = array( "e/test1.txt", "e/test2.txt", @@ -1477,27 +1838,28 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (no trailing slash) - $list = array(); + // Actual listing (no trailing slash) at root $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + // Actual listing (no trailing slash) at root with advise + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Actual listing (with trailing slash) + // Actual listing (with trailing slash) at root $list = array(); $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/" ) ); foreach ( $iter as $file ) { $list[] = $file; } sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Expected listing + // Expected listing at subdir $expected = array( "test1.txt", "test2.txt", @@ -1509,36 +1871,39 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (no trailing slash) - $list = array(); + // Actual listing (no trailing slash) at subdir $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); + // Actual listing (no trailing slash) at subdir with advise + $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); - // Actual listing (with trailing slash) + // Actual listing (with trailing slash) at subdir $list = array(); $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir/" ) ); foreach ( $iter as $file ) { $list[] = $file; } sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName)." ); // Actual listing (using iterator second time) - $list = array(); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); - $this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." ); - // Expected listing (top files only) + // Actual listing (top files only) at root + $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1" ) ); + $list = $this->listToArray( $iter ); + sort( $list ); + $this->assertEquals( array(), $list, "Correct top file listing ($backendName)." ); + + // Expected listing (top files only) at subdir $expected = array( "test1.txt", "test2.txt", @@ -1548,14 +1913,16 @@ class FileBackendTest extends MediaWikiTestCase { ); sort( $expected ); - // Actual listing (top files only) - $list = array(); + // Actual listing (top files only) at subdir $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir" ) ); - foreach ( $iter as $file ) { - $list[] = $file; - } + $list = $this->listToArray( $iter ); sort( $list ); + $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." ); + // Actual listing (top files only) at subdir with advise + $iter = $this->backend->getTopFileList( array( 'dir' => "$base/unittest-cont1/e/subdir2/subdir", 'adviseStat' => 1 ) ); + $list = $this->listToArray( $iter ); + sort( $list ); $this->assertEquals( $expected, $list, "Correct top file listing ($backendName)." ); foreach ( $files as $file ) { // clean up @@ -1563,9 +1930,15 @@ class FileBackendTest extends MediaWikiTestCase { } $iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); - foreach ( $iter as $iter ) {} // no errors + foreach ( $iter as $iter ) { + // no errors + } } + /** + * @covers FileBackend::getTopDirectoryList + * @covers FileBackend::getDirectoryList + */ public function testGetDirectoryList() { $this->backend = $this->singleBackend; $this->tearDownFiles(); @@ -1581,7 +1954,7 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestGetDirectoryList() { $backendName = $this->backendClass(); - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $files = array( "$base/unittest-cont1/e/test1.txt", "$base/unittest-cont1/e/test2.txt", @@ -1751,14 +2124,31 @@ class FileBackendTest extends MediaWikiTestCase { $this->assertEquals( $expected, $list, "Correct dir listing ($backendName)." ); + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/e/subdir1" ) ); + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); + foreach ( $files as $file ) { // clean up $this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) ); } $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/not/exists" ) ); - foreach ( $iter as $iter ) {} // no errors + foreach ( $iter as $file ) { + // no errors + } + + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); + + $iter = $this->backend->getDirectoryList( array( 'dir' => "$base/unittest-cont1/e/not/exists" ) ); + $items = $this->listToArray( $iter ); + $this->assertEquals( array(), $items, "Directory listing is empty." ); } + /** + * @covers FileBackend::lockFiles + * @covers FileBackend::unlockFiles + */ public function testLockCalls() { $this->backend = $this->singleBackend; $this->doTestLockCalls(); @@ -1767,54 +2157,102 @@ class FileBackendTest extends MediaWikiTestCase { private function doTestLockCalls() { $backendName = $this->backendClass(); - for ( $i=0; $i<50; $i++ ) { - $paths = array( - "test1.txt", - "test2.txt", - "test3.txt", - "subdir1", - "subdir1", // duplicate - "subdir1/test1.txt", - "subdir1/test2.txt", - "subdir2", - "subdir2", // duplicate - "subdir2/test3.txt", - "subdir2/test4.txt", - "subdir2/subdir", - "subdir2/subdir/test1.txt", - "subdir2/subdir/test2.txt", - "subdir2/subdir/test3.txt", - "subdir2/subdir/test4.txt", - "subdir2/subdir/test5.txt", - "subdir2/subdir/sub", - "subdir2/subdir/sub/test0.txt", - "subdir2/subdir/sub/120-px-file.txt", - ); + $paths = array( + "test1.txt", + "test2.txt", + "test3.txt", + "subdir1", + "subdir1", // duplicate + "subdir1/test1.txt", + "subdir1/test2.txt", + "subdir2", + "subdir2", // duplicate + "subdir2/test3.txt", + "subdir2/test4.txt", + "subdir2/subdir", + "subdir2/subdir/test1.txt", + "subdir2/subdir/test2.txt", + "subdir2/subdir/test3.txt", + "subdir2/subdir/test4.txt", + "subdir2/subdir/test5.txt", + "subdir2/subdir/sub", + "subdir2/subdir/sub/test0.txt", + "subdir2/subdir/sub/120-px-file.txt", + ); + for ( $i = 0; $i < 25; $i++ ) { $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX ); - $this->assertEquals( array(), $status->errors, - "Locking of files succeeded ($backendName)." ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName). ($i)" ); $this->assertEquals( true, $status->isOK(), - "Locking of files succeeded with OK status ($backendName)." ); + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + ## Flip the acquire/release ordering around ## + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->lockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_EX ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName). ($i)" ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); + + $status = $this->backend->unlockFiles( $paths, LockManager::LOCK_SH ); + $this->assertEquals( print_r( array(), true ), print_r( $status->errors, true ), + "Locking of files succeeded ($backendName) ($i)." ); + $this->assertEquals( true, $status->isOK(), + "Locking of files succeeded with OK status ($backendName) ($i)." ); } + + $status = Status::newGood(); + $sl = $this->backend->getScopedFileLocks( $paths, LockManager::LOCK_EX, $status ); + $this->assertType( 'ScopedLock', $sl, + "Scoped locking of files succeeded ($backendName)." ); + $this->assertEquals( array(), $status->errors, + "Scoped locking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Scoped locking of files succeeded with OK status ($backendName)." ); + + ScopedLock::release( $sl ); + $this->assertEquals( null, $sl, + "Scoped unlocking of files succeeded ($backendName)." ); + $this->assertEquals( array(), $status->errors, + "Scoped unlocking of files succeeded ($backendName)." ); + $this->assertEquals( true, $status->isOK(), + "Scoped unlocking of files succeeded with OK status ($backendName)." ); + } + + // helper function + private function listToArray( $iter ) { + return is_array( $iter ) ? $iter : iterator_to_array( $iter ); } // test helper wrapper for backend prepare() function @@ -1825,14 +2263,17 @@ class FileBackendTest extends MediaWikiTestCase { // test helper wrapper for backend prepare() function private function create( array $params ) { $params['op'] = 'create'; + return $this->backend->doQuickOperations( array( $params ) ); } function tearDownFiles() { foreach ( $this->filesToPrune as $file ) { - @unlink( $file ); + if ( is_file( $file ) ) { + unlink( $file ); + } } - $containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' ); + $containers = array( 'unittest-cont1', 'unittest-cont2' ); foreach ( $containers as $container ) { $this->deleteFiles( $container ); } @@ -1840,13 +2281,14 @@ class FileBackendTest extends MediaWikiTestCase { } private function deleteFiles( $container ) { - $base = $this->baseStorePath(); + $base = self::baseStorePath(); $iter = $this->backend->getFileList( array( 'dir' => "$base/$container" ) ); if ( $iter ) { foreach ( $iter as $file ) { - $this->backend->delete( array( 'src' => "$base/$container/$file" ), - array( 'force' => 1, 'nonLocking' => 1 ) ); + $this->backend->quickDelete( array( 'src' => "$base/$container/$file" ) ); } + // free the directory, to avoid Permission denied under windows on rmdir + unset( $iter ); } $this->backend->clean( array( 'dir' => "$base/$container", 'recursive' => 1 ) ); } @@ -1861,8 +2303,4 @@ class FileBackendTest extends MediaWikiTestCase { function assertGoodStatus( $status, $msg ) { $this->assertEquals( print_r( array(), 1 ), print_r( $status->errors, 1 ), $msg ); } - - function tearDown() { - parent::tearDown(); - } } diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php index 8f92c123..e3a75567 100644 --- a/tests/phpunit/includes/filerepo/FileRepoTest.php +++ b/tests/phpunit/includes/filerepo/FileRepoTest.php @@ -4,39 +4,49 @@ class FileRepoTest extends MediaWikiTestCase { /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionCanNotBeNull() { - $f = new FileRepo(); + public function testFileRepoConstructionOptionCanNotBeNull() { + new FileRepo(); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { - $f = new FileRepo( array() ); + public function testFileRepoConstructionOptionCanNotBeAnEmptyArray() { + new FileRepo( array() ); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionNeedNameKey() { - $f = new FileRepo( array( + public function testFileRepoConstructionOptionNeedNameKey() { + new FileRepo( array( 'backend' => 'foobar' ) ); } + /** * @expectedException MWException + * @covers FileRepo::__construct */ - function testFileRepoConstructionOptionNeedBackendKey() { - $f = new FileRepo( array( + public function testFileRepoConstructionOptionNeedBackendKey() { + new FileRepo( array( 'name' => 'foobar' ) ); } - function testFileRepoConstructionWithRequiredOptions() { + /** + * @covers FileRepo::__construct + */ + public function testFileRepoConstructionWithRequiredOptions() { $f = new FileRepo( array( - 'name' => 'FileRepoTestRepository', + 'name' => 'FileRepoTestRepository', 'backend' => new FSFileBackend( array( - 'name' => 'local-testing', - 'lockManager' => 'nullLockManager', + 'name' => 'local-testing', + 'lockManager' => 'nullLockManager', 'containerPaths' => array() ) ) ) ); diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php index 3ab56af8..b33c1bbb 100644 --- a/tests/phpunit/includes/filerepo/StoreBatchTest.php +++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php @@ -1,11 +1,17 @@ <?php + /** * @group FileRepo * @group medium */ class StoreBatchTest extends MediaWikiTestCase { - public function setUp() { + protected $createdFiles; + protected $date; + /** @var FileRepo */ + protected $repo; + + protected function setUp() { global $wgFileBackends; parent::setUp(); @@ -24,18 +30,18 @@ class StoreBatchTest extends MediaWikiTestCase { $backend = new $class( $useConfig ); } else { $backend = new FSFileBackend( array( - 'name' => 'local-testing', + 'name' => 'local-testing', 'lockManager' => 'nullLockManager', 'containerPaths' => array( - 'unittests-public' => "{$tmpPrefix}-public", - 'unittests-thumb' => "{$tmpPrefix}-thumb", - 'unittests-temp' => "{$tmpPrefix}-temp", + 'unittests-public' => "{$tmpPrefix}-public", + 'unittests-thumb' => "{$tmpPrefix}-thumb", + 'unittests-temp' => "{$tmpPrefix}-temp", 'unittests-deleted' => "{$tmpPrefix}-deleted", ) ) ); } $this->repo = new FileRepo( array( - 'name' => 'unittests', + 'name' => 'unittests', 'backend' => $backend ) ); @@ -43,14 +49,26 @@ class StoreBatchTest extends MediaWikiTestCase { $this->createdFiles = array(); } + protected function tearDown() { + $this->repo->cleanupBatch( $this->createdFiles ); // delete files + foreach ( $this->createdFiles as $tmp ) { // delete dirs + $tmp = $this->repo->resolveVirtualUrl( $tmp ); + while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { + $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); + } + } + parent::tearDown(); + } + /** * Store a file or virtual URL source into a media file name. * * @param $originalName string The title of the image * @param $srcPath string The filepath or virtual URL * @param $flags integer Flags to pass into repo::store(). + * @return FileRepoStatus */ - private function storeit($originalName, $srcPath, $flags) { + private function storeit( $originalName, $srcPath, $flags ) { $hashPath = $this->repo->getHashPath( $originalName ); $dstRel = "$hashPath{$this->date}!$originalName"; $dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName ); @@ -58,6 +76,7 @@ class StoreBatchTest extends MediaWikiTestCase { $result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags ); $result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel; $this->createdFiles[] = $result->value; + return $result; } @@ -67,57 +86,49 @@ class StoreBatchTest extends MediaWikiTestCase { * @param $fn string The title of the image * @param $infn string The name of the file (in the filesystem) * @param $otherfn string The name of the different file (in the filesystem) - * @param $fromrepo logical 'true' if we want to copy from a virtual URL out of the Repo. + * @param $fromrepo bool 'true' if we want to copy from a virtual URL out of the Repo. */ - private function storecohort($fn, $infn, $otherfn, $fromrepo) { + private function storecohort( $fn, $infn, $otherfn, $fromrepo ) { $f = $this->storeit( $fn, $infn, 0 ); $this->assertTrue( $f->isOK(), 'failed to store a new file' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); if ( $fromrepo ) { - $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE); + $f = $this->storeit( "Other-$fn", $infn, FileRepo::OVERWRITE ); $infn = $f->value; } // This should work because we're allowed to overwrite $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE ); $this->assertTrue( $f->isOK(), 'We should be allowed to overwrite' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); // This should fail because we're overwriting. $f = $this->storeit( $fn, $infn, 0 ); $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite' ); $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); // This should succeed because we're overwriting the same content. $f = $this->storeit( $fn, $infn, FileRepo::OVERWRITE_SAME ); $this->assertTrue( $f->isOK(), 'We should be able to overwrite the same content' ); $this->assertEquals( $f->failCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 1 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); // This should fail because we're overwriting different content. if ( $fromrepo ) { - $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE); + $f = $this->storeit( "Other-$fn", $otherfn, FileRepo::OVERWRITE ); $otherfn = $f->value; } $f = $this->storeit( $fn, $otherfn, FileRepo::OVERWRITE_SAME ); $this->assertFalse( $f->isOK(), 'We should not be allowed to overwrite different content' ); $this->assertEquals( $f->failCount, 1, "counts wrong {$f->successCount} {$f->failCount}" ); - $this->assertEquals( $f->successCount, 0 , "counts wrong {$f->successCount} {$f->failCount}" ); + $this->assertEquals( $f->successCount, 0, "counts wrong {$f->successCount} {$f->failCount}" ); } + /** + * @covers FileRepo::store + */ public function teststore() { global $IP; $this->storecohort( "Test1.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", false ); $this->storecohort( "Test2.png", "$IP/skins/monobook/wiki.png", "$IP/skins/monobook/video.png", true ); } - - public function tearDown() { - $this->repo->cleanupBatch( $this->createdFiles ); // delete files - foreach ( $this->createdFiles as $tmp ) { // delete dirs - $tmp = $this->repo->resolveVirtualUrl( $tmp ); - while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) { - $this->repo->getBackend()->clean( array( 'dir' => $tmp ) ); - } - } - parent::tearDown(); - } } diff --git a/tests/phpunit/includes/installer/InstallDocFormatterTest.php b/tests/phpunit/includes/installer/InstallDocFormatterTest.php index 56485d3e..0e5f2671 100644 --- a/tests/phpunit/includes/installer/InstallDocFormatterTest.php +++ b/tests/phpunit/includes/installer/InstallDocFormatterTest.php @@ -1,5 +1,5 @@ <?php -/* +/* * To change this template, choose Tools | Templates * and open the template in the editor. */ @@ -9,7 +9,7 @@ class InstallDocFormatterTest extends MediaWikiTestCase { * @covers InstallDocFormatter::format * @dataProvider provideDocFormattingTests */ - function testFormat( $expected, $unformattedText, $message = '' ) { + public function testFormat( $expected, $unformattedText, $message = '' ) { $this->assertEquals( $expected, InstallDocFormatter::format( $unformattedText ), @@ -20,21 +20,22 @@ class InstallDocFormatterTest extends MediaWikiTestCase { /** * Provider for testFormat() */ - function provideDocFormattingTests() { + public static function provideDocFormattingTests() { # Format: (expected string, unformattedText string, optional message) return array( # Escape some wikitext - array( 'Install <tag>' , 'Install <tag>', 'Escaping <' ), - array( 'Install {{template}}' , 'Install {{template}}', 'Escaping [[' ), - array( 'Install [[page]]' , 'Install [[page]]', 'Escaping {{' ), - array( 'Install ' , "Install \r", 'Removing \r' ), + array( 'Install <tag>', 'Install <tag>', 'Escaping <' ), + array( 'Install {{template}}', 'Install {{template}}', 'Escaping [[' ), + array( 'Install [[page]]', 'Install [[page]]', 'Escaping {{' ), + array( 'Install __TOC__', 'Install __TOC__', 'Escaping __' ), + array( 'Install ', "Install \r", 'Removing \r' ), # Transform \t{1,2} into :{1,2} array( ':One indentation', "\tOne indentation", 'Replacing a single \t' ), array( '::Two indentations', "\t\tTwo indentations", 'Replacing 2 x \t' ), # Transform 'bug 123' links - array( + array( '<span class="config-plainlink">[https://bugzilla.wikimedia.org/123 bug 123]</span>', 'bug 123', 'Testing bug 123 links' ), array( @@ -55,7 +56,7 @@ class InstallDocFormatterTest extends MediaWikiTestCase { array( '<span class="config-plainlink">[http://www.mediawiki.org/wiki/Manual:$wgFoo_Bar $wgFoo_Bar]</span>', '$wgFoo_Bar', 'Testing $wgFoo_Bar (with underscore)' ), - + # Icky variables that shouldn't link array( '$myAwesomeVariable', '$myAwesomeVariable', 'Testing $myAwesomeVariable (not starting with $wg)' ), array( '$()not!a&Var', '$()not!a&Var', 'Testing $()not!a&Var (obviously not a variable)' ), diff --git a/tests/phpunit/includes/installer/OracleInstallerTest.php b/tests/phpunit/includes/installer/OracleInstallerTest.php new file mode 100644 index 00000000..66e65592 --- /dev/null +++ b/tests/phpunit/includes/installer/OracleInstallerTest.php @@ -0,0 +1,48 @@ +<?php + +/** + * Tests for OracleInstaller + * + * @group Database + * @group Installer + */ + +class OracleInstallerTest extends MediaWikiTestCase { + + /** + * @dataProvider provideOracleConnectStrings + * @covers OracleInstaller::checkConnectStringFormat + */ + public function testCheckConnectStringFormat( $expected, $connectString, $msg = '' ) { + $validity = $expected ? 'should be valid' : 'should NOT be valid'; + $msg = "'$connectString' ($msg) $validity."; + $this->assertEquals( $expected, + OracleInstaller::checkConnectStringFormat( $connectString ), + $msg + ); + } + + /** + * Provider to test OracleInstaller::checkConnectStringFormat() + */ + function provideOracleConnectStrings() { + // expected result, connectString[, message] + return array( + array( true, 'simple_01', 'Simple TNS name' ), + array( true, 'simple_01.world', 'TNS name with domain' ), + array( true, 'simple_01.domain.net', 'TNS name with domain' ), + array( true, 'host123', 'Host only' ), + array( true, 'host123.domain.net', 'FQDN only' ), + array( true, '//host123.domain.net', 'FQDN URL only' ), + array( true, '123.223.213.132', 'Host IP only' ), + array( true, 'host:1521', 'Host and port' ), + array( true, 'host:1521/service', 'Host, port and service' ), + array( true, 'host:1521/service:shared', 'Host, port, service and shared server type' ), + array( true, 'host:1521/service:dedicated', 'Host, port, service and dedicated server type' ), + array( true, 'host:1521/service:pooled', 'Host, port, service and pooled server type' ), + array( true, 'host:1521/service:shared/instance1', 'Host, port, service, server type and instance' ), + array( true, 'host:1521//instance1', 'Host, port and instance' ), + ); + } + +} diff --git a/tests/phpunit/includes/jobqueue/JobQueueTest.php b/tests/phpunit/includes/jobqueue/JobQueueTest.php new file mode 100644 index 00000000..4e51c4fc --- /dev/null +++ b/tests/phpunit/includes/jobqueue/JobQueueTest.php @@ -0,0 +1,329 @@ +<?php + +/** + * @group JobQueue + * @group medium + * @group Database + */ +class JobQueueTest extends MediaWikiTestCase { + protected $key; + protected $queueRand, $queueRandTTL, $queueFifo, $queueFifoTTL; + + function __construct( $name = null, array $data = array(), $dataName = '' ) { + parent::__construct( $name, $data, $dataName ); + + $this->tablesUsed[] = 'job'; + } + + protected function setUp() { + global $wgJobTypeConf; + parent::setUp(); + + $this->setMwGlobals( 'wgMemc', new HashBagOStuff() ); + + if ( $this->getCliArg( 'use-jobqueue=' ) ) { + $name = $this->getCliArg( 'use-jobqueue=' ); + if ( !isset( $wgJobTypeConf[$name] ) ) { + throw new MWException( "No \$wgJobTypeConf entry for '$name'." ); + } + $baseConfig = $wgJobTypeConf[$name]; + } else { + $baseConfig = array( 'class' => 'JobQueueDB' ); + } + $baseConfig['type'] = 'null'; + $baseConfig['wiki'] = wfWikiID(); + $variants = array( + 'queueRand' => array( 'order' => 'random', 'claimTTL' => 0 ), + 'queueRandTTL' => array( 'order' => 'random', 'claimTTL' => 10 ), + 'queueTimestamp' => array( 'order' => 'timestamp', 'claimTTL' => 0 ), + 'queueTimestampTTL' => array( 'order' => 'timestamp', 'claimTTL' => 10 ), + 'queueFifo' => array( 'order' => 'fifo', 'claimTTL' => 0 ), + 'queueFifoTTL' => array( 'order' => 'fifo', 'claimTTL' => 10 ), + ); + foreach ( $variants as $q => $settings ) { + try { + $this->$q = JobQueue::factory( $settings + $baseConfig ); + if ( !( $this->$q instanceof JobQueueDB ) ) { + $this->$q->setTestingPrefix( 'unittests-' . wfRandomString( 32 ) ); + } + } catch ( MWException $e ) { + // unsupported? + // @todo What if it was another error? + }; + } + } + + protected function tearDown() { + parent::tearDown(); + foreach ( + array( + 'queueRand', 'queueRandTTL', 'queueTimestamp', 'queueTimestampTTL', + 'queueFifo', 'queueFifoTTL' + ) as $q + ) { + if ( $this->$q ) { + $this->$q->delete(); + } + $this->$q = null; + } + } + + /** + * @dataProvider provider_queueLists + */ + public function testProperties( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertEquals( wfWikiID(), $queue->getWiki(), "Proper wiki ID ($desc)" ); + $this->assertEquals( 'null', $queue->getType(), "Proper job type ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testBasicOperations( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $this->assertTrue( $queue->push( $this->newJob() ), "Push worked ($desc)" ); + $this->assertTrue( $queue->batchPush( array( $this->newJob() ) ), "Push worked ($desc)" ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 2, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + $jobs = iterator_to_array( $queue->getAllQueuedJobs() ); + $this->assertEquals( 2, count( $jobs ), "Queue iterator size is correct ($desc)" ); + + $job1 = $queue->pop(); + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $job2 = $queue->pop(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 2, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job1 ); + + $queue->flushCaches(); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job2 ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + + $this->assertTrue( $queue->batchPush( array( $this->newJob(), $this->newJob() ) ), + "Push worked ($desc)" ); + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->delete(); + $queue->flushCaches(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testBasicDeduplication( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $this->assertTrue( + $queue->batchPush( + array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ) + ), + "Push worked ($desc)" ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $this->assertTrue( + $queue->batchPush( + array( $this->newDedupedJob(), $this->newDedupedJob(), $this->newDedupedJob() ) + ), + "Push worked ($desc)" + ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 1, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $job1 = $queue->pop(); + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + if ( $recycles ) { + $this->assertEquals( 1, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } else { + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + $queue->ack( $job1 ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Active job count ($desc)" ); + } + + /** + * @dataProvider provider_queueLists + */ + public function testRootDeduplication( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + $id = wfRandomString( 32 ); + $root1 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp + for ( $i = 0; $i < 5; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( 0, $root1 ) ), "Push worked ($desc)" ); + } + $queue->deduplicateRootJob( $this->newJob( 0, $root1 ) ); + sleep( 1 ); // roo job timestamp will increase + $root2 = Job::newRootJobParams( "nulljobspam:$id" ); // task ID/timestamp + $this->assertNotEquals( $root1['rootJobTimestamp'], $root2['rootJobTimestamp'], + "Root job signatures have different timestamps." ); + for ( $i = 0; $i < 5; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( 0, $root2 ) ), "Push worked ($desc)" ); + } + $queue->deduplicateRootJob( $this->newJob( 0, $root2 ) ); + + $this->assertFalse( $queue->isEmpty(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 10, $queue->getSize(), "Queue size is correct ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + + $dupcount = 0; + $jobs = array(); + do { + $job = $queue->pop(); + if ( $job ) { + $jobs[] = $job; + $queue->ack( $job ); + } + if ( $job instanceof DuplicateJob ) { + ++$dupcount; + } + } while ( $job ); + + $this->assertEquals( 10, count( $jobs ), "Correct number of jobs popped ($desc)" ); + $this->assertEquals( 5, $dupcount, "Correct number of duplicate jobs popped ($desc)" ); + } + + /** + * @dataProvider provider_fifoQueueLists + */ + public function testJobOrder( $queue, $recycles, $desc ) { + $queue = $this->$queue; + if ( !$queue ) { + $this->markTestSkipped( $desc ); + } + + $this->assertTrue( $queue->isEmpty(), "Queue is empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "Queue is empty ($desc)" ); + + for ( $i = 0; $i < 10; ++$i ) { + $this->assertTrue( $queue->push( $this->newJob( $i ) ), "Push worked ($desc)" ); + } + + for ( $i = 0; $i < 10; ++$i ) { + $job = $queue->pop(); + $this->assertTrue( $job instanceof Job, "Jobs popped from queue ($desc)" ); + $params = $job->getParams(); + $this->assertEquals( $i, $params['i'], "Job popped from queue is FIFO ($desc)" ); + $queue->ack( $job ); + } + + $this->assertFalse( $queue->pop(), "Queue is not empty ($desc)" ); + + $queue->flushCaches(); + $this->assertEquals( 0, $queue->getSize(), "Queue is empty ($desc)" ); + $this->assertEquals( 0, $queue->getAcquiredCount(), "No jobs active ($desc)" ); + } + + public static function provider_queueLists() { + return array( + array( 'queueRand', false, 'Random queue without ack()' ), + array( 'queueRandTTL', true, 'Random queue with ack()' ), + array( 'queueTimestamp', false, 'Time ordered queue without ack()' ), + array( 'queueTimestampTTL', true, 'Time ordered queue with ack()' ), + array( 'queueFifo', false, 'FIFO ordered queue without ack()' ), + array( 'queueFifoTTL', true, 'FIFO ordered queue with ack()' ) + ); + } + + public static function provider_fifoQueueLists() { + return array( + array( 'queueFifo', false, 'Ordered queue without ack()' ), + array( 'queueFifoTTL', true, 'Ordered queue with ack()' ) + ); + } + + function newJob( $i = 0, $rootJob = array() ) { + return new NullJob( Title::newMainPage(), + array( 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 0, 'i' => $i ) + $rootJob ); + } + + function newDedupedJob( $i = 0, $rootJob = array() ) { + return new NullJob( Title::newMainPage(), + array( 'lives' => 0, 'usleep' => 0, 'removeDuplicates' => 1, 'i' => $i ) + $rootJob ); + } +} diff --git a/tests/phpunit/includes/json/FormatJsonTest.php b/tests/phpunit/includes/json/FormatJsonTest.php new file mode 100644 index 00000000..149be05b --- /dev/null +++ b/tests/phpunit/includes/json/FormatJsonTest.php @@ -0,0 +1,161 @@ +<?php + +class FormatJsonTest extends MediaWikiTestCase { + + public function testEncoderPrettyPrinting() { + $obj = array( + 'emptyObject' => new stdClass, + 'emptyArray' => array(), + 'string' => 'foobar\\', + 'filledArray' => array( + array( + 123, + 456, + ), + // Nested json works without problems + '"7":["8",{"9":"10"}]', + // Whitespace clean up doesn't touch strings that look alike + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}", + ), + ); + + // 4 space indent, no trailing whitespace, no trailing linefeed + $json = '{ + "emptyObject": {}, + "emptyArray": [], + "string": "foobar\\\\", + "filledArray": [ + [ + 123, + 456 + ], + "\"7\":[\"8\",{\"9\":\"10\"}]", + "{\n\t\"emptyObject\": {\n\t},\n\t\"emptyArray\": [ ]\n}" + ] +}'; + + $json = str_replace( "\r", '', $json ); // Windows compat + $this->assertSame( $json, FormatJson::encode( $obj, true ) ); + } + + public static function provideEncodeDefault() { + return self::getEncodeTestCases( array() ); + } + + /** + * @dataProvider provideEncodeDefault + */ + public function testEncodeDefault( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from ) ); + } + + public static function provideEncodeUtf8() { + return self::getEncodeTestCases( array( 'unicode' ) ); + } + + /** + * @dataProvider provideEncodeUtf8 + */ + public function testEncodeUtf8( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::UTF8_OK ) ); + } + + public static function provideEncodeXmlMeta() { + return self::getEncodeTestCases( array( 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeXmlMeta + */ + public function testEncodeXmlMeta( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::XMLMETA_OK ) ); + } + + public static function provideEncodeAllOk() { + return self::getEncodeTestCases( array( 'unicode', 'xmlmeta' ) ); + } + + /** + * @dataProvider provideEncodeAllOk + */ + public function testEncodeAllOk( $from, $to ) { + $this->assertSame( $to, FormatJson::encode( $from, false, FormatJson::ALL_OK ) ); + } + + public function testEncodePhpBug46944() { + $this->assertNotEquals( + '\ud840\udc00', + strtolower( FormatJson::encode( "\xf0\xa0\x80\x80" ) ), + 'Test encoding an broken json_encode character (U+20000)' + ); + } + + public function testDecodeReturnType() { + $this->assertInternalType( + 'object', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}' ), + 'Default to object' + ); + + $this->assertInternalType( + 'array', + FormatJson::decode( '{"Name": "Cheeso", "Rank": 7}', true ), + 'Optional array' + ); + } + + /** + * Generate a set of test cases for a particular combination of encoder options. + * + * @param array $unescapedGroups List of character groups to leave unescaped + * @return array: Arrays of unencoded strings and corresponding encoded strings + */ + private static function getEncodeTestCases( array $unescapedGroups ) { + $groups = array( + 'always' => array( + // Forward slash (always unescaped) + '/' => '/', + + // Control characters + "\0" => '\u0000', + "\x08" => '\b', + "\t" => '\t', + "\n" => '\n', + "\r" => '\r', + "\f" => '\f', + "\x1f" => '\u001f', // representative example + + // Double quotes + '"' => '\"', + + // Backslashes + '\\' => '\\\\', + '\\\\' => '\\\\\\\\', + '\\u00e9' => '\\\u00e9', // security check for Unicode unescaping + + // Line terminators + "\xe2\x80\xa8" => '\u2028', + "\xe2\x80\xa9" => '\u2029', + ), + 'unicode' => array( + "\xc3\xa9" => '\u00e9', + "\xf0\x9d\x92\x9e" => '\ud835\udc9e', // U+1D49E, outside the BMP + ), + 'xmlmeta' => array( + '<' => '\u003C', // JSON_HEX_TAG uses uppercase hex digits + '>' => '\u003E', + '&' => '\u0026', + ), + ); + + $cases = array(); + foreach ( $groups as $name => $rules ) { + $leaveUnescaped = in_array( $name, $unescapedGroups ); + foreach ( $rules as $from => $to ) { + $cases[] = array( $from, '"' . ( $leaveUnescaped ? $from : $to ) . '"' ); + } + } + + return $cases; + } +} diff --git a/tests/phpunit/includes/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php deleted file mode 100644 index 8f2421a2..00000000 --- a/tests/phpunit/includes/json/ServicesJsonTest.php +++ /dev/null @@ -1,93 +0,0 @@ -<?php -/* - * Test cases for our Services_Json library. Requires PHP json support as well, - * so we can compare output - */ -class ServicesJsonTest extends MediaWikiTestCase { - /** - * Test to make sure core json_encode() and our Services_Json()->encode() - * produce the same output - * - * @dataProvider provideValuesToEncode - */ - public function testJsonEncode( $input, $desc ) { - if ( !function_exists( 'json_encode' ) ) { - $this->markTestIncomplete( 'No PHP json support, unable to test' ); - return; - } elseif( strtolower( json_encode( "\xf0\xa0\x80\x80" ) ) != '"\ud840\udc00"' ) { - $this->markTestIncomplete( 'Have buggy PHP json support, unable to test' ); - return; - } else { - $jsonObj = new Services_JSON(); - $this->assertEquals( - $jsonObj->encode( $input ), - json_encode( $input ), - $desc - ); - } - } - - /** - * Test to make sure core json_decode() and our Services_Json()->decode() - * produce the same output - * - * @dataProvider provideValuesToDecode - */ - public function testJsonDecode( $input, $desc ) { - if ( !function_exists( 'json_decode' ) ) { - $this->markTestIncomplete( 'No PHP json support, unable to test' ); - return; - } else { - $jsonObj = new Services_JSON(); - $this->assertEquals( - $jsonObj->decode( $input ), - json_decode( $input ), - $desc - ); - } - } - - function provideValuesToEncode() { - $obj = new stdClass(); - $obj->property = 'value'; - $obj->property2 = null; - $obj->property3 = 1.234; - return array( - array( 1, 'basic integer' ), - array( -1, 'negative integer' ), - array( 1.1, 'basic float' ), - array( true, 'basic bool true' ), - array( false, 'basic bool false' ), - array( 'some string', 'basic string test' ), - array( "some string\nwith newline", 'newline string test' ), - array( '♥ü', 'unicode string test' ), - array( array( 'some', 'string', 'values' ), 'basic array of strings' ), - array( array( 'key1' => 'val1', 'key2' => 'val2' ), 'array with string keys' ), - array( array( 1 => 'val1', 3 => 'val2', '2' => 'val3' ), 'out of order numbered array test' ), - array( array(), 'empty array test' ), - array( $obj, 'basic object test' ), - array( new stdClass, 'empty object test' ), - array( null, 'null test' ), - ); - } - - function provideValuesToDecode() { - return array( - array( '1', 'basic integer' ), - array( '-1', 'negative integer' ), - array( '1.1', 'basic float' ), - array( '1.1e1', 'scientific float' ), - array( 'true', 'basic bool true' ), - array( 'false', 'basic bool false' ), - array( '"some string"', 'basic string test' ), - array( '"some string\nwith newline"', 'newline string test' ), - array( '"♥ü"', 'unicode character string test' ), - array( '"\u2665"', 'unicode \\u string test' ), - array( '["some","string","values"]', 'basic array of strings' ), - array( '[]', 'empty array test' ), - array( '{"key":"value"}', 'Basic key => value test' ), - array( '{}', 'empty object test' ), - array( 'null', 'null test' ), - ); - } -} diff --git a/tests/phpunit/includes/libs/CSSJanusTest.php b/tests/phpunit/includes/libs/CSSJanusTest.php index 54f66077..5a3c1619 100644 --- a/tests/phpunit/includes/libs/CSSJanusTest.php +++ b/tests/phpunit/includes/libs/CSSJanusTest.php @@ -4,12 +4,14 @@ * CSSJanus libary: * http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus_test.py * Ported to PHP for ResourceLoader and has been extended since. + * + * @covers CSSJanus */ class CSSJanusTest extends MediaWikiTestCase { /** * @dataProvider provideTransformCases */ - function testTransform( $cssA, $cssB = null ) { + public function testTransform( $cssA, $cssB = null ) { if ( $cssB ) { $transformedA = CSSJanus::transform( $cssA ); @@ -17,10 +19,9 @@ class CSSJanusTest extends MediaWikiTestCase { $transformedB = CSSJanus::transform( $cssB ); $this->assertEquals( $transformedB, $cssA, 'Test B-A transformation' ); - - // If no B version is provided, it means - // the output should equal the input. } else { + // If no B version is provided, it means + // the output should equal the input. $transformedA = CSSJanus::transform( $cssA ); $this->assertEquals( $transformedA, $cssA, 'Nothing was flipped' ); } @@ -29,22 +30,23 @@ class CSSJanusTest extends MediaWikiTestCase { /** * @dataProvider provideTransformAdvancedCases */ - function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { + public function testTransformAdvanced( $code, $expectedOutput, $options = array() ) { $swapLtrRtlInURL = isset( $options['swapLtrRtlInURL'] ) ? $options['swapLtrRtlInURL'] : false; $swapLeftRightInURL = isset( $options['swapLeftRightInURL'] ) ? $options['swapLeftRightInURL'] : false; $flipped = CSSJanus::transform( $code, $swapLtrRtlInURL, $swapLeftRightInURL ); $this->assertEquals( $expectedOutput, $flipped, - 'Test flipping, options: url-ltr-rtl=' . ($swapLtrRtlInURL ? 'true' : 'false') - . ' url-left-right=' . ($swapLeftRightInURL ? 'true' : 'false') + 'Test flipping, options: url-ltr-rtl=' . ( $swapLtrRtlInURL ? 'true' : 'false' ) + . ' url-left-right=' . ( $swapLeftRightInURL ? 'true' : 'false' ) ); } + /** * @dataProvider provideTransformBrokenCases * @group Broken */ - function testTransformBroken( $code, $expectedOutput ) { + public function testTransformBroken( $code, $expectedOutput ) { $flipped = CSSJanus::transform( $code ); $this->assertEquals( $expectedOutput, $flipped, 'Test flipping' ); @@ -54,7 +56,7 @@ class CSSJanusTest extends MediaWikiTestCase { * These transform cases are tested *in both directions* * No need to declare a principle twice in both directions here. */ - function provideTransformCases() { + public static function provideTransformCases() { return array( // Property keys array( @@ -137,10 +139,15 @@ class CSSJanusTest extends MediaWikiTestCase { '.foo { padding: 1px inherit 3px auto; }', '.foo { padding: 1px auto 3px inherit; }' ), + // border-radius assigns different meanings to the values array( '.foo { border-radius: .25em 15px 0pt 0ex; }', - '.foo { border-radius: .25em 0ex 0pt 15px; }' + '.foo { border-radius: 15px .25em 0ex 0pt; }' + ), + array( + '.foo { border-radius: 0px 0px 5px 5px; }', ), + // Ensure the rule doesn't break other stuff array( '.foo { x-unknown: a b c d; }' ), @@ -151,14 +158,26 @@ class CSSJanusTest extends MediaWikiTestCase { '#settings td p strong' ), array( - # Not sure how 4+ values should behave, - # testing to make sure changes are detected - '.foo { x-unknown: 1 2 3 4 5; }', - '.foo { x-unknown: 1 4 3 2 5; }', + // Color names + '.foo { border-color: red green blue white }', + '.foo { border-color: red white blue green }', + ), + array( + // Color name, hexdecimal, RGB & RGBA + '.foo { border-color: red #f00 rgb(255, 0, 0) rgba(255, 0, 0, 0.5) }', + '.foo { border-color: red rgba(255, 0, 0, 0.5) rgb(255, 0, 0) #f00 }', ), array( - '.foo { x-unknown: 1 2 3 4 5 6; }', - '.foo { x-unknown: 1 4 3 2 5 6; }', + // Color name, hexdecimal, HSL & HSLA + '.foo { border-color: red #f00 hsl(0, 100%, 50%) hsla(0, 100%, 50%, 0.5) }', + '.foo { border-color: red hsla(0, 100%, 50%, 0.5) hsl(0, 100%, 50%) #f00 }', + ), + array( + // Do not mangle 5 or more values + '.foo { -x-unknown: 1 2 3 4 5; }' + ), + array( + '.foo { -x-unknown: 1 2 3 4 5 6; }' ), // Shorthand / Three notation @@ -179,6 +198,28 @@ class CSSJanusTest extends MediaWikiTestCase { '.foo { padding: 1px; }' ), + // text-shadow and box-shadow + array( + '.foo { box-shadow: -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + '.foo { box-shadow: 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + ), + array( + '.foo { box-shadow: inset -6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + '.foo { box-shadow: inset 6px 3px 8px 5px rgba(0, 0, 0, 0.25); }', + ), + array( + '.foo { text-shadow: orange 2px 0; }', + '.foo { text-shadow: orange -2px 0; }', + ), + array( + '.foo { text-shadow: 2px 0 orange; }', + '.foo { text-shadow: -2px 0 orange; }', + ), + array( + // Don't mangle zeroes + '.foo { text-shadow: orange 0 2px; }' + ), + // Direction // Note: This differs from the Python implementation, // see also CSSJanus::fixDirection for more info. @@ -377,6 +418,11 @@ class CSSJanusTest extends MediaWikiTestCase { '/* @noflip */ div { float: left; } .foo { float: right; }' ), array( + // support parentheses in selector + '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }', + '/* @noflip */ .test:not(:first) { margin-right: -0.25em; margin-left: 0.25em; }' + ), + array( // after multiple rules '.foo { float: left; } /* @noflip */ div { float: left; }', '.foo { float: right; } /* @noflip */ div { float: left; }' @@ -458,6 +504,16 @@ class CSSJanusTest extends MediaWikiTestCase { ".foo\t{\tleft\t:\t0;}", ".foo\t{\tright\t:\t0;}" ), + + // Guard against partial keys + array( + '.foo { leftxx: 0; }', + '.foo { leftxx: 0; }' + ), + array( + '.foo { rightxx: 0; }', + '.foo { rightxx: 0; }' + ), ); } @@ -466,7 +522,7 @@ class CSSJanusTest extends MediaWikiTestCase { * If both ways can be tested, either put both versions in here or move * it to provideTransformCases(). */ - function provideTransformAdvancedCases() { + public static function provideTransformAdvancedCases() { $bgPairs = array( # [ - _ . ] <-> [ left right ltr rtl ] 'foo.jpg' => 'foo.jpg', @@ -532,18 +588,8 @@ class CSSJanusTest extends MediaWikiTestCase { * Cases that are currently failing, but * should be looked at in the future as enhancements and/or bug fix */ - function provideTransformBrokenCases() { + public static function provideTransformBrokenCases() { return array( - // Guard against partial keys - array( - '.foo { leftxx: 0; }', - '.foo { leftxx: 0; }' - ), - array( - '.foo { rightxx: 0; }', - '.foo { rightxx: 0; }' - ), - // Guard against selectors that look flippable array( # <foo-left-x attr="x"> diff --git a/tests/phpunit/includes/libs/CSSMinTest.php b/tests/phpunit/includes/libs/CSSMinTest.php index a3827756..951dd7b9 100644 --- a/tests/phpunit/includes/libs/CSSMinTest.php +++ b/tests/phpunit/includes/libs/CSSMinTest.php @@ -6,37 +6,28 @@ */ class CSSMinTest extends MediaWikiTestCase { - protected $oldServer = null, $oldCanServer = null; - function setUp() { + protected function setUp() { parent::setUp(); - // Fake $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $this->oldServer = $wgServer; - $this->oldCanServer = $wgCanonicalServer; - $wgServer = $wgCanonicalServer = 'http://wiki.example.org'; - } - - function tearDown() { - // Restore $wgServer and $wgCanonicalServer - global $wgServer, $wgCanonicalServer; - $wgServer = $this->oldServer; - $wgCanonicalServer = $this->oldCanServer; + $server = 'http://doc.example.org'; - parent::tearDown(); + $this->setMwGlobals( array( + 'wgServer' => $server, + 'wgCanonicalServer' => $server, + ) ); } /** * @dataProvider provideMinifyCases */ - function testMinify( $code, $expectedOutput ) { + public function testMinify( $code, $expectedOutput ) { $minified = CSSMin::minify( $code ); $this->assertEquals( $expectedOutput, $minified, 'Minified output should be in the form expected.' ); } - function provideMinifyCases() { + public static function provideMinifyCases() { return array( // Whitespace array( "\r\t\f \v\n\r", "" ), @@ -79,14 +70,14 @@ class CSSMinTest extends MediaWikiTestCase { /** * @dataProvider provideRemapCases */ - function testRemap( $message, $params, $expectedOutput ) { + public function testRemap( $message, $params, $expectedOutput ) { $remapped = call_user_func_array( 'CSSMin::remap', $params ); $messageAdd = " Case: $message"; $this->assertEquals( $expectedOutput, $remapped, 'CSSMin::remap should return the expected url form.' . $messageAdd ); } - function provideRemapCases() { + public static function provideRemapCases() { // Parameter signature: // CSSMin::remap( $code, $local, $remote, $embedData = true ) return array( @@ -113,7 +104,7 @@ class CSSMinTest extends MediaWikiTestCase { array( 'Expand absolute paths', array( 'foo { prop: url(/w/skin/images/bar.png); }', false, 'http://example.org/quux', false ), - 'foo { prop: url(http://wiki.example.org/w/skin/images/bar.png); }', + 'foo { prop: url(http://doc.example.org/w/skin/images/bar.png); }', ), ); } @@ -124,11 +115,11 @@ class CSSMinTest extends MediaWikiTestCase { * @group Broken * @dataProvider provideStringCases */ - function testMinifyWithCSSStringValues( $code, $expectedOutput ) { + public function testMinifyWithCSSStringValues( $code, $expectedOutput ) { $this->testMinifyOutput( $code, $expectedOutput ); } - function provideStringCases() { + public static function provideStringCases() { return array( // String values should be respected // - More than one space in a string value diff --git a/tests/phpunit/includes/libs/GenericArrayObjectTest.php b/tests/phpunit/includes/libs/GenericArrayObjectTest.php index 70fce111..7436c43c 100644 --- a/tests/phpunit/includes/libs/GenericArrayObjectTest.php +++ b/tests/phpunit/includes/libs/GenericArrayObjectTest.php @@ -36,7 +36,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * * @return array */ - public abstract function elementInstancesProvider(); + abstract public function elementInstancesProvider(); /** * Returns the name of the concrete class being tested. @@ -45,7 +45,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * * @return string */ - public abstract function getInstanceClass(); + abstract public function getInstanceClass(); /** * Provides instances of the concrete class being tested. @@ -58,7 +58,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $instances = array(); foreach ( $this->elementInstancesProvider() as $elementInstances ) { - $instances[] = $this->getNew( $elementInstances ); + $instances[] = $this->getNew( $elementInstances[0] ); } return $this->arrayWrap( $instances ); @@ -73,6 +73,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { */ protected function getNew( array $elements = array() ) { $class = $this->getInstanceClass(); + return new $class( $elements ); } @@ -110,7 +111,9 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { * @param GenericArrayObject $list */ public function testUnset( GenericArrayObject $list ) { - if ( !$list->isEmpty() ) { + if ( $list->isEmpty() ) { + $this->assertTrue( true ); // We cannot test unset if there are no elements + } else { $offset = $list->getIterator()->key(); $count = $list->count(); $list->offsetUnset( $offset ); @@ -123,10 +126,6 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { unset( $list[$offset] ); $this->assertEquals( $count - 1, $list->count() ); } - - $exception = null; - try { $list->offsetUnset( 'sdfsedtgsrdysftu' ); } catch ( \Exception $exception ){} - $this->assertInstanceOf( '\Exception', $exception ); } /** @@ -155,7 +154,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $this->assertEquals( $listSize, $list->count() ); - $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { $list->append( $element ); } ); } @@ -171,14 +170,13 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $elementClass = $list->getObjectType(); - foreach ( array( 42, 'foo', array(), new \stdClass(), 4.2 ) as $element ) { + foreach ( array( 42, 'foo', array(), new stdClass(), 4.2 ) as $element ) { $validValid = $element instanceof $elementClass; - try{ + try { call_user_func( $function, $list, $element ); $valid = true; - } - catch ( InvalidArgumentException $exception ) { + } catch ( InvalidArgumentException $exception ) { $valid = false; } @@ -200,6 +198,7 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { public function testOffsetSet( array $elements ) { if ( $elements === array() ) { $this->assertTrue( true ); + return; } @@ -237,9 +236,28 @@ abstract class GenericArrayObjectTest extends MediaWikiTestCase { $this->assertEquals( count( $elements ), $list->count() ); - $this->checkTypeChecks( function( GenericArrayObject $list, $element ) { + $this->checkTypeChecks( function ( GenericArrayObject $list, $element ) { $list->offsetSet( mt_rand(), $element ); } ); } + /** + * @dataProvider instanceProvider + * + * @since 1.21 + * + * @param GenericArrayObject $list + */ + public function testSerialization( GenericArrayObject $list ) { + $serialization = serialize( $list ); + $copy = unserialize( $serialization ); + + $this->assertEquals( $serialization, serialize( $copy ) ); + $this->assertEquals( count( $list ), count( $copy ) ); + + $list = $list->getArrayCopy(); + $copy = $copy->getArrayCopy(); + + $this->assertArrayEquals( $list, $copy, true, true ); + } } diff --git a/tests/phpunit/includes/libs/IEUrlExtensionTest.php b/tests/phpunit/includes/libs/IEUrlExtensionTest.php index c6270e90..66fe915a 100644 --- a/tests/phpunit/includes/libs/IEUrlExtensionTest.php +++ b/tests/phpunit/includes/libs/IEUrlExtensionTest.php @@ -4,15 +4,15 @@ * Tests for IEUrlExtension::findIE6Extension */ class IEUrlExtensionTest extends MediaWikiTestCase { - function testSimple() { - $this->assertEquals( + public function testSimple() { + $this->assertEquals( 'y', IEUrlExtension::findIE6Extension( 'x.y' ), 'Simple extension' ); } - function testSimpleNoExt() { + public function testSimpleNoExt() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'x' ), @@ -20,7 +20,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testEmpty() { + public function testEmpty() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '' ), @@ -28,7 +28,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testQuestionMark() { + public function testQuestionMark() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '?' ), @@ -36,7 +36,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExtQuestionMark() { + public function testExtQuestionMark() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '.x?' ), @@ -44,7 +44,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testQuestionMarkExt() { + public function testQuestionMarkExt() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '?.x' ), @@ -52,7 +52,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testInvalidChar() { + public function testInvalidChar() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '.x*' ), @@ -60,7 +60,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testInvalidCharThenExtension() { + public function testInvalidCharThenExtension() { $this->assertEquals( 'x', IEUrlExtension::findIE6Extension( '*.x' ), @@ -68,7 +68,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testMultipleQuestionMarks() { + public function testMultipleQuestionMarks() { $this->assertEquals( 'c', IEUrlExtension::findIE6Extension( 'a?b?.c?.d?e?f' ), @@ -76,7 +76,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExeException() { + public function testExeException() { $this->assertEquals( 'd', IEUrlExtension::findIE6Extension( 'a?b?.exe?.d?.e' ), @@ -84,7 +84,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testExeException2() { + public function testExeException2() { $this->assertEquals( 'exe', IEUrlExtension::findIE6Extension( 'a?b?.exe' ), @@ -92,7 +92,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testHash() { + public function testHash() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'a#b.c' ), @@ -100,7 +100,7 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testHash2() { + public function testHash2() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( 'a?#b.c' ), @@ -108,11 +108,19 @@ class IEUrlExtensionTest extends MediaWikiTestCase { ); } - function testDotAtEnd() { + public function testDotAtEnd() { $this->assertEquals( '', IEUrlExtension::findIE6Extension( '.' ), 'Dot at end of string' ); } + + public function testTwoDots() { + $this->assertEquals( + 'z', + IEUrlExtension::findIE6Extension( 'x.y.z' ), + 'Two dots' + ); + } } diff --git a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php index f121b018..ab72e361 100644 --- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php +++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php @@ -2,7 +2,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { - function provideCases() { + public static function provideCases() { return array( // Basic whitespace and comments that should be stripped entirely @@ -14,7 +14,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { * At some point there was a bug that caused this comment to be ended at '* /', * causing /M... to be left as the beginning of a regex. */ - array( "/**\n * Foo\n * {\n * 'bar' : {\n * //Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", ""), + array( "/**\n * Foo\n * {\n * 'bar' : {\n * //Multiple rules with configurable operators\n * 'baz' : false\n * }\n */", "" ), /** * ' Foo \' bar \ @@ -80,7 +80,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { array( "switch(x){case y?z:{}/ x/g:{}/ x/g;}", "switch(x){case y?z:{}/x/g:{}/ x/g;}" ), array( "function x(){}/ x/g", "function x(){}/ x/g" ), array( "+function x(){}/ x/g", "+function x(){}/x/g" ), - + // Multiline quoted string array( "var foo=\"\\\nblah\\\n\";", "var foo=\"\\\nblah\\\n\";" ), @@ -96,16 +96,16 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { // Division vs. regex nastiness array( "alert( (10+10) / '/'.charCodeAt( 0 ) + '//' );", "alert((10+10)/'/'.charCodeAt(0)+'//');" ), array( "if(1)/a /g.exec('Pa ss');", "if(1)/a /g.exec('Pa ss');" ), - + // newline insertion after 1000 chars: break after the "++", not before array( str_repeat( ';', 996 ) . "if(x++);", str_repeat( ';', 996 ) . "if(x++\n);" ), // Unicode letter characters should pass through ok in identifiers (bug 31187) - array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}'), + array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}' ), // Per spec unicode char escape values should work in identifiers, // as long as it's a valid char. In future it might get normalized. - array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'), + array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}' ), // Some structures that might look invalid at first sight array( "var a = 5.;", "var a=5.;" ), @@ -119,7 +119,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { /** * @dataProvider provideCases */ - function testJavaScriptMinifierOutput( $code, $expectedOutput ) { + public function testJavaScriptMinifierOutput( $code, $expectedOutput ) { $minified = JavaScriptMinifier::minify( $code ); // JSMin+'s parser will throw an exception if output is not valid JS. @@ -132,12 +132,12 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { $this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." ); } - function provideBug32548() { + public static function provideBug32548() { return array( array( // This one gets interpreted all together by the prior code; // no break at the 'E' happens. - '1.23456789E55', + '1.23456789E55', ), array( // This one breaks under the bad code; splits between 'E' and '+' @@ -153,7 +153,7 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { /** * @dataProvider provideBug32548 */ - function testBug32548Exponent( $num ) { + public function testBug32548Exponent( $num ) { // Long line breaking was being incorrectly done between the base and // exponent part of a number, causing a syntax error. The line should // instead break at the start of the number. @@ -165,6 +165,6 @@ class JavaScriptMinifierTest extends MediaWikiTestCase { $minified = JavaScriptMinifier::minify( $input ); - $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent"); + $this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent" ); } } diff --git a/tests/phpunit/includes/logging/LogFormatterTest.php b/tests/phpunit/includes/logging/LogFormatterTest.php new file mode 100644 index 00000000..e8ccf433 --- /dev/null +++ b/tests/phpunit/includes/logging/LogFormatterTest.php @@ -0,0 +1,207 @@ +<?php +/** + * @group Database + */ +class LogFormatterTest extends MediaWikiLangTestCase { + + /** + * @var User + */ + protected $user; + + /** + * @var Title + */ + protected $title; + + /** + * @var RequestContext + */ + protected $context; + + protected function setUp() { + parent::setUp(); + + global $wgLang; + + $this->setMwGlobals( array( + 'wgLogTypes' => array( 'phpunit' ), + 'wgLogActionsHandlers' => array( 'phpunit/test' => 'LogFormatter', + 'phpunit/param' => 'LogFormatter' ), + 'wgUser' => User::newFromName( 'Testuser' ), + 'wgExtensionMessagesFiles' => array( 'LogTests' => __DIR__ . '/LogTests.i18n.php' ), + ) ); + + $wgLang->getLocalisationCache()->recache( $wgLang->getCode() ); + + $this->user = User::newFromName( 'Testuser' ); + $this->title = Title::newMainPage(); + + $this->context = new RequestContext(); + $this->context->setUser( $this->user ); + $this->context->setTitle( $this->title ); + $this->context->setLanguage( $wgLang ); + } + + protected function tearDown() { + parent::tearDown(); + + global $wgLang; + $wgLang->getLocalisationCache()->recache( $wgLang->getCode() ); + } + + public function newLogEntry( $action, $params ) { + $logEntry = new ManualLogEntry( 'phpunit', $action ); + $logEntry->setPerformer( $this->user ); + $logEntry->setTarget( $this->title ); + $logEntry->setComment( 'A very good reason' ); + + $logEntry->setParameters( $params ); + + return $logEntry; + } + + public function testNormalLogParams() { + $entry = $this->newLogEntry( 'test', array() ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $formatter->setShowUserToolLinks( false ); + $paramsWithoutTools = $formatter->getMessageParametersForTesting(); + unset( $formatter->parsedParameters ); + + $formatter->setShowUserToolLinks( true ); + $paramsWithTools = $formatter->getMessageParametersForTesting(); + + $userLink = Linker::userLink( + $this->user->getId(), + $this->user->getName() + ); + + $userTools = Linker::userToolLinksRedContribs( + $this->user->getId(), + $this->user->getName(), + $this->user->getEditCount() + ); + + $titleLink = Linker::link( $this->title, null, array(), array() ); + + // $paramsWithoutTools and $paramsWithTools should be only different + // in index 0 + $this->assertEquals( $paramsWithoutTools[1], $paramsWithTools[1] ); + $this->assertEquals( $paramsWithoutTools[2], $paramsWithTools[2] ); + + $this->assertEquals( $userLink, $paramsWithoutTools[0]['raw'] ); + $this->assertEquals( $userLink . $userTools, $paramsWithTools[0]['raw'] ); + + $this->assertEquals( $this->user->getName(), $paramsWithoutTools[1] ); + + $this->assertEquals( $titleLink, $paramsWithoutTools[2]['raw'] ); + } + + public function testLogParamsTypeRaw() { + $params = array( '4:raw:raw' => Linker::link( $this->title, null, array(), array() ) ); + $expected = Linker::link( $this->title, null, array(), array() ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeMsg() { + $params = array( '4:msg:msg' => 'log-description-phpunit' ); + $expected = wfMessage( 'log-description-phpunit' )->text(); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeMsgContent() { + $params = array( '4:msg-content:msgContent' => 'log-description-phpunit' ); + $expected = wfMessage( 'log-description-phpunit' )->inContentLanguage()->text(); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeNumber() { + global $wgLang; + + $params = array( '4:number:number' => 123456789 ); + $expected = $wgLang->formatNum( 123456789 ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeUserLink() { + $params = array( '4:user-link:userLink' => $this->user->getName() ); + $expected = Linker::userLink( + $this->user->getId(), + $this->user->getName() + ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypeTitleLink() { + $params = array( '4:title-link:titleLink' => $this->title->getText() ); + $expected = Linker::link( $this->title, null, array(), array() ); + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogParamsTypePlain() { + $params = array( '4:plain:plain' => 'Some plain text' ); + $expected = 'Some plain text'; + + $entry = $this->newLogEntry( 'param', $params ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $logParam = $formatter->getActionText(); + + $this->assertEquals( $expected, $logParam ); + } + + public function testLogComment() { + $entry = $this->newLogEntry( 'test', array() ); + $formatter = LogFormatter::newFromEntry( $entry ); + $formatter->setContext( $this->context ); + + $comment = ltrim( Linker::commentBlock( $entry->getComment() ) ); + + $this->assertEquals( $comment, $formatter->getComment() ); + } +} diff --git a/tests/phpunit/includes/logging/LogTests.i18n.php b/tests/phpunit/includes/logging/LogTests.i18n.php new file mode 100644 index 00000000..78787ba1 --- /dev/null +++ b/tests/phpunit/includes/logging/LogTests.i18n.php @@ -0,0 +1,15 @@ +<?php +/** + * Internationalisation file for log tests. + * + * @file + */ + +$messages = array(); + +$messages['en'] = array( + 'log-name-phpunit' => 'PHPUnit-log', + 'log-description-phpunit' => 'Log for PHPUnit-tests', + 'logentry-phpunit-test' => '$1 {{GENDER:$2|tests}} with page $3', + 'logentry-phpunit-param' => '$4', +); diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php index 88f87ef9..a0e63a8a 100644 --- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php +++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php @@ -1,7 +1,11 @@ <?php class BitmapMetadataHandlerTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgShowEXIF', false ); + $this->filePath = __DIR__ . '/../../data/media/'; } @@ -12,33 +16,31 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * Basically the file has IPTC and XMP metadata, the * IPTC should override the XMP, except for the multilingual * translation (to en) where XMP should win. + * @covers BitmapMetadataHandler::Jpeg */ public function testMultilingualCascade() { - if ( !wfDl( 'exif' ) ) { + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - if ( !wfDl( 'xml' ) ) { + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( "This test needs the xml extension." ); } - global $wgShowEXIF; - $oldExif = $wgShowEXIF; - $wgShowEXIF = true; + + $this->setMwGlobals( 'wgShowEXIF', true ); $meta = BitmapMetadataHandler::Jpeg( $this->filePath . '/Xmp-exif-multilingual_test.jpg' ); $expected = array( 'x-default' => 'right(iptc)', - 'en' => 'right translation', - '_type' => 'lang' + 'en' => 'right translation', + '_type' => 'lang' ); - + $this->assertArrayHasKey( 'ImageDescription', $meta, 'Did not extract any ImageDescription info?!' ); $this->assertEquals( $expected, $meta['ImageDescription'] ); - - $wgShowEXIF = $oldExif; } /** @@ -47,6 +49,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * * There's more extensive tests of comment extraction in * JpegMetadataExtractorTests.php + * @covers BitmapMetadataHandler::Jpeg */ public function testJpegComment() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -59,6 +62,7 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { /** * Make sure a bad iptc block doesn't stop the other metadata * from being extracted. + * @covers BitmapMetadataHandler::Jpeg */ public function testBadIPTC() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -66,6 +70,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] ); } + /** + * @covers BitmapMetadataHandler::Jpeg + */ public function testIPTCDates() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . 'iptc-timetest.jpg' ); @@ -73,9 +80,11 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( '2020:07:14 01:36:05', $meta['DateTimeDigitized'] ); $this->assertEquals( '1997:03:02 00:01:02', $meta['DateTimeOriginal'] ); } + /** * File has an invalid time (+ one valid but really weird time) * that shouldn't be included + * @covers BitmapMetadataHandler::Jpeg */ public function testIPTCDatesInvalid() { $meta = BitmapMetadataHandler::Jpeg( $this->filePath . @@ -89,6 +98,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { * XMP data should take priority over iptc data * when hash has been updated, but not when * the hash is wrong. + * @covers BitmapMetadataHandler::addMetadata + * @covers BitmapMetadataHandler::getMetadataArray */ public function testMerging() { $merger = new BitmapMetadataHandler(); @@ -112,35 +123,45 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } + /** + * @covers BitmapMetadataHandler::png + */ public function testPNGXMP() { - if ( !wfDl( 'xml' ) ) { + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( "This test needs the xml extension." ); } $handler = new BitmapMetadataHandler(); $result = $handler->png( $this->filePath . 'xmp.png' ); - $expected = array ( + $expected = array( 'frameCount' => 0, 'loopCount' => 1, 'duration' => 0, 'bitDepth' => 1, 'colorType' => 'index-coloured', - 'metadata' => array ( + 'metadata' => array( 'SerialNumber' => '123456789', '_MW_PNG_VERSION' => 1, ), ); - $this->assertEquals( $expected, $result ); + $this->assertEquals( $expected, $result ); } + + /** + * @covers BitmapMetadataHandler::png + */ public function testPNGNative() { $handler = new BitmapMetadataHandler(); $result = $handler->png( $this->filePath . 'Png-native-test.png' ); $expected = 'http://example.com/url'; - $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] ); + $this->assertEquals( $expected, $result['metadata']['Identifier']['x-default'] ); } + + /** + * @covers BitmapMetadataHandler::getTiffByteOrder + */ public function testTiffByteOrder() { $handler = new BitmapMetadataHandler(); $res = $handler->getTiffByteOrder( $this->filePath . 'test.tiff' ); $this->assertEquals( 'LE', $res ); } - } diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php index 11d9dc47..9395b660 100644 --- a/tests/phpunit/includes/media/BitmapScalingTest.php +++ b/tests/phpunit/includes/media/BitmapScalingTest.php @@ -2,36 +2,34 @@ class BitmapScalingTest extends MediaWikiTestCase { - function setUp() { - global $wgMaxImageArea, $wgCustomConvertCommand; - $this->oldMaxImageArea = $wgMaxImageArea; - $this->oldCustomConvertCommand = $wgCustomConvertCommand; - $wgMaxImageArea = 1.25e7; // 3500x3500 - $wgCustomConvertCommand = 'dummy'; // Set so that we don't get client side rendering - } - function tearDown() { - global $wgMaxImageArea, $wgCustomConvertCommand; - $wgMaxImageArea = $this->oldMaxImageArea; - $wgCustomConvertCommand = $this->oldCustomConvertCommand; + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( array( + 'wgMaxImageArea' => 1.25e7, // 3500x3500 + 'wgCustomConvertCommand' => 'dummy', // Set so that we don't get client side rendering + ) ); } + /** * @dataProvider provideNormaliseParams + * @covers BitmapHandler::normaliseParams */ - function testNormaliseParams( $fileDimensions, $expectedParams, $params, $msg ) { + public function testNormaliseParams( $fileDimensions, $expectedParams, $params, $msg ) { $file = new FakeDimensionFile( $fileDimensions ); $handler = new BitmapHandler; $valid = $handler->normaliseParams( $file, $params ); $this->assertTrue( $valid ); $this->assertEquals( $expectedParams, $params, $msg ); } - - function provideNormaliseParams() { + + public static function provideNormaliseParams() { return array( - /* Regular resize operations */ + /* Regular resize operations */ array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, 'page' => 1, ), @@ -40,53 +38,53 @@ class BitmapScalingTest extends MediaWikiTestCase { ), array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, - 'page' => 1, + 'page' => 1, ), array( 'width' => 512, 'height' => 768 ), 'Resizing with height set too high', ), array( array( 1024, 768 ), - array( - 'width' => 512, 'height' => 384, + array( + 'width' => 512, 'height' => 384, 'physicalWidth' => 512, 'physicalHeight' => 384, - 'page' => 1, + 'page' => 1, ), array( 'width' => 1024, 'height' => 384 ), 'Resizing with height set', ), - + /* Very tall images */ array( array( 1000, 100 ), - array( + array( 'width' => 5, 'height' => 1, 'physicalWidth' => 5, 'physicalHeight' => 1, - 'page' => 1, + 'page' => 1, ), array( 'width' => 5 ), 'Very wide image', ), - + array( array( 100, 1000 ), - array( + array( 'width' => 1, 'height' => 10, 'physicalWidth' => 1, 'physicalHeight' => 10, - 'page' => 1, + 'page' => 1, ), array( 'width' => 1 ), 'Very high image', ), array( array( 100, 1000 ), - array( + array( 'width' => 1, 'height' => 5, 'physicalWidth' => 1, 'physicalHeight' => 10, - 'page' => 1, + 'page' => 1, ), array( 'width' => 10, 'height' => 5 ), 'Very high image with height set', @@ -94,58 +92,46 @@ class BitmapScalingTest extends MediaWikiTestCase { /* Max image area */ array( array( 4000, 4000 ), - array( + array( 'width' => 5000, 'height' => 5000, 'physicalWidth' => 4000, 'physicalHeight' => 4000, - 'page' => 1, + 'page' => 1, ), array( 'width' => 5000 ), 'Bigger than max image size but doesn\'t need scaling', ), ); - } - function testTooBigImage() { + } + + /** + * @covers BitmapHandler::doTransform + */ + public function testTooBigImage() { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $handler = new BitmapHandler; $params = array( 'width' => '3700' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformParameterError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } - function testTooBigMustRenderImage() { + + /** + * @covers BitmapHandler::doTransform + */ + public function testTooBigMustRenderImage() { $file = new FakeDimensionFile( array( 4000, 4000 ) ); $file->mustRender = true; $handler = new BitmapHandler; $params = array( 'width' => '5000' ); // Still bigger than max size. - $this->assertEquals( 'TransformParameterError', + $this->assertEquals( 'TransformParameterError', get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) ); } - - function testImageArea() { + + /** + * @covers BitmapHandler::getImageArea + */ + public function testImageArea() { $file = new FakeDimensionFile( array( 7, 9 ) ); $handler = new BitmapHandler; $this->assertEquals( 63, $handler->getImageArea( $file ) ); } } - -class FakeDimensionFile extends File { - public $mustRender = false; - - public function __construct( $dimensions ) { - parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), - new NullRepo( null ) ); - - $this->dimensions = $dimensions; - } - public function getWidth( $page = 1 ) { - return $this->dimensions[0]; - } - public function getHeight( $page = 1 ) { - return $this->dimensions[1]; - } - public function mustRender() { - return $this->mustRender; - } - public function getPath() { - return ''; - } -} diff --git a/tests/phpunit/includes/media/ExifBitmapTest.php b/tests/phpunit/includes/media/ExifBitmapTest.php index b2f6b7ba..a2e0eb62 100644 --- a/tests/phpunit/includes/media/ExifBitmapTest.php +++ b/tests/phpunit/includes/media/ExifBitmapTest.php @@ -2,60 +2,91 @@ class ExifBitmapTest extends MediaWikiTestCase { - public function setUp() { - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; - $this->handler = new ExifBitmapHandler; - if ( !wfDl( 'exif' ) ) { + /** + * @var ExifBitmapHandler + */ + protected $handler; + + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; + $this->setMwGlobals( 'wgShowEXIF', true ); + + $this->handler = new ExifBitmapHandler; + } + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsOldBroken() { $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsBrokenFile() { $res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsInvalid() { $res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testGoodMetadata() { $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res ); } + + /** + * @covers ExifBitmapHandler::isMetadataValid + */ public function testIsOldGood() { $meta = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res ); } - // Handle metadata from paged tiff handler (gotten via instant commons) - // gracefully. + + /** + * Handle metadata from paged tiff handler (gotten via instant commons) gracefully. + * @covers ExifBitmapHandler::isMetadataValid + */ public function testPagedTiffHandledGracefully() { $meta = 'a:6:{s:9:"page_data";a:1:{i:1;a:5:{s:5:"width";i:643;s:6:"height";i:448;s:5:"alpha";s:4:"true";s:4:"page";i:1;s:6:"pixels";i:288064;}}s:10:"page_count";i:1;s:10:"first_page";i:1;s:9:"last_page";i:1;s:4:"exif";a:9:{s:10:"ImageWidth";i:643;s:11:"ImageLength";i:448;s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:4;s:12:"RowsPerStrip";i:50;s:19:"PlanarConfiguration";i:1;s:22:"MEDIAWIKI_EXIF_VERSION";i:1;}s:21:"TIFF_METADATA_VERSION";s:3:"1.4";}'; $res = $this->handler->isMetadataValid( null, $meta ); $this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res ); } - function testConvertMetadataLatest() { + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataLatest() { $metadata = array( - 'foo' => array( 'First', 'Second', '_type' => 'ol' ), - 'MEDIAWIKI_EXIF_VERSION' => 2 - ); + 'foo' => array( 'First', 'Second', '_type' => 'ol' ), + 'MEDIAWIKI_EXIF_VERSION' => 2 + ); $res = $this->handler->convertMetadataVersion( $metadata, 2 ); $this->assertEquals( $metadata, $res ); } - function testConvertMetadataToOld() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataToOld() { $metadata = array( 'foo' => array( 'First', 'Second', '_type' => 'ol' ), 'bar' => array( 'First', 'Second', '_type' => 'ul' ), @@ -73,9 +104,13 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } - function testConvertMetadataSoftware() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataSoftware() { $metadata = array( - 'Software' => array( array('GIMP', '1.1' ) ), + 'Software' => array( array( 'GIMP', '1.1' ) ), 'MEDIAWIKI_EXIF_VERSION' => 2, ); $expected = array( @@ -85,7 +120,11 @@ class ExifBitmapTest extends MediaWikiTestCase { $res = $this->handler->convertMetadataVersion( $metadata, 1 ); $this->assertEquals( $expected, $res ); } - function testConvertMetadataSoftwareNormal() { + + /** + * @covers ExifBitmapHandler::convertMetadataVersion + */ + public function testConvertMetadataSoftwareNormal() { $metadata = array( 'Software' => array( "GIMP 1.2", "vim" ), 'MEDIAWIKI_EXIF_VERSION' => 2, diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php index 6af52dd1..64276d92 100644 --- a/tests/phpunit/includes/media/ExifRotationTest.php +++ b/tests/phpunit/includes/media/ExifRotationTest.php @@ -1,51 +1,44 @@ <?php - /** - * Tests related to auto rotation + * Tests related to auto rotation. + * + * @group medium + * + * @todo covers tags */ class ExifRotationTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + $this->handler = new BitmapHandler(); $filePath = __DIR__ . '/../../data/media'; $tmpDir = $this->getNewTempDirectory(); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', - 'backend' => new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', + 'backend' => new FSFileBackend( array( + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'temp-thumb' => $tmpDir, 'data' => $filePath ) ) ) ) ); - if ( !wfDl( 'exif' ) ) { - $this->markTestSkipped( "This test needs the exif extension." ); - } - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - - global $wgEnableAutoRotation; - $this->oldAuto = $wgEnableAutoRotation; - $wgEnableAutoRotation = true; - } - - public function tearDown() { - global $wgShowEXIF, $wgEnableAutoRotation; - $wgShowEXIF = $this->show; - $wgEnableAutoRotation = $this->oldAuto; - parent::tearDown(); + $this->setMwGlobals( array( + 'wgShowEXIF' => true, + 'wgEnableAutoRotation' => true, + ) ); } /** - * - * @dataProvider providerFiles + * @dataProvider provideFiles */ - function testMetadata( $name, $type, $info ) { + public function testMetadata( $name, $type, $info ) { if ( !BitmapHandler::canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } @@ -56,14 +49,14 @@ class ExifRotationTest extends MediaWikiTestCase { /** * - * @dataProvider providerFiles + * @dataProvider provideFiles */ - function testRotationRendering( $name, $type, $info, $thumbs ) { + public function testRotationRendering( $name, $type, $info, $thumbs ) { if ( !BitmapHandler::canRotate() ) { $this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." ); } - foreach( $thumbs as $size => $out ) { - if( preg_match('/^(\d+)px$/', $size, $matches ) ) { + foreach ( $thumbs as $size => $out ) { + if ( preg_match( '/^(\d+)px$/', $size, $matches ) ) { $params = array( 'width' => $matches[1], ); @@ -73,7 +66,7 @@ class ExifRotationTest extends MediaWikiTestCase { 'height' => $matches[2] ); } else { - throw new MWException('bogus test data format ' . $size); + throw new MWException( 'bogus test data format ' . $size ); } $file = $this->dataFile( $name, $type ); @@ -83,23 +76,24 @@ class ExifRotationTest extends MediaWikiTestCase { $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); $gis = getimagesize( $thumb->getLocalCopyPath() ); - if ($out[0] > $info['width']) { + if ( $out[0] > $info['width'] ) { // Physical image won't be scaled bigger than the original. - $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size" ); } else { - $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size" ); } } } + /* Utility function */ private function dataFile( $name, $type ) { return new UnregisteredLocalFile( false, $this->repo, "mwstore://localtesting/data/$name", $type ); } - function providerFiles() { + public static function provideFiles() { return array( array( 'landscape-plain.jpg', @@ -134,29 +128,25 @@ class ExifRotationTest extends MediaWikiTestCase { /** * Same as before, but with auto-rotation disabled. - * @dataProvider providerFilesNoAutoRotate + * @dataProvider provideFilesNoAutoRotate */ - function testMetadataNoAutoRotate( $name, $type, $info ) { - global $wgEnableAutoRotation; - $wgEnableAutoRotation = false; + public function testMetadataNoAutoRotate( $name, $type, $info ) { + $this->setMwGlobals( 'wgEnableAutoRotation', false ); $file = $this->dataFile( $name, $type ); $this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" ); $this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" ); - - $wgEnableAutoRotation = true; } /** * - * @dataProvider providerFilesNoAutoRotate + * @dataProvider provideFilesNoAutoRotate */ - function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) { - global $wgEnableAutoRotation; - $wgEnableAutoRotation = false; + public function testRotationRenderingNoAutoRotate( $name, $type, $info, $thumbs ) { + $this->setMwGlobals( 'wgEnableAutoRotation', false ); - foreach( $thumbs as $size => $out ) { - if( preg_match('/^(\d+)px$/', $size, $matches ) ) { + foreach ( $thumbs as $size => $out ) { + if ( preg_match( '/^(\d+)px$/', $size, $matches ) ) { $params = array( 'width' => $matches[1], ); @@ -166,7 +156,7 @@ class ExifRotationTest extends MediaWikiTestCase { 'height' => $matches[2] ); } else { - throw new MWException('bogus test data format ' . $size); + throw new MWException( 'bogus test data format ' . $size ); } $file = $this->dataFile( $name, $type ); @@ -176,19 +166,18 @@ class ExifRotationTest extends MediaWikiTestCase { $this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" ); $gis = getimagesize( $thumb->getLocalCopyPath() ); - if ($out[0] > $info['width']) { + if ( $out[0] > $info['width'] ) { // Physical image won't be scaled bigger than the original. - $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $info['width'], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $info['height'], $gis[1], "$name: thumb actual height check for $size" ); } else { - $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size"); - $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size"); + $this->assertEquals( $out[0], $gis[0], "$name: thumb actual width check for $size" ); + $this->assertEquals( $out[1], $gis[1], "$name: thumb actual height check for $size" ); } } - $wgEnableAutoRotation = true; } - function providerFilesNoAutoRotate() { + public static function provideFilesNoAutoRotate() { return array( array( 'landscape-plain.jpg', @@ -220,41 +209,40 @@ class ExifRotationTest extends MediaWikiTestCase { ) ); } - - + + const TEST_WIDTH = 100; const TEST_HEIGHT = 200; - + /** * @dataProvider provideBitmapExtractPreRotationDimensions */ - function testBitmapExtractPreRotationDimensions( $rotation, $expected ) { + public function testBitmapExtractPreRotationDimensions( $rotation, $expected ) { $result = $this->handler->extractPreRotationDimensions( array( - 'physicalWidth' => self::TEST_WIDTH, - 'physicalHeight' => self::TEST_HEIGHT, - ), $rotation ); + 'physicalWidth' => self::TEST_WIDTH, + 'physicalHeight' => self::TEST_HEIGHT, + ), $rotation ); $this->assertEquals( $expected, $result ); } - - function provideBitmapExtractPreRotationDimensions() { + + public static function provideBitmapExtractPreRotationDimensions() { return array( array( 0, - array( self::TEST_WIDTH, self::TEST_HEIGHT ) + array( self::TEST_WIDTH, self::TEST_HEIGHT ) ), array( 90, - array( self::TEST_HEIGHT, self::TEST_WIDTH ) + array( self::TEST_HEIGHT, self::TEST_WIDTH ) ), array( 180, - array( self::TEST_WIDTH, self::TEST_HEIGHT ) + array( self::TEST_WIDTH, self::TEST_HEIGHT ) ), array( 270, - array( self::TEST_HEIGHT, self::TEST_WIDTH ) + array( self::TEST_HEIGHT, self::TEST_WIDTH ) ), ); } } - diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php index 045777d7..dea36b03 100644 --- a/tests/phpunit/includes/media/ExifTest.php +++ b/tests/phpunit/includes/media/ExifTest.php @@ -1,25 +1,25 @@ <?php class ExifTest extends MediaWikiTestCase { - public function setUp() { - $this->mediaPath = __DIR__ . '/../../data/media/'; + /** @var string */ + protected $mediaPath; - if ( !wfDl( 'exif' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; + $this->mediaPath = __DIR__ . '/../../data/media/'; + + + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testGPSExtraction() { $filename = $this->mediaPath . 'exif-gps.jpg'; - $seg = JpegMetadataExtractor::segmentSplitter( $filename ); + $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); $data = $exif->getFilteredData(); $expected = array( @@ -34,7 +34,7 @@ class ExifTest extends MediaWikiTestCase { public function testUnicodeUserComment() { $filename = $this->mediaPath . 'exif-user-comment.jpg'; - $seg = JpegMetadataExtractor::segmentSplitter( $filename ); + $seg = JpegMetadataExtractor::segmentSplitter( $filename ); $exif = new Exif( $filename, $seg['byteOrder'] ); $data = $exif->getFilteredData(); @@ -43,6 +43,4 @@ class ExifTest extends MediaWikiTestCase { ); $this->assertEquals( $expected, $data ); } - - } diff --git a/tests/phpunit/includes/media/FakeDimensionFile.php b/tests/phpunit/includes/media/FakeDimensionFile.php new file mode 100644 index 00000000..7926000b --- /dev/null +++ b/tests/phpunit/includes/media/FakeDimensionFile.php @@ -0,0 +1,28 @@ +<?php + +class FakeDimensionFile extends File { + public $mustRender = false; + + public function __construct( $dimensions ) { + parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), + new NullRepo( null ) ); + + $this->dimensions = $dimensions; + } + + public function getWidth( $page = 1 ) { + return $this->dimensions[0]; + } + + public function getHeight( $page = 1 ) { + return $this->dimensions[1]; + } + + public function mustRender() { + return $this->mustRender; + } + + public function getPath() { + return ''; + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php index 6ade6702..a073e4ca 100644 --- a/tests/phpunit/includes/media/FormatMetadataTest.php +++ b/tests/phpunit/includes/media/FormatMetadataTest.php @@ -1,36 +1,43 @@ <?php + +/** + * @todo covers tags + */ class FormatMetadataTest extends MediaWikiTestCase { - public function setUp() { - if ( !wfDl( 'exif' ) ) { + + /** @var FSFileBackend */ + protected $backend; + /** @var FSRepo */ + protected $repo; + + protected function setUp() { + parent::setUp(); + + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - $filePath = __DIR__ . '/../../data/media'; + $filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->show; + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testInvalidDate() { $file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' ); - + // Throws an error if bug hit $meta = $file->formatMetadata(); $this->assertNotEquals( false, $meta, 'Valid metadata extracted' ); - + // Find date exif entry $this->assertArrayHasKey( 'visible', $meta ); $dateIndex = null; @@ -40,7 +47,7 @@ class FormatMetadataTest extends MediaWikiTestCase { } } $this->assertNotNull( $dateIndex, 'Date entry exists in metadata' ); - $this->assertEquals( '0000:01:00 00:02:27', + $this->assertEquals( '0000:01:00 00:02:27', $meta['visible'][$dateIndex]['value'], 'File with invalid date metadata (bug 29471)' ); } diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php index 650fdd5c..9e3f9244 100644 --- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php @@ -1,20 +1,25 @@ <?php class GIFMetadataExtractorTest extends MediaWikiTestCase { - public function setUp() { + protected function setUp() { + parent::setUp(); + $this->mediaPath = __DIR__ . '/../../data/media/'; } + /** * Put in a file, and see if the metadata coming out is as expected. * @param $filename String * @param $expected Array The extracted metadata. - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers GIFMetadataExtractor::getMetadata */ public function testGetMetadata( $filename, $expected ) { $actual = GIFMetadataExtractor::getMetadata( $this->mediaPath . $filename ); $this->assertEquals( $expected, $actual ); } - public function dataGetMetadata() { + + public static function provideGetMetadata() { $xmpNugget = <<<EOF <?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> @@ -66,29 +71,35 @@ EOF; $xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat return array( - array( 'nonanimated.gif', array( - 'comment' => array( 'GIF test file ⁕ Created with GIMP' ), - 'duration' => 0.1, - 'frameCount' => 1, - 'looped' => false, - 'xmp' => '', + array( + 'nonanimated.gif', + array( + 'comment' => array( 'GIF test file ⁕ Created with GIMP' ), + 'duration' => 0.1, + 'frameCount' => 1, + 'looped' => false, + 'xmp' => '', ) ), - array( 'animated.gif', array( - 'comment' => array( 'GIF test file . Created with GIMP' ), - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'xmp' => '', + array( + 'animated.gif', + array( + 'comment' => array( 'GIF test file . Created with GIMP' ), + 'duration' => 2.4, + 'frameCount' => 4, + 'looped' => true, + 'xmp' => '', ) ), - array( 'animated-xmp.gif', array( - 'xmp' => $xmpNugget, - 'duration' => 2.4, - 'frameCount' => 4, - 'looped' => true, - 'comment' => array( 'GIƒ·test·file' ), + array( + 'animated-xmp.gif', + array( + 'xmp' => $xmpNugget, + 'duration' => 2.4, + 'frameCount' => 4, + 'looped' => true, + 'comment' => array( 'GIƒ·test·file' ), ) ), ); diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php index 5dcbeee0..c8e729c8 100644 --- a/tests/phpunit/includes/media/GIFTest.php +++ b/tests/phpunit/includes/media/GIFTest.php @@ -1,36 +1,53 @@ <?php class GIFHandlerTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media'; + /** @var FSFileBackend */ + protected $backend; + /** @var GIFHandler */ + protected $handler; + /** @var FSRepo */ + protected $repo; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $this->filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); $this->handler = new GIFHandler(); } + /** + * @covers GIFHandler::getMetadata + */ public function testInvalidFile() { $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( GIFHandler::BROKEN_FILE, $res ); } + /** * @param $filename String basename of the file to check * @param $expected boolean Expected result. - * @dataProvider dataIsAnimated + * @dataProvider provideIsAnimated + * @covers GIFHandler::isAnimatedImage */ public function testIsAnimanted( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } - public function dataIsAnimated() { + + public static function provideIsAnimated() { return array( array( 'animated.gif', true ), array( 'nonanimated.gif', false ), @@ -40,14 +57,16 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected Integer Total image area - * @dataProvider dataGetImageArea + * @dataProvider provideGetImageArea + * @covers GIFHandler::getImageArea */ public function testGetImageArea( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } - public function dataGetImageArea() { + + public static function provideGetImageArea() { return array( array( 'animated.gif', 5400 ), array( 'nonanimated.gif', 1350 ), @@ -57,13 +76,15 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $metadata String Serialized metadata * @param $expected Integer One of the class constants of GIFHandler - * @dataProvider dataIsMetadataValid + * @dataProvider provideIsMetadataValid + * @covers GIFHandler::isMetadataValid */ public function testIsMetadataValid( $metadata, $expected ) { $actual = $this->handler->isMetadataValid( null, $metadata ); $this->assertEquals( $expected, $actual ); } - public function dataIsMetadataValid() { + + public static function provideIsMetadataValid() { return array( array( GIFHandler::BROKEN_FILE, GIFHandler::METADATA_GOOD ), array( '', GIFHandler::METADATA_BAD ), @@ -76,7 +97,8 @@ class GIFHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected String Serialized array - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers GIFHandler::getMetadata */ public function testGetMetadata( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/gif' ); @@ -84,7 +106,7 @@ class GIFHandlerTest extends MediaWikiTestCase { $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); } - public function dataGetMetadata() { + public static function provideGetMetadata() { return array( array( 'nonanimated.gif', 'a:4:{s:10:"frameCount";i:1;s:6:"looped";b:0;s:8:"duration";d:0.1000000000000000055511151231257827021181583404541015625;s:8:"metadata";a:2:{s:14:"GIFFileComment";a:1:{i:0;s:35:"GIF test file ⁕ Created with GIMP";}s:15:"_MW_GIF_VERSION";i:1;}}' ), array( 'animated-xmp.gif', 'a:4:{s:10:"frameCount";i:4;s:6:"looped";b:1;s:8:"duration";d:2.399999999999999911182158029987476766109466552734375;s:8:"metadata";a:5:{s:6:"Artist";s:7:"Bawolff";s:16:"ImageDescription";a:2:{s:9:"x-default";s:18:"A file to test GIF";s:5:"_type";s:4:"lang";}s:15:"SublocationDest";s:13:"The interwebs";s:14:"GIFFileComment";a:1:{i:0;s:16:"GIƒ·test·file";}s:15:"_MW_GIF_VERSION";i:1;}}' ), diff --git a/tests/phpunit/includes/media/IPTCTest.php b/tests/phpunit/includes/media/IPTCTest.php index ec6deeb8..81c1d287 100644 --- a/tests/phpunit/includes/media/IPTCTest.php +++ b/tests/phpunit/includes/media/IPTCTest.php @@ -1,11 +1,19 @@ <?php + class IPTCTest extends MediaWikiTestCase { + + /** + * @covers IPTC::getCharset + */ public function testRecognizeUtf8() { // utf-8 is the only one used in practise. $res = IPTC::getCharset( "\x1b%G" ); $this->assertEquals( 'UTF-8', $res ); } + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharset88591() { // basically IPTC for keyword with value of 0xBC which is 1/4 in iso-8859-1 // This data doesn't specify a charset. We're supposed to guess @@ -14,16 +22,23 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ - /* \xC3 = Ã, \xB8 = ¸ */ + + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharset88591b() { + /* This one contains a sequence that's valid iso 8859-1 but not valid utf8 */ + /* \xC3 = Ã, \xB8 = ¸ */ $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x09\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( 'ÃÃø' ), $res['Keywords'] ); } - /* Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. + + /** + * Same as testIPTCParseNoCharset88591b, but forcing the charset to utf-8. * What should happen is the first "\xC3\xC3" should be dropped as invalid, * leaving \xC3\xB8, which is ø + * @covers IPTC::Parse */ public function testIPTCParseForcedUTFButInvalid() { $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x11\x1c\x02\x19\x00\x04\xC3\xC3\xC3\xB8" @@ -31,12 +46,20 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( 'ø' ), $res['Keywords'] ); } + + /** + * @covers IPTC::Parse + */ public function testIPTCParseNoCharsetUTF8() { $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x07\x1c\x02\x19\x00\x02¼"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - // Testing something that has 2 values for keyword + + /** + * Testing something that has 2 values for keyword + * @covers IPTC::Parse + */ public function testIPTCParseMulti() { $iptcData = /* identifier */ "Photoshop 3.0\08BIM\4\4" /* length */ . "\0\0\0\0\0\x0D" @@ -45,11 +68,14 @@ class IPTCTest extends MediaWikiTestCase { $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼', '¼½' ), $res['Keywords'] ); } + + /** + * @covers IPTC::Parse + */ public function testIPTCParseUTF8() { // This has the magic "\x1c\x01\x5A\x00\x03\x1B\x25\x47" which marks content as UTF8. $iptcData = "Photoshop 3.0\08BIM\4\4\0\0\0\0\0\x0F\x1c\x02\x19\x00\x02¼\x1c\x01\x5A\x00\x03\x1B\x25\x47"; $res = IPTC::Parse( $iptcData ); $this->assertEquals( array( '¼' ), $res['Keywords'] ); } - } diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php index 41d81190..eafc8a2e 100644 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php @@ -5,10 +5,15 @@ * serve as a very good "test". (Adobe photoshop probably creates such files * but it costs money). The implementation of it currently in MediaWiki is based * solely on reading the standard, without any real world test files. + * @todo covers tags */ class JpegMetadataExtractorTest extends MediaWikiTestCase { - public function setUp() { + protected $filePath; + + protected function setUp() { + parent::setUp(); + $this->filePath = __DIR__ . '/../../data/media/'; } @@ -16,26 +21,29 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { * We also use this test to test padding bytes don't * screw stuff up * - * @param $file filename + * @param string $file filename * - * @dataProvider dataUtf8Comment + * @dataProvider provideUtf8Comment */ public function testUtf8Comment( $file ) { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . $file ); $this->assertEquals( array( 'UTF-8 JPEG Comment — ¼' ), $res['COM'] ); } - public function dataUtf8Comment() { + + public static function provideUtf8Comment() { return array( array( 'jpeg-comment-utf.jpg' ), array( 'jpeg-padding-even.jpg' ), array( 'jpeg-padding-odd.jpg' ), ); } + /** The file is iso-8859-1, but it should get auto converted */ public function testIso88591Comment() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-iso8859-1.jpg' ); $this->assertEquals( array( 'ISO-8859-1 JPEG Comment - ¼' ), $res['COM'] ); } + /** Comment values that are non-textual (random binary junk) should not be shown. * The example test file has a comment with a 0x5 byte in it which is a control character * and considered binary junk for our purposes. @@ -44,6 +52,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-binary.jpg' ); $this->assertEmpty( $res['COM'] ); } + /* Very rarely a file can have multiple comments. * Order of comments is based on order inside the file. */ @@ -51,16 +60,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-comment-multiple.jpg' ); $this->assertEquals( array( 'foo', 'bar' ), $res['COM'] ); } + public function testXMPExtraction() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' ); $this->assertEquals( $expected, $res['XMP'] ); } + public function testPSIRExtraction() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' ); $expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004'; $this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) ); } + public function testXMPExtractionAltAppId() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' ); $expected = file_get_contents( $this->filePath . 'jpeg-xmp-psir.xmp' ); @@ -74,18 +86,21 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( 'iptc-no-hash', $res ); } + public function testIPTCHashComparisionBadHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' ); $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-bad-hash', $res ); } + public function testIPTCHashComparisionGoodHash() { $segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-good-hash.jpg' ); $res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] ); $this->assertEquals( 'iptc-good-hash', $res ); } + public function testExifByteOrder() { $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'exif-user-comment.jpg' ); $expected = 'BE'; diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php index ea007f90..9af4f1e1 100644 --- a/tests/phpunit/includes/media/JpegTest.php +++ b/tests/phpunit/includes/media/JpegTest.php @@ -1,18 +1,19 @@ <?php +/** + * @todo covers tags + */ class JpegTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media/'; - if ( !wfDl( 'exif' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { $this->markTestSkipped( "This test needs the exif extension." ); } - global $wgShowEXIF; - $this->show = $wgShowEXIF; - $wgShowEXIF = true; - } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->show; + + $this->filePath = __DIR__ . '/../../data/media/'; + + + $this->setMwGlobals( 'wgShowEXIF', true ); } public function testInvalidFile() { @@ -20,6 +21,7 @@ class JpegTest extends MediaWikiTestCase { $res = $jpeg->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } + public function testJpegMetadataExtraction() { $h = new JpegHandler; $res = $h->getMetadata( null, $this->filePath . 'test.jpg' ); diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php index 99df4f80..c28898bb 100644 --- a/tests/phpunit/includes/media/MediaHandlerTest.php +++ b/tests/phpunit/includes/media/MediaHandlerTest.php @@ -1,7 +1,12 @@ <?php class MediaHandlerTest extends MediaWikiTestCase { - function testFitBoxWidth() { + + /** + * @covers MediaHandler::fitBoxWidth + * @todo split into a dataprovider and test method + */ + public function testFitBoxWidth() { $vals = array( array( 'width' => 50, @@ -46,5 +51,3 @@ class MediaHandlerTest extends MediaWikiTestCase { } } } - - diff --git a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php index 1b1b2ec3..939f2cfc 100644 --- a/tests/phpunit/includes/media/PNGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/PNGMetadataExtractorTest.php @@ -1,13 +1,21 @@ <?php + +/** + * @todo covers tags + */ class PNGMetadataExtractorTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); $this->filePath = __DIR__ . '/../../data/media/'; } + /** - * Tests zTXt tag (compressed textual metadata) + * Tests zTXt tag (compressed textual metadata) */ - function testPngNativetZtxt() { + public function testPngNativetZtxt() { + $this->checkPHPExtension( 'zlib' ); + $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $expected = "foo bar baz foo foo foo foof foo foo foo foo"; @@ -22,7 +30,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { /** * Test tEXt tag (Uncompressed textual metadata) */ - function testPngNativeText() { + public function testPngNativeText() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $expected = "Some long image desc"; @@ -39,7 +47,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { * tEXt tags must be encoded iso-8859-1 (vs iTXt which are utf-8) * Make sure non-ascii characters get converted properly */ - function testPngNativeTextNonAscii() { + public function testPngNativeTextNonAscii() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -48,7 +56,6 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { // encoded as just \xA9. $expected = "© 2010 Bawolff"; - $this->assertArrayHasKey( 'text', $meta ); $meta = $meta['text']; $this->assertArrayHasKey( 'Copyright', $meta ); @@ -60,7 +67,9 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { /** * Test extraction of pHYs tags, which can tell what the * actual resolution of the image is (aka in dots per meter). - function testPngPhysTag () { + */ + /* + public function testPngPhysTag() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -71,11 +80,12 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( '2835/100', $meta['YResolution'] ); $this->assertEquals( 3, $meta['ResolutionUnit'] ); // 3 = cm } + */ /** * Given a normal static PNG, check the animation metadata returned. */ - function testStaticPngAnimationMetadata() { + public function testStaticPngAnimationMetadata() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); @@ -88,7 +98,7 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { * Given an animated APNG image file * check it gets animated metadata right. */ - function testApngAnimationMetadata() { + public function testApngAnimationMetadata() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Animated_PNG_example_bouncing_beach_ball.png' ); @@ -98,44 +108,48 @@ class PNGMetadataExtractorTest extends MediaWikiTestCase { $this->assertEquals( 1.5, $meta['duration'], '', 0.00001 ); } - function testPngBitDepth8() { + public function testPngBitDepth8() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $this->assertEquals( 8, $meta['bitDepth'] ); } - function testPngBitDepth1() { + + public function testPngBitDepth1() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . '1bit-png.png' ); $this->assertEquals( 1, $meta['bitDepth'] ); } - function testPngIndexColour() { + public function testPngIndexColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'Png-native-test.png' ); $this->assertEquals( 'index-coloured', $meta['colorType'] ); } - function testPngRgbColour() { + + public function testPngRgbColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'rgb-png.png' ); $this->assertEquals( 'truecolour-alpha', $meta['colorType'] ); } - function testPngRgbNoAlphaColour() { + + public function testPngRgbNoAlphaColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'rgb-na-png.png' ); $this->assertEquals( 'truecolour', $meta['colorType'] ); } - function testPngGreyscaleColour() { + + public function testPngGreyscaleColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'greyscale-png.png' ); $this->assertEquals( 'greyscale-alpha', $meta['colorType'] ); } - function testPngGreyscaleNoAlphaColour() { + + public function testPngGreyscaleNoAlphaColour() { $meta = PNGMetadataExtractor::getMetadata( $this->filePath . 'greyscale-na-png.png' ); $this->assertEquals( 'greyscale', $meta['colorType'] ); } - } diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php index fe73c9c7..ad4c2493 100644 --- a/tests/phpunit/includes/media/PNGTest.php +++ b/tests/phpunit/includes/media/PNGTest.php @@ -1,36 +1,53 @@ <?php class PNGHandlerTest extends MediaWikiTestCase { - public function setUp() { - $this->filePath = __DIR__ . '/../../data/media'; + /** @var PNGHandler */ + protected $handler; + /** @var FSRepo */ + protected $repo; + /** @var FSFileBackend */ + protected $backend; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + + $this->filePath = __DIR__ . '/../../data/media'; $this->backend = new FSFileBackend( array( - 'name' => 'localtesting', - 'lockManager' => 'nullLockManager', + 'name' => 'localtesting', + 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'data' => $this->filePath ) ) ); $this->repo = new FSRepo( array( - 'name' => 'temp', - 'url' => 'http://localhost/thumbtest', + 'name' => 'temp', + 'url' => 'http://localhost/thumbtest', 'backend' => $this->backend ) ); $this->handler = new PNGHandler(); } + /** + * @covers PNGHandler::getMetadata + */ public function testInvalidFile() { $res = $this->handler->getMetadata( null, $this->filePath . '/README' ); $this->assertEquals( PNGHandler::BROKEN_FILE, $res ); } + /** * @param $filename String basename of the file to check * @param $expected boolean Expected result. - * @dataProvider dataIsAnimated + * @dataProvider provideIsAnimated + * @covers PNGHandler::isAnimatedImage */ public function testIsAnimanted( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/png' ); $actual = $this->handler->isAnimatedImage( $file ); $this->assertEquals( $expected, $actual ); } - public function dataIsAnimated() { + + public static function provideIsAnimated() { return array( array( 'Animated_PNG_example_bouncing_beach_ball.png', true ), array( '1bit-png.png', false ), @@ -40,14 +57,16 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected Integer Total image area - * @dataProvider dataGetImageArea + * @dataProvider provideGetImageArea + * @covers PNGHandler::getImageArea */ public function testGetImageArea( $filename, $expected ) { - $file = $this->dataFile($filename, 'image/png' ); + $file = $this->dataFile( $filename, 'image/png' ); $actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() ); $this->assertEquals( $expected, $actual ); } - public function dataGetImageArea() { + + public static function provideGetImageArea() { return array( array( '1bit-png.png', 2500 ), array( 'greyscale-png.png', 2500 ), @@ -59,13 +78,15 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $metadata String Serialized metadata * @param $expected Integer One of the class constants of PNGHandler - * @dataProvider dataIsMetadataValid + * @dataProvider provideIsMetadataValid + * @covers PNGHandler::isMetadataValid */ public function testIsMetadataValid( $metadata, $expected ) { $actual = $this->handler->isMetadataValid( null, $metadata ); $this->assertEquals( $expected, $actual ); } - public function dataIsMetadataValid() { + + public static function provideIsMetadataValid() { return array( array( PNGHandler::BROKEN_FILE, PNGHandler::METADATA_GOOD ), array( '', PNGHandler::METADATA_BAD ), @@ -78,7 +99,8 @@ class PNGHandlerTest extends MediaWikiTestCase { /** * @param $filename String * @param $expected String Serialized array - * @dataProvider dataGetMetadata + * @dataProvider provideGetMetadata + * @covers PNGHandler::getMetadata */ public function testGetMetadata( $filename, $expected ) { $file = $this->dataFile( $filename, 'image/png' ); @@ -86,10 +108,11 @@ class PNGHandlerTest extends MediaWikiTestCase { // $this->assertEquals( unserialize( $expected ), unserialize( $actual ) ); $this->assertEquals( ( $expected ), ( $actual ) ); } - public function dataGetMetadata() { + + public static function provideGetMetadata() { return array( array( 'rgb-na-png.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:8;s:9:"colorType";s:10:"truecolour";s:8:"metadata";a:1:{s:15:"_MW_PNG_VERSION";i:1;}}' ), - array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), + array( 'xmp.png', 'a:6:{s:10:"frameCount";i:0;s:9:"loopCount";i:1;s:8:"duration";d:0;s:8:"bitDepth";i:1;s:9:"colorType";s:14:"index-coloured";s:8:"metadata";a:2:{s:12:"SerialNumber";s:9:"123456789";s:15:"_MW_PNG_VERSION";i:1;}}' ), ); } diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php index 2116554e..257009b0 100644 --- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php @@ -1,25 +1,30 @@ <?php +/** + * @todo covers tags + */ class SVGMetadataExtractorTest extends MediaWikiTestCase { - function setUp() { + protected function setUp() { + parent::setUp(); AutoLoader::loadClass( 'SVGMetadataExtractorTest' ); } /** - * @dataProvider providerSvgFiles + * @dataProvider provideSvgFiles */ - function testGetMetadata( $infile, $expected ) { + public function testGetMetadata( $infile, $expected ) { $this->assertMetadata( $infile, $expected ); } - + /** - * @dataProvider providerSvgFilesWithXMLMetadata + * @dataProvider provideSvgFilesWithXMLMetadata */ - function testGetXMLMetadata( $infile, $expected ) { + public function testGetXMLMetadata( $infile, $expected ) { $r = new XMLReader(); - if( !method_exists( $r, 'readInnerXML' ) ) { + if ( !method_exists( $r, 'readInnerXML' ) ) { $this->markTestSkipped( 'XMLReader::readInnerXML() does not exist (libxml >2.6.20 needed).' ); + return; } $this->assertMetadata( $infile, $expected ); @@ -38,8 +43,9 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { } } - function providerSvgFiles() { + public static function provideSvgFiles() { $base = __DIR__ . '/../../data/media'; + return array( array( "$base/Wikimedia-logo.svg", @@ -81,10 +87,9 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { ); } - function providerSvgFilesWithXMLMetadata() { + public static function provideSvgFilesWithXMLMetadata() { $base = __DIR__ . '/../../data/media'; - $metadata = - '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + $metadata = '<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <ns4:Work xmlns:ns4="http://creativecommons.org/ns#" rdf:about=""> <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format> <ns5:type xmlns:ns5="http://purl.org/dc/elements/1.1/" rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> @@ -105,4 +110,3 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase { ); } } - diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php index 4c79f66c..8d74b98d 100644 --- a/tests/phpunit/includes/media/TiffTest.php +++ b/tests/phpunit/includes/media/TiffTest.php @@ -1,31 +1,35 @@ <?php class TiffTest extends MediaWikiTestCase { - public function setUp() { - global $wgShowEXIF; - $this->showExif = $wgShowEXIF; - $wgShowEXIF = true; + /** @var TiffHandler */ + protected $handler; + /** @var string */ + protected $filePath; + + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'exif' ) ) { + $this->markTestSkipped( "This test needs the exif extension." ); + } + + $this->setMwGlobals( 'wgShowEXIF', true ); + $this->filePath = __DIR__ . '/../../data/media/'; $this->handler = new TiffHandler; } - public function tearDown() { - global $wgShowEXIF; - $wgShowEXIF = $this->showExif; - } - + /** + * @covers TiffHandler::getMetadata + */ public function testInvalidFile() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->getMetadata( null, $this->filePath . 'README' ); $this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res ); } + /** + * @covers TiffHandler::getMetadata + */ public function testTiffMetadataExtraction() { - if ( !wfDl( 'exif' ) ) { - $this->markTestIncomplete( "This test needs the exif extension." ); - } $res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' ); $expected = 'a:16:{s:10:"ImageWidth";i:20;s:11:"ImageLength";i:20;s:13:"BitsPerSample";a:3:{i:0;i:8;i:1;i:8;i:2;i:8;}s:11:"Compression";i:5;s:25:"PhotometricInterpretation";i:2;s:16:"ImageDescription";s:17:"Created with GIMP";s:12:"StripOffsets";i:8;s:11:"Orientation";i:1;s:15:"SamplesPerPixel";i:3;s:12:"RowsPerStrip";i:64;s:15:"StripByteCounts";i:238;s:11:"XResolution";s:19:"1207959552/16777216";s:11:"YResolution";s:19:"1207959552/16777216";s:19:"PlanarConfiguration";i:1;s:14:"ResolutionUnit";i:2;s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}'; // Re-unserialize in case there are subtle differences between how versions diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php index 8198d3b0..d12e9b00 100644 --- a/tests/phpunit/includes/media/XMPTest.php +++ b/tests/phpunit/includes/media/XMPTest.php @@ -1,8 +1,13 @@ <?php + +/** + * @todo covers tags + */ class XMPTest extends MediaWikiTestCase { - function setUp() { - if ( !wfDl( 'xml' ) ) { + protected function setUp() { + parent::setUp(); + if ( !extension_loaded( 'xml' ) ) { $this->markTestSkipped( 'Requires libxml to do XMP parsing' ); } } @@ -14,7 +19,8 @@ class XMPTest extends MediaWikiTestCase { * @param $expected Array expected result of parsing the xmp. * @param $info String Short sentence on what's being tested. * - * @dataProvider dataXMPParse + * @throws Exception + * @dataProvider provideXMPParse */ public function testXMPParse( $xmp, $expected, $info ) { if ( !is_string( $xmp ) || !is_array( $expected ) ) { @@ -25,8 +31,8 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $reader->getResults(), $info, 0.0000000001 ); } - public function dataXMPParse() { - $xmpPath = __DIR__ . '/../../data/xmp/' ; + public static function provideXMPParse() { + $xmpPath = __DIR__ . '/../../data/xmp/'; $data = array(); // $xmpFiles format: array of arrays with first arg file base name, @@ -53,16 +59,18 @@ class XMPTest extends MediaWikiTestCase { array( 'utf32LE', 'UTF-32LE encoding' ), array( 'xmpExt', 'Extended XMP missing second part' ), array( 'gps', 'Handling of exif GPS parameters in XMP' ), - ); - foreach( $xmpFiles as $file ) { + ); + + foreach ( $xmpFiles as $file ) { $xmp = file_get_contents( $xmpPath . $file[0] . '.xmp' ); // I'm not sure if this is the best way to handle getting the // result array, but it seems kind of big to put directly in the test // file. $result = null; - include( $xmpPath . $file[0] . '.result.php' ); + include $xmpPath . $file[0] . '.result.php'; $data[] = array( $xmp, $result, '[' . $file[0] . '.xmp] ' . $file[1] ); } + return $data; } @@ -72,7 +80,7 @@ class XMPTest extends MediaWikiTestCase { * @todo This is based on what the standard says. Need to find a real * world example file to double check the support for this is right. */ - function testExtendedXMP() { + public function testExtendedXMP() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -87,8 +95,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, 'FNumber' => '2/10', @@ -102,7 +110,7 @@ class XMPTest extends MediaWikiTestCase { * This test has an extended XMP block with a wrong guid (md5sum) * and thus should only return the StandardXMP, not the ExtendedXMP. */ - function testExtendedXMPWithWrongGUID() { + public function testExtendedXMPWithWrongGUID() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -117,8 +125,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, ) @@ -126,11 +134,12 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } + /** * Have a high offset to simulate a missing packet, * which should cause it to ignore the ExtendedXMP packet. */ - function testExtendedXMPMissingPacket() { + public function testExtendedXMPMissingPacket() { $xmpPath = __DIR__ . '/../../data/xmp/'; $standardXMP = file_get_contents( $xmpPath . 'xmpExt.xmp' ); $extendedXMP = file_get_contents( $xmpPath . 'xmpExt2.xmp' ); @@ -145,8 +154,8 @@ class XMPTest extends MediaWikiTestCase { $reader->parseExtended( $extendedPacket ); $actual = $reader->getResults(); - $expected = array( 'xmp-exif' => - array( + $expected = array( + 'xmp-exif' => array( 'DigitalZoomRatio' => '0/10', 'Flash' => 9, ) @@ -154,5 +163,4 @@ class XMPTest extends MediaWikiTestCase { $this->assertEquals( $expected, $actual ); } - } diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php index e2bb8d8d..96bf5e47 100644 --- a/tests/phpunit/includes/media/XMPValidateTest.php +++ b/tests/phpunit/includes/media/XMPValidateTest.php @@ -2,15 +2,16 @@ class XMPValidateTest extends MediaWikiTestCase { /** - * @dataProvider providerDate + * @dataProvider provideDates + * @covers XMPValidate::validateDate */ - function testValidateDate( $value, $expected ) { + public function testValidateDate( $value, $expected ) { // The method should modify $value. XMPValidate::validateDate( array(), $value, true ); $this->assertEquals( $expected, $value ); } - function providerDate() { + public static function provideDates() { /* For reference valid date formats are: * YYYY * YYYY-MM @@ -41,7 +42,5 @@ class XMPValidateTest extends MediaWikiTestCase { array( '2001-05-12T15', null ), array( '2001-12T15:13', null ), ); - } - } diff --git a/tests/phpunit/includes/mobile/DeviceDetectionTest.php b/tests/phpunit/includes/mobile/DeviceDetectionTest.php deleted file mode 100644 index 0e156532..00000000 --- a/tests/phpunit/includes/mobile/DeviceDetectionTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -/** - * @group Mobile - */ - class DeviceDetectionTest extends MediaWikiTestCase { - - /** - * @dataProvider provideTestFormatName - */ - public function testFormatName( $format, $userAgent ) { - $detector = new DeviceDetection(); - $this->assertEquals( $format, $detector->detectFormatName( $userAgent ) ); - } - - public function provideTestFormatName() { - return array( - array( 'android', 'Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17' ), - array( 'iphone2', 'Mozilla/5.0 (ipod: U;CPU iPhone OS 2_2 like Mac OS X: es_es) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), - array( 'iphone', 'Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3B48b Safari/419.3' ), - array( 'nokia', 'Mozilla/5.0 (SymbianOS/9.1; U; [en]; SymbianOS/91 Series60/3.0) AppleWebKit/413 (KHTML, like Gecko) Safari/413' ), - array( 'palm_pre', 'Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0' ), - array( 'wii', 'Opera/9.00 (Nintendo Wii; U; ; 1309-9; en)' ), - array( 'operamini', 'Opera/9.50 (J2ME/MIDP; Opera Mini/4.0.10031/298; U; en)' ), - array( 'operamobile', 'Opera/9.51 Beta (Microsoft Windows; PPC; Opera Mobi/1718; U; en)' ), - array( 'kindle', 'Mozilla/4.0 (compatible; Linux 2.6.10) NetFront/3.3 Kindle/1.0 (screen 600x800)' ), - array( 'kindle2', 'Mozilla/4.0 (compatible; Linux 2.6.22) NetFront/3.4 Kindle/2.0 (screen 824x1200; rotate)' ), - array( 'capable', 'Mozilla/5.0 (X11; Linux i686; rv:2.0.1) Gecko/20100101 Firefox/4.0.1' ), - array( 'netfront', 'Mozilla/4.08 (Windows; Mobile Content Viewer/1.0) NetFront/3.2' ), - array( 'wap2', 'SonyEricssonK608i/R2L/SN356841000828910 Browser/SEMC-Browser/4.2 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - array( 'wap2', 'NokiaN73-2/3.0-630.0.2 Series60/3.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - array( 'psp', 'Mozilla/4.0 (PSP (PlayStation Portable); 2.00)' ), - array( 'ps3', 'Mozilla/5.0 (PLAYSTATION 3; 1.00)' ), - array( 'ie', 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)' ), - array( 'ie', 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0)' ), - array( 'blackberry', 'BlackBerry9300/5.0.0.716 Profile/MIDP-2.1 Configuration/CLDC-1.1 VendorID/133' ), - array( 'blackberry-lt5', 'BlackBerry7250/4.0.0 Profile/MIDP-2.0 Configuration/CLDC-1.1' ), - ); - } -} diff --git a/tests/phpunit/includes/normal/CleanUpTest.php b/tests/phpunit/includes/normal/CleanUpTest.php index d5ad18d8..52dd2ef5 100644 --- a/tests/phpunit/includes/normal/CleanUpTest.php +++ b/tests/phpunit/includes/normal/CleanUpTest.php @@ -29,16 +29,21 @@ * Requires PHPUnit. * * @ingroup UtfNormal + * @group Large + * + * We ignore code coverage for this test suite until they are rewritten + * to use data providers (bug 46561). + * @codeCoverageIgnore */ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ - function testAscii() { + public function testAscii() { $text = 'This is plain ASCII text.'; $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); } /** @todo document */ - function testNull() { + public function testNull() { $text = "a \x00 null"; $expect = "a \xef\xbf\xbd null"; $this->assertEquals( @@ -47,13 +52,13 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testLatin() { + public function testLatin() { $text = "L'\xc3\xa9cole"; $this->assertEquals( $text, UtfNormal::cleanUp( $text ) ); } /** @todo document */ - function testLatinNormal() { + public function testLatinNormal() { $text = "L'e\xcc\x81cole"; $expect = "L'\xc3\xa9cole"; $this->assertEquals( $expect, UtfNormal::cleanUp( $text ) ); @@ -65,19 +70,24 @@ class CleanUpTest extends MediaWikiTestCase { */ function XtestAllChars() { $rep = UTF8_REPLACEMENT; - for( $i = 0x0; $i < UNICODE_MAX; $i++ ) { + for ( $i = 0x0; $i < UNICODE_MAX; $i++ ) { $char = codepointToUtf8( $i ); $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%04X", $i ); - if( $i % 0x1000 == 0 ) echo "U+$x\n"; - if( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ($i > 0x001f && $i < UNICODE_SURROGATE_FIRST) || - ($i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) || - ($i > 0xffff && $i <= UNICODE_MAX ) ) { - if( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) { - $comp = UtfNormal::NFC( $char ); + + if ( $i % 0x1000 == 0 ) { + echo "U+$x\n"; + } + + if ( $i == 0x0009 || + $i == 0x000a || + $i == 0x000d || + ( $i > 0x001f && $i < UNICODE_SURROGATE_FIRST ) || + ( $i > UNICODE_SURROGATE_LAST && $i < 0xfffe ) || + ( $i > 0xffff && $i <= UNICODE_MAX ) + ) { + if ( isset( UtfNormal::$utfCanonicalComp[$char] ) || isset( UtfNormal::$utfCanonicalDecomp[$char] ) ) { + $comp = UtfNormal::NFC( $char ); $this->assertEquals( bin2hex( $comp ), bin2hex( $clean ), @@ -95,7 +105,7 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testAllBytes() { + public function testAllBytes() { $this->doTestBytes( '', '' ); $this->doTestBytes( 'x', '' ); $this->doTestBytes( '', 'x' ); @@ -104,32 +114,38 @@ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ function doTestBytes( $head, $tail ) { - for( $i = 0x0; $i < 256; $i++ ) { + for ( $i = 0x0; $i < 256; $i++ ) { $char = $head . chr( $i ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X", $i ); - if( $i == 0x0009 || - $i == 0x000a || - $i == 0x000d || - ($i > 0x001f && $i < 0x80) ) { + + if ( $i == 0x0009 || + $i == 0x000a || + $i == 0x000d || + ( $i > 0x001f && $i < 0x80 ) + ) { $this->assertEquals( bin2hex( $char ), bin2hex( $clean ), "ASCII byte $x should be intact" ); - if( $char != $clean ) return; + if ( $char != $clean ) { + return; + } } else { $norm = $head . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden byte $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } } } /** @todo document */ - function testDoubleBytes() { + public function testDoubleBytes() { $this->doTestDoubleBytes( '', '' ); $this->doTestDoubleBytes( 'x', '' ); $this->doTestDoubleBytes( '', 'x' ); @@ -140,42 +156,49 @@ class CleanUpTest extends MediaWikiTestCase { * @todo document */ function doTestDoubleBytes( $head, $tail ) { - for( $first = 0xc0; $first < 0x100; $first+=2 ) { - for( $second = 0x80; $second < 0x100; $second+=2 ) { + for ( $first = 0xc0; $first < 0x100; $first += 2 ) { + for ( $second = 0x80; $second < 0x100; $second += 2 ) { $char = $head . chr( $first ) . chr( $second ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X,%02X", $first, $second ); - if( $first > 0xc1 && - $first < 0xe0 && - $second < 0xc0 ) { - $norm = UtfNormal::NFC( $char ); + if ( $first > 0xc1 && + $first < 0xe0 && + $second < 0xc0 + ) { + $norm = UtfNormal::NFC( $char ); $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Pair $x should be intact" ); - if( $norm != $clean ) return; - } elseif( $first > 0xfd || $second > 0xbf ) { + if ( $norm != $clean ) { + return; + } + } elseif ( $first > 0xfd || $second > 0xbf ) { # fe and ff are not legal head bytes -- expect two replacement chars $norm = $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden pair $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } else { $norm = $head . UTF8_REPLACEMENT . $tail; $this->assertEquals( bin2hex( $norm ), bin2hex( $clean ), "Forbidden pair $x should be rejected" ); - if( $norm != $clean ) return; + if ( $norm != $clean ) { + return; + } } } } } /** @todo document */ - function testTripleBytes() { + public function testTripleBytes() { $this->doTestTripleBytes( '', '' ); $this->doTestTripleBytes( 'x', '' ); $this->doTestTripleBytes( '', 'x' ); @@ -184,24 +207,27 @@ class CleanUpTest extends MediaWikiTestCase { /** @todo document */ function doTestTripleBytes( $head, $tail ) { - for( $first = 0xc0; $first < 0x100; $first+=2 ) { - for( $second = 0x80; $second < 0x100; $second+=2 ) { + for ( $first = 0xc0; $first < 0x100; $first += 2 ) { + for ( $second = 0x80; $second < 0x100; $second += 2 ) { #for( $third = 0x80; $third < 0x100; $third++ ) { - for( $third = 0x80; $third < 0x81; $third++ ) { + for ( $third = 0x80; $third < 0x81; $third++ ) { $char = $head . chr( $first ) . chr( $second ) . chr( $third ) . $tail; $clean = UtfNormal::cleanUp( $char ); $x = sprintf( "%02X,%02X,%02X", $first, $second, $third ); - if( $first >= 0xe0 && + + if ( $first >= 0xe0 && $first < 0xf0 && $second < 0xc0 && - $third < 0xc0 ) { - if( $first == 0xe0 && $second < 0xa0 ) { + $third < 0xc0 + ) { + if ( $first == 0xe0 && $second < 0xa0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Overlong triplet $x should be rejected" ); - } elseif( $first == 0xed && - ( chr( $first ) . chr( $second ) . chr( $third )) >= UTF8_SURROGATE_FIRST ) { + } elseif ( $first == 0xed && + ( chr( $first ) . chr( $second ) . chr( $third ) ) >= UTF8_SURROGATE_FIRST + ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), @@ -212,27 +238,28 @@ class CleanUpTest extends MediaWikiTestCase { bin2hex( $clean ), "Triplet $x should be intact" ); } - } elseif( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) { + } elseif ( $first > 0xc1 && $first < 0xe0 && $second < 0xc0 ) { $this->assertEquals( bin2hex( UtfNormal::NFC( $head . chr( $first ) . chr( $second ) ) . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Valid 2-byte $x + broken tail" ); - } elseif( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) { + } elseif ( $second > 0xc1 && $second < 0xe0 && $third < 0xc0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . UtfNormal::NFC( chr( $second ) . chr( $third ) . $tail ) ), bin2hex( $clean ), "Broken head + valid 2-byte $x" ); - } elseif( ( $first > 0xfd || $second > 0xfd ) && - ( ( $second > 0xbf && $third > 0xbf ) || - ( $second < 0xc0 && $third < 0xc0 ) || - ( $second > 0xfd ) || - ( $third > 0xfd ) ) ) { + } elseif ( ( $first > 0xfd || $second > 0xfd ) && + ( ( $second > 0xbf && $third > 0xbf ) || + ( $second < 0xc0 && $third < 0xc0 ) || + ( $second > 0xfd ) || + ( $third > 0xfd ) ) + ) { # fe and ff are not legal head bytes -- expect three replacement chars $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . UTF8_REPLACEMENT . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), "Forbidden triplet $x should be rejected" ); - } elseif( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) { + } elseif ( $first > 0xc2 && $second < 0xc0 && $third < 0xc0 ) { $this->assertEquals( bin2hex( $head . UTF8_REPLACEMENT . $tail ), bin2hex( $clean ), @@ -249,22 +276,22 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testChunkRegression() { + public function testChunkRegression() { # Check for regression against a chunking bug - $text = "\x46\x55\xb8" . - "\xdc\x96" . - "\xee" . - "\xe7" . - "\x44" . - "\xaa" . - "\x2f\x25"; + $text = "\x46\x55\xb8" . + "\xdc\x96" . + "\xee" . + "\xe7" . + "\x44" . + "\xaa" . + "\x2f\x25"; $expect = "\x46\x55\xef\xbf\xbd" . - "\xdc\x96" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x44" . - "\xef\xbf\xbd" . - "\x2f\x25"; + "\xdc\x96" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x44" . + "\xef\xbf\xbd" . + "\x2f\x25"; $this->assertEquals( bin2hex( $expect ), @@ -272,34 +299,34 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testInterposeRegression() { - $text = "\x4e\x30" . - "\xb1" . # bad tail - "\x3a" . - "\x92" . # bad tail - "\x62\x3a" . - "\x84" . # bad tail - "\x43" . - "\xc6" . # bad head - "\x3f" . - "\x92" . # bad tail - "\xad" . # bad tail - "\x7d" . - "\xd9\x95"; + public function testInterposeRegression() { + $text = "\x4e\x30" . + "\xb1" . # bad tail + "\x3a" . + "\x92" . # bad tail + "\x62\x3a" . + "\x84" . # bad tail + "\x43" . + "\xc6" . # bad head + "\x3f" . + "\x92" . # bad tail + "\xad" . # bad tail + "\x7d" . + "\xd9\x95"; $expect = "\x4e\x30" . - "\xef\xbf\xbd" . - "\x3a" . - "\xef\xbf\xbd" . - "\x62\x3a" . - "\xef\xbf\xbd" . - "\x43" . - "\xef\xbf\xbd" . - "\x3f" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x7d" . - "\xd9\x95"; + "\xef\xbf\xbd" . + "\x3a" . + "\xef\xbf\xbd" . + "\x62\x3a" . + "\xef\xbf\xbd" . + "\x43" . + "\xef\xbf\xbd" . + "\x3f" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x7d" . + "\xd9\x95"; $this->assertEquals( bin2hex( $expect ), @@ -307,63 +334,63 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testOverlongRegression() { - $text = "\x67" . - "\x1a" . # forbidden ascii - "\xea" . # bad head - "\xc1\xa6" . # overlong sequence - "\xad" . # bad tail - "\x1c" . # forbidden ascii - "\xb0" . # bad tail - "\x3c" . - "\x9e"; # bad tail + public function testOverlongRegression() { + $text = "\x67" . + "\x1a" . # forbidden ascii + "\xea" . # bad head + "\xc1\xa6" . # overlong sequence + "\xad" . # bad tail + "\x1c" . # forbidden ascii + "\xb0" . # bad tail + "\x3c" . + "\x9e"; # bad tail $expect = "\x67" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x3c" . - "\xef\xbf\xbd"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x3c" . + "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testSurrogateRegression() { - $text = "\xed\xb4\x96" . # surrogate 0xDD16 - "\x83" . # bad tail - "\xb4" . # bad tail - "\xac"; # bad head + public function testSurrogateRegression() { + $text = "\xed\xb4\x96" . # surrogate 0xDD16 + "\x83" . # bad tail + "\xb4" . # bad tail + "\xac"; # bad head $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testBomRegression() { - $text = "\xef\xbf\xbe" . # U+FFFE, illegal char - "\xb2" . # bad tail - "\xef" . # bad head - "\x59"; + public function testBomRegression() { + $text = "\xef\xbf\xbe" . # U+FFFE, illegal char + "\xb2" . # bad tail + "\xef" . # bad head + "\x59"; $expect = "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\xef\xbf\xbd" . - "\x59"; + "\xef\xbf\xbd" . + "\xef\xbf\xbd" . + "\x59"; $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); } /** @todo document */ - function testForbiddenRegression() { - $text = "\xef\xbf\xbf"; # U+FFFF, illegal char + public function testForbiddenRegression() { + $text = "\xef\xbf\xbf"; # U+FFFF, illegal char $expect = "\xef\xbf\xbd"; $this->assertEquals( bin2hex( $expect ), @@ -371,10 +398,10 @@ class CleanUpTest extends MediaWikiTestCase { } /** @todo document */ - function testHangulRegression() { + public function testHangulRegression() { $text = "\xed\x9c\xaf" . # Hangul char - "\xe1\x87\x81"; # followed by another final jamo - $expect = $text; # Should *not* change. + "\xe1\x87\x81"; # followed by another final jamo + $expect = $text; # Should *not* change. $this->assertEquals( bin2hex( $expect ), bin2hex( UtfNormal::cleanUp( $text ) ) ); diff --git a/tests/phpunit/includes/objectcache/BagOStuffTest.php b/tests/phpunit/includes/objectcache/BagOStuffTest.php new file mode 100644 index 00000000..aa783943 --- /dev/null +++ b/tests/phpunit/includes/objectcache/BagOStuffTest.php @@ -0,0 +1,149 @@ +<?php +/** + * This class will test BagOStuff. + * + * @author Matthias Mullie <mmullie@wikimedia.org> + */ +class BagOStuffTest extends MediaWikiTestCase { + private $cache; + + protected function setUp() { + parent::setUp(); + + // type defined through parameter + if ( $this->getCliArg( 'use-bagostuff=' ) ) { + $name = $this->getCliArg( 'use-bagostuff=' ); + + $this->cache = ObjectCache::newFromId( $name ); + } else { + // no type defined - use simple hash + $this->cache = new HashBagOStuff; + } + + $this->cache->delete( wfMemcKey( 'test' ) ); + } + + protected function tearDown() { + } + + public function testMerge() { + $key = wfMemcKey( 'test' ); + + $usleep = 0; + + /** + * Callback method: append "merged" to whatever is in cache. + * + * @param BagOStuff $cache + * @param string $key + * @param int $existingValue + * @use int $usleep + * @return int + */ + $callback = function ( BagOStuff $cache, $key, $existingValue ) use ( &$usleep ) { + // let's pretend this is an expensive callback to test concurrent merge attempts + usleep( $usleep ); + + if ( $existingValue === false ) { + return 'merged'; + } + + return $existingValue . 'merged'; + }; + + // merge on non-existing value + $merged = $this->cache->merge( $key, $callback, 0 ); + $this->assertTrue( $merged ); + $this->assertEquals( $this->cache->get( $key ), 'merged' ); + + // merge on existing value + $merged = $this->cache->merge( $key, $callback, 0 ); + $this->assertTrue( $merged ); + $this->assertEquals( $this->cache->get( $key ), 'mergedmerged' ); + + /* + * Test concurrent merges by forking this process, if: + * - not manually called with --use-bagostuff + * - pcntl_fork is supported by the system + * - cache type will correctly support calls over forks + */ + $fork = (bool)$this->getCliArg( 'use-bagostuff=' ); + $fork &= function_exists( 'pcntl_fork' ); + $fork &= !$this->cache instanceof HashBagOStuff; + $fork &= !$this->cache instanceof EmptyBagOStuff; + $fork &= !$this->cache instanceof MultiWriteBagOStuff; + if ( $fork ) { + // callback should take awhile now so that we can test concurrent merge attempts + $usleep = 5000; + + $pid = pcntl_fork(); + if ( $pid == -1 ) { + // can't fork, ignore this test... + } elseif ( $pid ) { + // wait a little, making sure that the child process is calling merge + usleep( 3000 ); + + // attempt a merge - this should fail + $merged = $this->cache->merge( $key, $callback, 0, 1 ); + + // merge has failed because child process was merging (and we only attempted once) + $this->assertFalse( $merged ); + + // make sure the child's merge is completed and verify + usleep( 3000 ); + $this->assertEquals( $this->cache->get( $key ), 'mergedmergedmerged' ); + } else { + $this->cache->merge( $key, $callback, 0, 1 ); + + // Note: I'm not even going to check if the merge worked, I'll + // compare values in the parent process to test if this merge worked. + // I'm just going to exit this child process, since I don't want the + // child to output any test results (would be rather confusing to + // have test output twice) + exit; + } + } + } + + public function testAdd() { + $key = wfMemcKey( 'test' ); + $this->assertTrue( $this->cache->add( $key, 'test' ) ); + } + + public function testGet() { + $value = array( 'this' => 'is', 'a' => 'test' ); + + $key = wfMemcKey( 'test' ); + $this->cache->add( $key, $value ); + $this->assertEquals( $this->cache->get( $key ), $value ); + } + + /** + * @covers BagOStuff::incr + */ + public function testIncr() { + $key = wfMemcKey( 'test' ); + $this->cache->add( $key, 0 ); + $this->cache->incr( $key ); + $expectedValue = 1; + $actualValue = $this->cache->get( $key ); + $this->assertEquals( $expectedValue, $actualValue, 'Value should be 1 after incrementing' ); + } + + public function testGetMulti() { + $value1 = array( 'this' => 'is', 'a' => 'test' ); + $value2 = array( 'this' => 'is', 'another' => 'test' ); + + $key1 = wfMemcKey( 'test1' ); + $key2 = wfMemcKey( 'test2' ); + + $this->cache->add( $key1, $value1 ); + $this->cache->add( $key2, $value2 ); + + $this->assertEquals( $this->cache->getMulti( array( $key1, $key2 ) ), array( $key1 => $value1, $key2 => $value2 ) ); + + // cleanup + $this->cache->delete( $key1 ); + $this->cache->delete( $key2 ); + } +} diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php index 31645313..c2c97c01 100644 --- a/tests/phpunit/includes/parser/MagicVariableTest.php +++ b/tests/phpunit/includes/parser/MagicVariableTest.php @@ -9,11 +9,13 @@ * @author Antoine Musso * @copyright Copyright © 2011, Antoine Musso * @file + * @todo covers tags */ -/** */ class MagicVariableTest extends MediaWikiTestCase { - /** Will contains a parser object*/ + /** + * @var Parser + */ private $testParser = null; /** @@ -29,12 +31,17 @@ class MagicVariableTest extends MediaWikiTestCase { ); /** setup a basic parser object */ - function setUp() { - global $wgContLang; - $wgContLang = Language::factory( 'en' ); + protected function setUp() { + parent::setUp(); + + $contLang = Language::factory( 'en' ); + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => $contLang, + ) ); $this->testParser = new Parser(); - $this->testParser->Options( new ParserOptions() ); + $this->testParser->Options( ParserOptions::newFromUserAndLang( new User, $contLang ) ); # initialize parser output $this->testParser->clearState(); @@ -46,9 +53,31 @@ class MagicVariableTest extends MediaWikiTestCase { $this->testParser->setTitle( $title ); } - /** destroy parser (TODO: is it really neded?)*/ - function tearDown() { - unset( $this->testParser ); + /** + * @param int $num upper limit for numbers + * @return array of numbers from 1 up to $num + */ + private static function createProviderUpTo( $num ) { + $ret = array(); + for ( $i = 1; $i <= $num; $i++ ) { + $ret[] = array( $i ); + } + + return $ret; + } + + /** + * @return array of months numbers (as an integer) + */ + public static function provideMonths() { + return self::createProviderUpTo( 12 ); + } + + /** + * @return array of days numbers (as an integer) + */ + public static function provideDays() { + return self::createProviderUpTo( 31 ); } ############### TESTS ############################################# @@ -58,117 +87,129 @@ class MagicVariableTest extends MediaWikiTestCase { # day - /** @dataProvider MediaWikiProvide::Days */ - function testCurrentdayIsUnPadded( $day ) { + /** @dataProvider provideDays */ + public function testCurrentdayIsUnPadded( $day ) { $this->assertUnPadded( 'currentday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testCurrentdaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testCurrentdaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'currentday2', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testLocaldayIsUnPadded( $day ) { + + /** @dataProvider provideDays */ + public function testLocaldayIsUnPadded( $day ) { $this->assertUnPadded( 'localday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testLocaldaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testLocaldaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'localday2', $day ); } - + # month - /** @dataProvider MediaWikiProvide::Months */ - function testCurrentmonthIsZeroPadded( $month ) { + /** @dataProvider provideMonths */ + public function testCurrentmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'currentmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testCurrentmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testCurrentmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'currentmonth1', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testLocalmonthIsZeroPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testLocalmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'localmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testLocalmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testLocalmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'localmonth1', $month ); } - # revision day - /** @dataProvider MediaWikiProvide::Days */ - function testRevisiondayIsUnPadded( $day ) { + /** @dataProvider provideDays */ + public function testRevisiondayIsUnPadded( $day ) { $this->assertUnPadded( 'revisionday', $day ); } - /** @dataProvider MediaWikiProvide::Days */ - function testRevisiondaytwoIsZeroPadded( $day ) { + + /** @dataProvider provideDays */ + public function testRevisiondaytwoIsZeroPadded( $day ) { $this->assertZeroPadded( 'revisionday2', $day ); } - + # revision month - /** @dataProvider MediaWikiProvide::Months */ - function testRevisionmonthIsZeroPadded( $month ) { + /** @dataProvider provideMonths */ + public function testRevisionmonthIsZeroPadded( $month ) { $this->assertZeroPadded( 'revisionmonth', $month ); } - /** @dataProvider MediaWikiProvide::Months */ - function testRevisionmonthoneIsUnPadded( $month ) { + + /** @dataProvider provideMonths */ + public function testRevisionmonthoneIsUnPadded( $month ) { $this->assertUnPadded( 'revisionmonth1', $month ); } /** * Rough tests for {{SERVERNAME}} magic word * Bug 31176 + * @group Database + * @dataProvider provideDataServernameFromDifferentProtocols */ - function testServernameFromDifferentProtocols() { - global $wgServer; - $saved_wgServer= $wgServer; + public function testServernameFromDifferentProtocols( $server ) { + $this->setMwGlobals( 'wgServer', $server ); - $wgServer = 'http://localhost/'; - $this->assertMagic( 'localhost', 'servername' ); - $wgServer = 'https://localhost/'; - $this->assertMagic( 'localhost', 'servername' ); - $wgServer = '//localhost/'; # bug 31176 $this->assertMagic( 'localhost', 'servername' ); + } - $wgServer = $saved_wgServer; + public static function provideDataServernameFromDifferentProtocols() { + return array( + array( 'http://localhost/' ), + array( 'https://localhost/' ), + array( '//localhost/' ), # bug 31176 + ); } ############### HELPERS ############################################ /** assertion helper expecting a magic output which is zero padded */ - PUBLIC function assertZeroPadded( $magic, $value ) { + public function assertZeroPadded( $magic, $value ) { $this->assertMagicPadding( $magic, $value, '%02d' ); } /** assertion helper expecting a magic output which is unpadded */ - PUBLIC function assertUnPadded( $magic, $value ) { + public function assertUnPadded( $magic, $value ) { $this->assertMagicPadding( $magic, $value, '%d' ); } /** * Main assertion helper for magic variables padding - * @param $magic string Magic variable name + * @param $magic string Magic variable name * @param $value mixed Month or day - * @param $format string sprintf format for $value + * @param $format string sprintf format for $value */ private function assertMagicPadding( $magic, $value, $format ) { # Initialize parser timestamp as year 2010 at 12h34 56s. # month and day are given by the caller ($value). Month < 12! - if( $value > 12 ) { $month = $value % 12; } - else { $month = $value; } - + if ( $value > 12 ) { + $month = $value % 12; + } else { + $month = $value; + } + $this->setParserTS( sprintf( '2010%02d%02d123456', $month, $value ) ); - # please keep the following commented line of code. It helps debugging. + # please keep the following commented line of code. It helps debugging. //print "\nDEBUG (value $value):" . sprintf( '2010%02d%02d123456', $value, $value ) . "\n"; # format expectation and test it $expected = sprintf( $format, $value ); - $this->assertMagic( $expected, $magic ); + $this->assertMagic( $expected, $magic ); } /** helper to set the parser timestamp and revision timestamp */ @@ -181,8 +222,8 @@ class MagicVariableTest extends MediaWikiTestCase { * Assertion helper to test a magic variable output */ private function assertMagic( $expected, $magic ) { - if( in_array( $magic, $this->expectedAsInteger ) ) { - $expected = (int) $expected; + if ( in_array( $magic, $this->expectedAsInteger ) ) { + $expected = (int)$expected; } # Generate a message for the assertion diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php index 6a6fded1..c120ca34 100644 --- a/tests/phpunit/includes/parser/MediaWikiParserTest.php +++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php @@ -1,36 +1,120 @@ <?php -require_once( __DIR__ . '/NewParserTest.php' ); +require_once __DIR__ . '/NewParserTest.php'; /** * The UnitTest must be either a class that inherits from MediaWikiTestCase - * or a class that provides a public static suite() method which returns + * or a class that provides a public static suite() method which returns * an PHPUnit_Framework_Test object - * + * * @group Parser * @group Database */ class MediaWikiParserTest { - public static function suite() { - global $wgParserTestFiles; + /** + * @defgroup filtering_constants Filtering constants + * + * Limit inclusion of parser tests files coming from MediaWiki core + * @{ + */ - $suite = new PHPUnit_Framework_TestSuite; + /** Include files shipped with MediaWiki core */ + const CORE_ONLY = 1; + /** Include non core files as set in $wgParserTestFiles */ + const NO_CORE = 2; + /** Include anything set via $wgParserTestFiles */ + const WITH_ALL = 3; # CORE_ONLY | NO_CORE + + /** @} */ + + /** + * Get a PHPUnit test suite of parser tests. Optionally filtered with + * $flags. + * + * @par Examples: + * Get a suite of parser tests shipped by MediaWiki core: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::CORE_ONLY ); + * @endcode + * Get a suite of various parser tests, like extensions: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE ); + * @endcode + * Get any test defined via $wgParserTestFiles: + * @code + * MediaWikiParserTest::suite( MediaWikiParserTest::WITH_ALL ); + * @endcode + * + * @param $flags bitwise flag to filter out the $wgParserTestFiles that + * will be included. Default: MediaWikiParserTest::CORE_ONLY + * + * @return PHPUnit_Framework_TestSuite + */ + public static function suite( $flags = self::CORE_ONLY ) { + if ( is_string( $flags ) ) { + $flags = self::CORE_ONLY; + } + global $wgParserTestFiles, $IP; + + $mwTestDir = $IP . '/tests/'; + + # Human friendly helpers + $wantsCore = ( $flags & self::CORE_ONLY ); + $wantsRest = ( $flags & self::NO_CORE ); + + # Will hold the .txt parser test files we will include + $filesToTest = array(); + + # Filter out .txt files + foreach ( $wgParserTestFiles as $parserTestFile ) { + $isCore = ( 0 === strpos( $parserTestFile, $mwTestDir ) ); + + if ( $isCore && $wantsCore ) { + self::debug( "included core parser tests: $parserTestFile" ); + $filesToTest[] = $parserTestFile; + } elseif ( !$isCore && $wantsRest ) { + self::debug( "included non core parser tests: $parserTestFile" ); + $filesToTest[] = $parserTestFile; + } else { + self::debug( "skipped parser tests: $parserTestFile" ); + } + } + self::debug( 'parser tests files: ' + . implode( ' ', $filesToTest ) ); - foreach ( $wgParserTestFiles as $filename ) { - $testsName = basename( $filename, '.txt' ); + $suite = new PHPUnit_Framework_TestSuite; + foreach ( $filesToTest as $fileName ) { + $testsName = basename( $fileName, '.txt' ); + $escapedFileName = strtr( $fileName, array( "'" => "\\'", '\\' => '\\\\' ) ); /* This used to be ucfirst( basename( dirname( $filename ) ) ) * and then was ucfirst( basename( $filename, '.txt' ) * but that didn't work with names like foo.tests.txt */ - $className = str_replace( '.', '_', ucfirst( basename( $filename, '.txt' ) ) ); - - eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " ); + $parserTestClassName = str_replace( '.', '_', ucfirst( $testsName ) ); + $parserTestClassDefinition = <<<EOT +/** + * @group Database + * @group Parser + * @group ParserTests + * @group ParserTests_$parserTestClassName + */ +class $parserTestClassName extends NewParserTest { + protected \$file = '$escapedFileName'; +} +EOT; - $parserTester = new $className( $testsName ); - $suite->addTestSuite( new ReflectionClass ( $parserTester ) ); + eval( $parserTestClassDefinition ); + self::debug( "Adding test class $parserTestClassName" ); + $suite->addTestSuite( $parserTestClassName ); } - - return $suite; } + + /** + * Write $msg under log group 'tests-parser' + * @param string $msg Message to log + */ + protected static function debug( $msg ) { + return wfDebugLog( 'tests-parser', wfGetCaller() . ' ' . $msg ); + } } diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php index 69a96e66..eac4de5c 100644 --- a/tests/phpunit/includes/parser/NewParserTest.php +++ b/tests/phpunit/includes/parser/NewParserTest.php @@ -6,19 +6,21 @@ * @group Database * @group Parser * @group Stub + * + * @todo covers tags */ class NewParserTest extends MediaWikiTestCase { - static protected $articles = array(); // Array of test articles defined by the tests - /* The dataProvider is run on a different instance than the test, so it must be static + static protected $articles = array(); // Array of test articles defined by the tests + /* The data provider is run on a different instance than the test, so it must be static * When running tests from several files, all tests will see all articles. */ static protected $backendToUse; public $keepUploads = false; public $runDisabled = false; + public $runParsoid = false; public $regex = ''; public $showProgress = true; - public $savedInitialGlobals = array(); public $savedWeirdGlobals = array(); public $savedGlobals = array(); public $hooks = array(); @@ -31,10 +33,16 @@ class NewParserTest extends MediaWikiTestCase { protected $file = false; - function setUp() { - global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases; + public static function setUpBeforeClass() { + // Inject ParserTest well-known interwikis + ParserTest::setupInterwikis(); + } + + protected function setUp() { + global $wgNamespaceAliases, $wgContLang; global $wgHooks, $IP; - $wgContLang = Language::factory( 'en' ); + + parent::setUp(); //Setup CLI arguments if ( $this->getCliArg( 'regex=' ) ) { @@ -48,133 +56,138 @@ class NewParserTest extends MediaWikiTestCase { $tmpGlobals = array(); + $tmpGlobals['wgLanguageCode'] = 'en'; + $tmpGlobals['wgContLang'] = Language::factory( 'en' ); + $tmpGlobals['wgSitename'] = 'MediaWiki'; + $tmpGlobals['wgServer'] = 'http://example.org'; $tmpGlobals['wgScript'] = '/index.php'; $tmpGlobals['wgScriptPath'] = '/'; $tmpGlobals['wgArticlePath'] = '/wiki/$1'; - $tmpGlobals['wgStyleSheetPath'] = '/skins'; + $tmpGlobals['wgActionPaths'] = array(); + $tmpGlobals['wgVariantArticlePath'] = false; + $tmpGlobals['wgExtensionAssetsPath'] = '/extensions'; $tmpGlobals['wgStylePath'] = '/skins'; + $tmpGlobals['wgEnableUploads'] = true; $tmpGlobals['wgThumbnailScriptPath'] = false; $tmpGlobals['wgLocalFileRepo'] = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => 'local-backend' + 'backend' => 'local-backend' ); $tmpGlobals['wgForeignFileRepos'] = array(); + $tmpGlobals['wgDefaultExternalStore'] = array(); $tmpGlobals['wgEnableParserCache'] = false; - $tmpGlobals['wgHooks'] = $wgHooks; + $tmpGlobals['wgCapitalLinks'] = true; + $tmpGlobals['wgNoFollowLinks'] = true; + $tmpGlobals['wgNoFollowDomainExceptions'] = array(); + $tmpGlobals['wgExternalLinkTarget'] = false; + $tmpGlobals['wgThumbnailScriptPath'] = false; + $tmpGlobals['wgUseImageResize'] = true; + $tmpGlobals['wgAllowExternalImages'] = true; + $tmpGlobals['wgRawHtml'] = false; + $tmpGlobals['wgUseTidy'] = false; + $tmpGlobals['wgAlwaysUseTidy'] = false; + $tmpGlobals['wgWellFormedXml'] = true; + $tmpGlobals['wgAllowMicrodataAttributes'] = true; + $tmpGlobals['wgExperimentalHtmlIds'] = false; + $tmpGlobals['wgAdaptiveMessageCache'] = true; + $tmpGlobals['wgUseDatabaseMessages'] = true; + $tmpGlobals['wgLocaltimezone'] = 'UTC'; $tmpGlobals['wgDeferredUpdateList'] = array(); - $tmpGlobals['wgMemc'] = wfGetMainCache(); - $tmpGlobals['messageMemc'] = wfGetMessageCacheStorage(); - $tmpGlobals['parserMemc'] = wfGetParserCacheStorage(); + $tmpGlobals['wgGroupPermissions'] = array( + '*' => array( + 'createaccount' => true, + 'read' => true, + 'edit' => true, + 'createpage' => true, + 'createtalk' => true, + ) ); + $tmpGlobals['wgNamespaceProtection'] = array( NS_MEDIAWIKI => 'editinterface' ); - // $tmpGlobals['wgContLang'] = new StubContLang; - $tmpGlobals['wgUser'] = new User; - $context = new RequestContext(); - $tmpGlobals['wgLang'] = $context->getLanguage(); - $tmpGlobals['wgOut'] = $context->getOutput(); - $tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) ); - $tmpGlobals['wgRequest'] = $context->getRequest(); + $tmpGlobals['wgParser'] = new StubObject( + 'wgParser', $GLOBALS['wgParserConf']['class'], + array( $GLOBALS['wgParserConf'] ) ); + + $tmpGlobals['wgFileExtensions'][] = 'svg'; + $tmpGlobals['wgSVGConverter'] = 'rsvg'; + $tmpGlobals['wgSVGConverters']['rsvg'] = + '$path/rsvg-convert -w $width -h $height $input -o $output'; if ( $GLOBALS['wgStyleDirectory'] === false ) { $tmpGlobals['wgStyleDirectory'] = "$IP/skins"; } + # Replace all media handlers with a mock. We do not need to generate + # actual thumbnails to do parser testing, we only care about receiving + # a ThumbnailImage properly initialized. + global $wgMediaHandlers; + foreach ( $wgMediaHandlers as $type => $handler ) { + $tmpGlobals['wgMediaHandlers'][$type] = 'MockBitmapHandler'; + } + // Vector images have to be handled slightly differently + $tmpGlobals['wgMediaHandlers']['image/svg+xml'] = 'MockSvgHandler'; - foreach ( $tmpGlobals as $var => $val ) { - if ( array_key_exists( $var, $GLOBALS ) ) { - $this->savedInitialGlobals[$var] = $GLOBALS[$var]; - } + $tmpHooks = $wgHooks; + $tmpHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; + $tmpHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; + $tmpGlobals['wgHooks'] = $tmpHooks; + # add a namespace shadowing a interwiki link, to test + # proper precedence when resolving links. (bug 51680) + $tmpGlobals['wgExtraNamespaces'] = array( 100 => 'MemoryAlpha' ); - $GLOBALS[$var] = $val; - } + $this->setMwGlobals( $tmpGlobals ); - $this->savedWeirdGlobals['mw_namespace_protection'] = $wgNamespaceProtection[NS_MEDIAWIKI]; $this->savedWeirdGlobals['image_alias'] = $wgNamespaceAliases['Image']; $this->savedWeirdGlobals['image_talk_alias'] = $wgNamespaceAliases['Image_talk']; - $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - } - public function tearDown() { - foreach ( $this->savedInitialGlobals as $var => $val ) { - $GLOBALS[$var] = $val; - } + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } - global $wgNamespaceProtection, $wgNamespaceAliases; + protected function tearDown() { + global $wgNamespaceAliases, $wgContLang; - $wgNamespaceProtection[NS_MEDIAWIKI] = $this->savedWeirdGlobals['mw_namespace_protection']; $wgNamespaceAliases['Image'] = $this->savedWeirdGlobals['image_alias']; $wgNamespaceAliases['Image_talk'] = $this->savedWeirdGlobals['image_talk_alias']; // Restore backends RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); + + // Remove temporary pages from the link cache + LinkCache::singleton()->clear(); + + // Restore message cache (temporary pages and $wgUseDatabaseMessages) + MessageCache::destroyInstance(); + + parent::tearDown(); + + MWNamespace::getCanonicalNamespaces( true ); # reset namespace cache + $wgContLang->resetNamespaces(); # reset namespace cache + } + + public static function tearDownAfterClass() { + ParserTest::tearDownInterwikis(); + parent::tearDownAfterClass(); } function addDBData() { $this->tablesUsed[] = 'site_stats'; - $this->tablesUsed[] = 'interwiki'; # disabled for performance #$this->tablesUsed[] = 'image'; - # Hack: insert a few Wikipedia in-project interwiki prefixes, - # for testing inter-language links - $this->db->insert( 'interwiki', array( - array( 'iw_prefix' => 'wikipedia', - 'iw_url' => 'http://en.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'meatball', - 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 0 ), - array( 'iw_prefix' => 'zh', - 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'es', - 'iw_url' => 'http://es.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'fr', - 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - array( 'iw_prefix' => 'ru', - 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', - 'iw_api' => '', - 'iw_wikiid' => '', - 'iw_local' => 1 ), - /** - * @todo Fixme! Why are we inserting duplicate data here? Shouldn't - * need this IGNORE or shouldn't need the insert at all. - */ - ), __METHOD__, array( 'IGNORE' ) - ); - - # Update certain things in site_stats $this->db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ), __METHOD__ ); - # Reinitialise the LocalisationCache to match the database state - Language::getLocalisationCache()->unloadAll(); - - # Clear the message cache - MessageCache::singleton()->clear(); - $user = User::newFromId( 0 ); LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision @@ -182,6 +195,10 @@ class NewParserTest extends MediaWikiTestCase { # We will upload the actual files later. Note that if anything causes LocalFile::load() # to be triggered before then, it will break via maybeUpgrade() setting the fileExists # member to false and storing it in cache. + # note that the size/width/height/bits/etc of the file + # are actually set by inspecting the file itself; the arguments + # to recordUpload2 have no effect. That said, we try to make things + # match up so it is less confusing to readers of the code & tests. $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { $image->recordUpload2( @@ -189,19 +206,39 @@ class NewParserTest extends MediaWikiTestCase { 'Upload of some lame file', 'Some lame file', array( - 'size' => 12345, - 'width' => 1941, - 'height' => 220, - 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', - 'metadata' => serialize( array() ), - 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), + 'size' => 7881, + 'width' => 1941, + 'height' => 220, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '1', 16, 36, 31 ), + 'fileExists' => true ), $this->db->timestamp( '20010115123500' ), $user ); } + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Thumb.png' ) ); + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( + '', // archive name + 'Upload of some lame thumbnail', + 'Some lame thumbnail', + array( + 'size' => 22589, + 'width' => 135, + 'height' => 135, + 'bits' => 8, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/png', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '2', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20130225203040' ), $user + ); + } + # This image will be blacklisted in [[MediaWiki:Bad image list]] $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { @@ -210,30 +247,41 @@ class NewParserTest extends MediaWikiTestCase { 'zomgnotcensored', 'Borderline image', array( + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '3', 16, 36, 31 ), + 'fileExists' => true ), + $this->db->timestamp( '20010115123500' ), $user + ); + } + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.svg' ) ); + if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) { + $image->recordUpload2( '', 'Upload of some lame SVG', 'Some lame SVG', array( 'size' => 12345, - 'width' => 320, - 'height' => 240, + 'width' => 200, + 'height' => 200, 'bits' => 24, - 'media_type' => MEDIATYPE_BITMAP, - 'mime' => 'image/jpeg', + 'media_type' => MEDIATYPE_DRAWING, + 'mime' => 'image/svg+xml', 'metadata' => serialize( array() ), 'sha1' => wfBaseConvert( '', 16, 36, 31 ), - 'fileExists' => true ), - $this->db->timestamp( '20010115123500' ), $user - ); + 'fileExists' => true + ), $this->db->timestamp( '20010115123500' ), $user ); } } - - - //ParserTest setup/teardown functions /** * Set up the global variables for a consistent environment for each test. * Ideally this should replace the global configuration entirely. */ - protected function setupGlobals( $opts = '', $config = '' ) { + protected function setupGlobals( $opts = array(), $config = '' ) { global $wgFileBackends; # Find out values for some special options. $lang = @@ -263,73 +311,39 @@ class NewParserTest extends MediaWikiTestCase { $backend = self::$backendToUse; } } else { - $backend = new FSFileBackend( array( - 'name' => 'local-backend', + # Replace with a mock. We do not care about generating real + # files on the filesystem, just need to expose the file + # informations. + $backend = new MockFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'nullLockManager', 'containerPaths' => array( 'local-public' => "$uploadDir", - 'local-thumb' => "$uploadDir/thumb", + 'local-thumb' => "$uploadDir/thumb", ) ) ); } $settings = array( - 'wgServer' => 'http://Britney-Spears', - 'wgScript' => '/index.php', - 'wgScriptPath' => '/', - 'wgArticlePath' => '/wiki/$1', - 'wgExtensionAssetsPath' => '/extensions', - 'wgActionPaths' => array(), 'wgLocalFileRepo' => array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => $backend + 'backend' => $backend ), 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), - 'wgStylePath' => '/skins', - 'wgStyleSheetPath' => '/skins', - 'wgSitename' => 'MediaWiki', 'wgLanguageCode' => $lang, 'wgDBprefix' => $this->db->getType() != 'oracle' ? 'unittest_' : 'ut_', - 'wgRawHtml' => isset( $opts['rawhtml'] ), - 'wgLang' => null, - 'wgContLang' => null, - 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), + 'wgRawHtml' => self::getOptionValue( 'wgRawHtml', $opts, false ), + 'wgNamespacesWithSubpages' => array( NS_MAIN => isset( $opts['subpage'] ) ), + 'wgAllowExternalImages' => self::getOptionValue( 'wgAllowExternalImages', $opts, true ), 'wgMaxTocLevel' => $maxtoclevel, - 'wgCapitalLinks' => true, - 'wgNoFollowLinks' => true, - 'wgNoFollowDomainExceptions' => array(), - 'wgThumbnailScriptPath' => false, - 'wgUseImageResize' => false, - 'wgUseTeX' => isset( $opts['math'] ), + 'wgUseTeX' => isset( $opts['math'] ) || isset( $opts['texvc'] ), 'wgMathDirectory' => $uploadDir . '/math', - 'wgLocaltimezone' => 'UTC', - 'wgAllowExternalImages' => true, - 'wgUseTidy' => false, 'wgDefaultLanguageVariant' => $variant, - 'wgVariantArticlePath' => false, - 'wgGroupPermissions' => array( '*' => array( - 'createaccount' => true, - 'read' => true, - 'edit' => true, - 'createpage' => true, - 'createtalk' => true, - ) ), - 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), - 'wgDefaultExternalStore' => array(), - 'wgForeignFileRepos' => array(), 'wgLinkHolderBatchSize' => $linkHolderBatchSize, - 'wgExperimentalHtmlIds' => false, - 'wgExternalLinkTarget' => false, - 'wgAlwaysUseTidy' => false, - 'wgHtml5' => true, - 'wgWellFormedXml' => true, - 'wgAllowMicrodataAttributes' => true, - 'wgAdaptiveMessageCache' => true, - 'wgUseDatabaseMessages' => true, ); if ( $config ) { @@ -347,6 +361,15 @@ class NewParserTest extends MediaWikiTestCase { /** @since 1.20 */ wfRunHooks( 'ParserTestGlobals', array( &$settings ) ); + $langObj = Language::factory( $lang ); + $settings['wgContLang'] = $langObj; + $settings['wgLang'] = $langObj; + + $context = new RequestContext(); + $settings['wgOut'] = $context->getOutput(); + $settings['wgUser'] = $context->getUser(); + $settings['wgRequest'] = $context->getRequest(); + foreach ( $settings as $var => $val ) { if ( array_key_exists( $var, $GLOBALS ) ) { $this->savedGlobals[$var] = $GLOBALS[$var]; @@ -355,21 +378,9 @@ class NewParserTest extends MediaWikiTestCase { $GLOBALS[$var] = $val; } - $langObj = Language::factory( $lang ); - $GLOBALS['wgContLang'] = $langObj; - $context = new RequestContext(); - $GLOBALS['wgLang'] = $context->getLanguage(); - - $GLOBALS['wgMemc'] = new EmptyBagOStuff; - $GLOBALS['wgOut'] = $context->getOutput(); - $GLOBALS['wgUser'] = $context->getUser(); - - global $wgHooks; - - $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; - $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; - MagicWord::clearCache(); + + # The entries saved into RepoGroup cache with previous globals will be wrong. RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); @@ -379,9 +390,6 @@ class NewParserTest extends MediaWikiTestCase { # Publish the articles after we have the final language set $this->publishTestArticles(); - # The entries saved into RepoGroup cache with previous globals will be wrong. - RepoGroup::destroySingleton(); - FileBackendGroup::destroySingleton(); MessageCache::destroyInstance(); return $context; @@ -406,6 +414,7 @@ class NewParserTest extends MediaWikiTestCase { // wfDebug( "Creating upload directory $dir\n" ); if ( file_exists( $dir ) ) { wfDebug( "Already exists!\n" ); + return $dir; } @@ -427,10 +436,27 @@ class NewParserTest extends MediaWikiTestCase { $backend->store( array( 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg" ) ); + $backend->prepare( array( 'dir' => "$base/local-public/e/ea" ) ); + $backend->store( array( + 'src' => "$IP/skins/monobook/wiki.png", 'dst' => "$base/local-public/e/ea/Thumb.png" + ) ); $backend->prepare( array( 'dir' => "$base/local-public/0/09" ) ); $backend->store( array( 'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/0/09/Bad.jpg" ) ); + + // No helpful SVG file to copy, so make one ourselves + $tmpDir = wfTempDir(); + $tempFsFile = new TempFSFile( "$tmpDir/Foobar.svg" ); + $tempFsFile->autocollect(); // destroy file when $tempFsFile leaves scope + file_put_contents( "$tmpDir/Foobar.svg", + '<?xml version="1.0" encoding="utf-8"?>' . + '<svg xmlns="http://www.w3.org/2000/svg" width="200" height="200"><text>Foo</text></svg>' ); + + $backend->prepare( array( 'dir' => "$base/local-public/f/ff" ) ); + $backend->quickStore( array( + 'src' => "$tmpDir/Foobar.svg", 'dst' => "$base/local-public/f/ff/Foobar.svg" + ) ); } /** @@ -443,9 +469,6 @@ class NewParserTest extends MediaWikiTestCase { foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; } - - RepoGroup::destroySingleton(); - LinkCache::singleton()->clear(); } /** @@ -456,18 +479,43 @@ class NewParserTest extends MediaWikiTestCase { return; } + $backend = RepoGroup::singleton()->getLocalRepo()->getBackend(); + if ( $backend instanceof MockFileBackend ) { + # In memory backend, so dont bother cleaning them up. + return; + } + $base = $this->getBaseDir(); // delete the files first, then the dirs. self::deleteFiles( - array ( + array( "$base/local-public/3/3a/Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", "$base/local-thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/1280px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/20px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/270px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/300px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/30px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/360px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/400px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/40px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/70px-Foobar.jpg", + "$base/local-thumb/3/3a/Foobar.jpg/960px-Foobar.jpg", + + "$base/local-public/e/ea/Thumb.png", "$base/local-public/0/09/Bad.jpg", - "$base/local-thumb/0/09/Bad.jpg", + + "$base/local-public/f/ff/Foobar.svg", + "$base/local-thumb/f/ff/Foobar.svg/180px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/270px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/360px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-180px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-270px-Foobar.svg.jpg", + "$base/local-thumb/f/ff/Foobar.svg/langde-360px-Foobar.svg.jpg", "$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", ) @@ -502,6 +550,7 @@ class NewParserTest extends MediaWikiTestCase { global $wgParserTestFiles; $this->file = $wgParserTestFiles[0]; } + return new TestFileIterator( $this->file, $this ); } @@ -523,6 +572,14 @@ class NewParserTest extends MediaWikiTestCase { return; } + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // parser tests frequently assume that the main namespace contains wikitext. + // @todo When setting up pages, force the content model. Only skip if + // $wgtContentModelUseDB is false. + $this->markTestSkipped( "Main namespace does not support wikitext," + . "skipping parser test: $desc" ); + } + wfDebug( "Running parser test: $desc\n" ); $opts = $this->parseOptions( $opts ); @@ -533,8 +590,7 @@ class NewParserTest extends MediaWikiTestCase { if ( isset( $opts['title'] ) ) { $titleText = $opts['title']; - } - else { + } else { $titleText = 'Parser test'; } @@ -544,6 +600,20 @@ class NewParserTest extends MediaWikiTestCase { $title = Title::newFromText( $titleText ); + # Parser test requiring math. Make sure texvc is executable + # or just skip such tests. + if ( isset( $opts['math'] ) || isset( $opts['texvc'] ) ) { + global $wgTexvc; + + if ( !isset( $wgTexvc ) ) { + $this->markTestSkipped( "SKIPPED: \$wgTexvc is not set" ); + } elseif ( !is_executable( $wgTexvc ) ) { + $this->markTestSkipped( "SKIPPED: texvc binary does not exist" + . " or is not executable.\n" + . "Current configuration is:\n\$wgTexvc = '$wgTexvc'" ); + } + } + if ( isset( $opts['pst'] ) ) { $out = $parser->preSaveTransform( $input, $title, $user, $options ); } elseif ( isset( $opts['msg'] ) ) { @@ -558,9 +628,10 @@ class NewParserTest extends MediaWikiTestCase { } elseif ( isset( $opts['comment'] ) ) { $out = Linker::formatComment( $input, $title, $local ); } elseif ( isset( $opts['preload'] ) ) { - $out = $parser->getpreloadText( $input, $title, $options ); + $out = $parser->getPreloadText( $input, $title, $options ); } else { $output = $parser->parse( $input, $title, $options, true, true, 1337 ); + $output->setTOCEnabled( !isset( $opts['notoc'] ) ); $out = $output->getText(); if ( isset( $opts['showtitle'] ) ) { @@ -602,12 +673,12 @@ class NewParserTest extends MediaWikiTestCase { * * @group ParserFuzz */ - function testFuzzTests() { + public function testFuzzTests() { global $wgParserTestFiles; $files = $wgParserTestFiles; - if( $this->getCliArg( 'file=' ) ) { + if ( $this->getCliArg( 'file=' ) ) { $files = array( $this->getCliArg( 'file=' ) ); } @@ -647,7 +718,9 @@ class NewParserTest extends MediaWikiTestCase { } catch ( Exception $exception ) { $input_dump = sprintf( "string(%d) \"%s\"\n", strlen( $input ), $input ); - $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\nInput: $input_dump\n\nError: {$exception->getMessage()}\n\nBacktrace: {$exception->getTraceAsString()}" ); + $this->assertTrue( false, "Test $id, fuzz seed {$this->fuzzSeed}. \n\n" . + "Input: $input_dump\n\nError: {$exception->getMessage()}\n\n" . + "Backtrace: {$exception->getTraceAsString()}" ); } $this->teardownGlobals(); @@ -669,7 +742,6 @@ class NewParserTest extends MediaWikiTestCase { } $id++; - } } @@ -769,15 +841,16 @@ class NewParserTest extends MediaWikiTestCase { */ public function requireHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. return isset( $wgParser->mTagHooks[$name] ); } public function requireFunctionHook( $name ) { global $wgParser; - $wgParser->firstCallInit( ); // make sure hooks are loaded. + $wgParser->firstCallInit(); // make sure hooks are loaded. return isset( $wgParser->mFunctionHooks[$name] ); } + //Various "cleanup" functions /** @@ -803,8 +876,7 @@ class NewParserTest extends MediaWikiTestCase { public function removeEndingNewline( $s ) { if ( substr( $s, -1 ) === "\n" ) { return substr( $s, 0, -1 ); - } - else { + } else { return $s; } } @@ -863,6 +935,7 @@ class NewParserTest extends MediaWikiTestCase { } } } + return $opts; } @@ -874,6 +947,7 @@ class NewParserTest extends MediaWikiTestCase { if ( substr( $opt, 0, 2 ) == '[[' ) { return substr( $opt, 2, -2 ); } + return $opt; } diff --git a/tests/phpunit/includes/parser/ParserMethodsTest.php b/tests/phpunit/includes/parser/ParserMethodsTest.php index dea406c3..e5c5cb21 100644 --- a/tests/phpunit/includes/parser/ParserMethodsTest.php +++ b/tests/phpunit/includes/parser/ParserMethodsTest.php @@ -2,19 +2,20 @@ class ParserMethodsTest extends MediaWikiLangTestCase { - public function dataPreSaveTransform() { + public static function providePreSaveTransform() { return array( array( 'hello this is ~~~', - "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", + "hello this is [[Special:Contributions/127.0.0.1|127.0.0.1]]", ), array( 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', - 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', + 'hello \'\'this\'\' is <nowiki>~~~</nowiki>', ), ); } /** - * @dataProvider dataPreSaveTransform + * @dataProvider providePreSaveTransform + * @covers Parser::preSaveTransform */ public function testPreSaveTransform( $text, $expected ) { global $wgParser; @@ -28,6 +29,67 @@ class ParserMethodsTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, $text ); } - // TODO: Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText() -} + /** + * @covers Parser::callParserFunction + */ + public function testCallParserFunction() { + global $wgParser; + + // Normal parses test passing PPNodes. Test passing an array. + $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) ); + $wgParser->startExternalParse( $title, new ParserOptions(), Parser::OT_HTML ); + $frame = $wgParser->getPreprocessor()->newFrame(); + $ret = $wgParser->callParserFunction( $frame, '#tag', + array( 'pre', 'foo', 'style' => 'margin-left: 1.6em' ) + ); + $ret['text'] = $wgParser->mStripState->unstripBoth( $ret['text'] ); + $this->assertSame( array( + 'found' => true, + 'text' => '<pre style="margin-left: 1.6em">foo</pre>', + ), $ret, 'callParserFunction works for {{#tag:pre|foo|style=margin-left: 1.6em}}' ); + } + + /** + * @covers Parser::parse + * @covers ParserOutput::getSections + */ + public function testGetSections() { + global $wgParser; + $title = Title::newFromText( str_replace( '::', '__', __METHOD__ ) ); + $out = $wgParser->parse( "==foo==\n<h2>bar</h2>\n==baz==\n", $title, new ParserOptions() ); + $this->assertSame( array( + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'foo', + 'number' => '1', + 'index' => '1', + 'fromtitle' => $title->getPrefixedDBkey(), + 'byteoffset' => 0, + 'anchor' => 'foo', + ), + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'bar', + 'number' => '2', + 'index' => '', + 'fromtitle' => false, + 'byteoffset' => null, + 'anchor' => 'bar', + ), + array( + 'toclevel' => 1, + 'level' => '2', + 'line' => 'baz', + 'number' => '3', + 'index' => '2', + 'fromtitle' => $title->getPrefixedDBkey(), + 'byteoffset' => 21, + 'anchor' => 'baz', + ), + ), $out->getSections(), 'getSections() with proper value when <h2> is used' ); + } + //@Todo Add tests for cleanSig() / cleanSigInSig(), getSection(), replaceSection(), getPreloadText() +} diff --git a/tests/phpunit/includes/parser/ParserOutputTest.php b/tests/phpunit/includes/parser/ParserOutputTest.php new file mode 100644 index 00000000..c73666da --- /dev/null +++ b/tests/phpunit/includes/parser/ParserOutputTest.php @@ -0,0 +1,59 @@ +<?php + +class ParserOutputTest extends MediaWikiTestCase { + + public static function provideIsLinkInternal() { + return array( + // Different domains + array( false, 'http://example.org', 'http://mediawiki.org' ), + // Same domains + array( true, 'http://example.org', 'http://example.org' ), + array( true, 'https://example.org', 'https://example.org' ), + array( true, '//example.org', '//example.org' ), + // Same domain different cases + array( true, 'http://example.org', 'http://EXAMPLE.ORG' ), + // Paths, queries, and fragments are not relevant + array( true, 'http://example.org', 'http://example.org/wiki/Main_Page' ), + array( true, 'http://example.org', 'http://example.org?my=query' ), + array( true, 'http://example.org', 'http://example.org#its-a-fragment' ), + // Different protocols + array( false, 'http://example.org', 'https://example.org' ), + array( false, 'https://example.org', 'http://example.org' ), + // Protocol relative servers always match http and https links + array( true, '//example.org', 'http://example.org' ), + array( true, '//example.org', 'https://example.org' ), + // But they don't match strange things like this + array( false, '//example.org', 'irc://example.org' ), + ); + } + + /** + * Test to make sure ParserOutput::isLinkInternal behaves properly + * @dataProvider provideIsLinkInternal + * @covers ParserOutput::isLinkInternal + */ + public function testIsLinkInternal( $shouldMatch, $server, $url ) { + $this->assertEquals( $shouldMatch, ParserOutput::isLinkInternal( $server, $url ) ); + } + + /** + * @covers ParserOutput::setExtensionData + * @covers ParserOutput::getExtensionData + */ + public function testExtensionData() { + $po = new ParserOutput(); + + $po->setExtensionData( "one", "Foo" ); + + $this->assertEquals( "Foo", $po->getExtensionData( "one" ) ); + $this->assertNull( $po->getExtensionData( "spam" ) ); + + $po->setExtensionData( "two", "Bar" ); + $this->assertEquals( "Foo", $po->getExtensionData( "one" ) ); + $this->assertEquals( "Bar", $po->getExtensionData( "two" ) ); + + $po->setExtensionData( "one", null ); + $this->assertNull( $po->getExtensionData( "one" ) ); + $this->assertEquals( "Bar", $po->getExtensionData( "two" ) ); + } +} diff --git a/tests/phpunit/includes/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php index 0e8ef530..d12fee36 100644 --- a/tests/phpunit/includes/parser/ParserPreloadTest.php +++ b/tests/phpunit/includes/parser/ParserPreloadTest.php @@ -4,12 +4,24 @@ * @author Antoine Musso */ class ParserPreloadTest extends MediaWikiTestCase { + /** + * @var Parser + */ private $testParser; + /** + * @var ParserOptions + */ private $testParserOptions; + /** + * @var Title + */ private $title; - function setUp() { - $this->testParserOptions = new ParserOptions(); + protected function setUp() { + global $wgContLang; + + parent::setUp(); + $this->testParserOptions = ParserOptions::newFromUserAndLang( new User, $wgContLang ); $this->testParser = new Parser(); $this->testParser->Options( $this->testParserOptions ); @@ -18,7 +30,9 @@ class ParserPreloadTest extends MediaWikiTestCase { $this->title = Title::newFromText( 'Preload Test' ); } - function tearDown() { + protected function tearDown() { + parent::tearDown(); + unset( $this->testParser ); unset( $this->title ); } @@ -26,14 +40,14 @@ class ParserPreloadTest extends MediaWikiTestCase { /** * @covers Parser::getPreloadText */ - function testPreloadSimpleText() { + public function testPreloadSimpleText() { $this->assertPreloaded( 'simple', 'simple' ); } /** * @covers Parser::getPreloadText */ - function testPreloadedPreIsUnstripped() { + public function testPreloadedPreIsUnstripped() { $this->assertPreloaded( '<pre>monospaced</pre>', '<pre>monospaced</pre>', @@ -44,7 +58,7 @@ class ParserPreloadTest extends MediaWikiTestCase { /** * @covers Parser::getPreloadText */ - function testPreloadedNowikiIsUnstripped() { + public function testPreloadedNowikiIsUnstripped() { $this->assertPreloaded( '<nowiki>[[Dummy title]]</nowiki>', '<nowiki>[[Dummy title]]</nowiki>', @@ -52,7 +66,7 @@ class ParserPreloadTest extends MediaWikiTestCase { ); } - function assertPreloaded( $expected, $text, $msg='') { + protected function assertPreloaded( $expected, $text, $msg = '' ) { $this->assertEquals( $expected, $this->testParser->getPreloadText( @@ -63,5 +77,4 @@ class ParserPreloadTest extends MediaWikiTestCase { $msg ); } - } diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php index fee56748..8aee937c 100644 --- a/tests/phpunit/includes/parser/PreprocessorTest.php +++ b/tests/phpunit/includes/parser/PreprocessorTest.php @@ -1,13 +1,21 @@ <?php class PreprocessorTest extends MediaWikiTestCase { - var $mTitle = 'Page title'; - var $mPPNodeCount = 0; - var $mOptions; + protected $mTitle = 'Page title'; + protected $mPPNodeCount = 0; + /** + * @var ParserOptions + */ + protected $mOptions; + /** + * @var Preprocessor + */ + protected $mPreprocessor; - function setUp() { - global $wgParserConf; - $this->mOptions = new ParserOptions(); + protected function setUp() { + global $wgParserConf, $wgContLang; + parent::setUp(); + $this->mOptions = ParserOptions::newFromUserAndLang( new User, $wgContLang ); $name = isset( $wgParserConf['preprocessorClass'] ) ? $wgParserConf['preprocessorClass'] : 'Preprocessor_DOM'; $this->mPreprocessor = new $name( $this ); @@ -17,7 +25,7 @@ class PreprocessorTest extends MediaWikiTestCase { return array( 'gallery', 'display map' /* Used by Maps, see r80025 CR */, '/foo' ); } - function provideCases() { + public static function provideCases() { return array( array( "Foo", "<root>Foo</root>" ), array( "<!-- Foo -->", "<root><comment><!-- Foo --></comment></root>" ), @@ -51,16 +59,16 @@ class PreprocessorTest extends MediaWikiTestCase { array( "Foo\n=\n==\n=\n", "<root>Foo\n=\n==\n=\n</root>" ), array( "{{Foo}}", "<root><template><title>Foo</title></template></root>" ), array( "\n{{Foo}}", "<root>\n<template lineStart=\"1\"><title>Foo</title></template></root>" ), - array( "{{Foo|bar}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), - array( "{{Foo|bar}}a", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), - array( "{{Foo|bar|baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|bar}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ), + array( "{{Foo|bar}}a", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ), + array( "{{Foo|bar|baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></template></root>" ), array( "{{Foo|1=bar}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ), array( "{{Foo|=bar}}", "<root><template><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ), - array( "{{Foo|bar=baz}}", "<root><template><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|bar=baz}}", "<root><template><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ), array( "{{Foo|{{bar}}=baz}}", "<root><template><title>Foo</title><part><name><template><title>bar</title></template></name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|1=bar|baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), + array( "{{Foo|1=bar|baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name index=\"1\" /><value>baz</value></part></template></root>" ), array( "{{Foo|1=bar|2=baz}}", "<root><template><title>Foo</title><part><name>1</name>=<value>bar</value></part><part><name>2</name>=<value>baz</value></part></template></root>" ), - array( "{{Foo|bar|foo=baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), + array( "{{Foo|bar|foo=baz}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name>foo</name>=<value>baz</value></part></template></root>" ), array( "{{{1}}}", "<root><tplarg><title>1</title></tplarg></root>" ), array( "{{{1|}}}", "<root><tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ), array( "{{{Foo}}}", "<root><tplarg><title>Foo</title></tplarg></root>" ), @@ -83,26 +91,26 @@ class PreprocessorTest extends MediaWikiTestCase { array( "Foo <gallery bar=\"baz\" />", "<root>Foo <ext><name>gallery</name><attr> bar="baz" </attr></ext></root>" ), array( "Foo <gallery bar=\"1\" baz=2 />", "<root>Foo <ext><name>gallery</name><attr> bar="1" baz=2 </attr></ext></root>" ), array( "</foo>Foo<//foo>", "<root><ext><name>/foo</name><attr></attr><inner>Foo</inner><close><//foo></close></ext></root>" ), # Worth blacklisting IMHO - array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>"), - array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>"), - array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>"), - array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>"), - array( "{{ {{Foo}}", "<root>{{ <template><title>Foo</title></template></root>"), - array( "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "<root>{{Foobar <template><title>Foo</title></template> <template><title>Bar</title></template> <template><title>Baz</title></template> </root>"), - array( "[[Foo]] |", "<root>[[Foo]] |</root>"), - array( "{{Foo|Bar|", "<root>{{Foo|Bar|</root>"), - array( "[[Foo]", "<root>[[Foo]</root>"), - array( "[[Foo|Bar]", "<root>[[Foo|Bar]</root>"), - array( "{{Foo| [[Bar] }}", "<root>{{Foo| [[Bar] }}</root>"), - array( "{{Foo| [[Bar|Baz] }}", "<root>{{Foo| [[Bar|Baz] }}</root>"), - array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>"), - array( "{{foo|", "<root>{{foo|</root>"), - array( "{{foo|}", "<root>{{foo|}</root>"), - array( "{{foo|} }}", "<root><template><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>"), - array( "{{foo|bar=|}", "<root>{{foo|bar=|}</root>"), - array( "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>"), - array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"), + array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template><title>#ifexpr: (<tplarg><title>1</title><part><name index=\"1\" /><value>1</value></part></tplarg> = 2) </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | Foo | {{Bar}} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> <template><title>Bar</title></template> </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | Foo | [[Bar]] }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> Foo </value></part><part><name index=\"2\" /><value> [[Bar]] </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | [[Foo]] | Bar }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> [[Foo]] </value></part><part><name index=\"2\" /><value> Bar </value></part></template></root>" ), + array( "{{#if: {{{1|}}} | 1 | {{#if: {{{1|}}} | 2 | 3 }} }}", "<root><template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 1 </value></part><part><name index=\"2\" /><value> <template><title>#if: <tplarg><title>1</title><part><name index=\"1\" /><value></value></part></tplarg> </title><part><name index=\"1\" /><value> 2 </value></part><part><name index=\"2\" /><value> 3 </value></part></template> </value></part></template></root>" ), + array( "{{ {{Foo}}", "<root>{{ <template><title>Foo</title></template></root>" ), + array( "{{Foobar {{Foo}} {{Bar}} {{Baz}} ", "<root>{{Foobar <template><title>Foo</title></template> <template><title>Bar</title></template> <template><title>Baz</title></template> </root>" ), + array( "[[Foo]] |", "<root>[[Foo]] |</root>" ), + array( "{{Foo|Bar|", "<root>{{Foo|Bar|</root>" ), + array( "[[Foo]", "<root>[[Foo]</root>" ), + array( "[[Foo|Bar]", "<root>[[Foo|Bar]</root>" ), + array( "{{Foo| [[Bar] }}", "<root>{{Foo| [[Bar] }}</root>" ), + array( "{{Foo| [[Bar|Baz] }}", "<root>{{Foo| [[Bar|Baz] }}</root>" ), + array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>" ), + array( "{{foo|", "<root>{{foo|</root>" ), + array( "{{foo|}", "<root>{{foo|}</root>" ), + array( "{{foo|} }}", "<root><template><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></root>" ), + array( "{{foo|bar=|}", "<root>{{foo|bar=|}</root>" ), + array( "{{Foo|} Bar=", "<root>{{Foo|} Bar=</root>" ), + array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>" ), /* array( file_get_contents( __DIR__ . '/QuoteQuran.txt' ), file_get_contents( __DIR__ . '/QuoteQuranExpanded.txt' ) ), */ ); } @@ -114,11 +122,11 @@ class PreprocessorTest extends MediaWikiTestCase { * @param string $wikiText * @return string */ - function preprocessToXml( $wikiText ) { + protected function preprocessToXml( $wikiText ) { if ( method_exists( $this->mPreprocessor, 'preprocessToXml' ) ) { return $this->normalizeXml( $this->mPreprocessor->preprocessToXml( $wikiText ) ); } - + $dom = $this->mPreprocessor->preprocessToObj( $wikiText ); if ( is_callable( array( $dom, 'saveXML' ) ) ) { return $dom->saveXML(); @@ -133,38 +141,36 @@ class PreprocessorTest extends MediaWikiTestCase { * @param string $xml * @return string */ - function normalizeXml( $xml ) { + protected function normalizeXml( $xml ) { return preg_replace( '!<([a-z]+)/>!', '<$1></$1>', str_replace( ' />', '/>', $xml ) ); - - $dom = new DOMDocument(); - // 1 << 19 == XML_PARSE_HUGE, needed so newer versions of libxml2 don't barf when the XML is >256 levels deep - $dom->loadXML( $xml, 1 << 19 ); - return $dom->saveXML(); } /** * @dataProvider provideCases + * @covers Preprocessor_DOM::preprocessToXml */ - function testPreprocessorOutput( $wikiText, $expectedXml ) { + public function testPreprocessorOutput( $wikiText, $expectedXml ) { $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } /** * These are more complex test cases taken out of wiki articles. */ - function provideFiles() { + public static function provideFiles() { return array( array( "QuoteQuran" ), # http://en.wikipedia.org/w/index.php?title=Template:QuoteQuran/sandbox&oldid=237348988 GFDL + CC-BY-SA by Striver array( "Factorial" ), # http://en.wikipedia.org/w/index.php?title=Template:Factorial&oldid=98548758 GFDL + CC-BY-SA by Polonium array( "All_system_messages" ), # http://tl.wiktionary.org/w/index.php?title=Suleras:All_system_messages&oldid=2765 GPL text generated by MediaWiki array( "Fundraising" ), # http://tl.wiktionary.org/w/index.php?title=MediaWiki:Sitenotice&oldid=5716 GFDL + CC-BY-SA, copied there by Sky Harbor. + array( "NestedTemplates" ), # bug 27936 ); } /** * @dataProvider provideFiles + * @covers Preprocessor_DOM::preprocessToXml */ - function testPreprocessorOutputFiles( $filename ) { + public function testPreprocessorOutputFiles( $filename ) { $folder = __DIR__ . "/../../../parser/preprocess"; $wikiText = file_get_contents( "$folder/$filename.txt" ); $output = $this->preprocessToXml( $wikiText ); @@ -183,8 +189,8 @@ class PreprocessorTest extends MediaWikiTestCase { /** * Tests from Bug 28642 · https://bugzilla.wikimedia.org/28642 */ - function provideHeadings() { - return array( /* These should become headings: */ + public static function provideHeadings() { + return array( /* These should become headings: */ array( "== h ==<!--c1-->", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment></h></root>" ), array( "== h == <!--c1-->", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment></h></root>" ), array( "== h ==<!--c1--> ", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> </h></root>" ), @@ -212,11 +218,11 @@ class PreprocessorTest extends MediaWikiTestCase { array( "== h == <!--c1--> <!--c2--><!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment><comment><!--c3--></comment> </h></root>" ), array( "== h == <!--c1--><!--c2--> <!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment><comment><!--c2--></comment> <comment><!--c3--></comment> </h></root>" ), array( "== h == <!--c1--> <!--c2--> <!--c3--> ", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment> <comment><!--c3--></comment> </h></root>" ), + array( "== h ==<!--c1--> <!--c2-->", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> <comment><!--c2--></comment></h></root>" ), + array( "== h == <!--c1--> <!--c2-->", "<root><h level=\"2\" i=\"1\">== h == <comment><!--c1--></comment> <comment><!--c2--></comment></h></root>" ), + array( "== h ==<!--c1--> <!--c2--> ", "<root><h level=\"2\" i=\"1\">== h ==<comment><!--c1--></comment> <comment><!--c2--></comment> </h></root>" ), /* These are not working: */ - array( "== h ==<!--c1--> <!--c2-->", "<root>== h ==<comment><!--c1--></comment> <comment><!--c2--></comment></root>" ), - array( "== h == <!--c1--> <!--c2-->", "<root>== h == <comment><!--c1--></comment> <comment><!--c2--></comment></root>" ), - array( "== h ==<!--c1--> <!--c2--> ", "<root>== h ==<comment><!--c1--></comment> <comment><!--c2--></comment> </root>" ), array( "== h == x <!--c1--><!--c2--><!--c3--> ", "<root>== h == x <comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> </root>" ), array( "== h ==<!--c1--> x <!--c2--><!--c3--> ", "<root>== h ==<comment><!--c1--></comment> x <comment><!--c2--></comment><comment><!--c3--></comment> </root>" ), array( "== h ==<!--c1--><!--c2--><!--c3--> x ", "<root>== h ==<comment><!--c1--></comment><comment><!--c2--></comment><comment><!--c3--></comment> x </root>" ), @@ -225,9 +231,9 @@ class PreprocessorTest extends MediaWikiTestCase { /** * @dataProvider provideHeadings + * @covers Preprocessor_DOM::preprocessToXml */ - function testHeadings( $wikiText, $expectedXml ) { + public function testHeadings( $wikiText, $expectedXml ) { $this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) ); } } - diff --git a/tests/phpunit/includes/parser/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php index 713ce846..61cbe45d 100644 --- a/tests/phpunit/includes/parser/TagHooksTest.php +++ b/tests/phpunit/includes/parser/TagHooksTest.php @@ -4,73 +4,78 @@ * @group Parser */ class TagHookTest extends MediaWikiTestCase { - public static function provideValidNames() { return array( array( 'foo' ), array( 'foo-bar' ), array( 'foo_bar' ), array( 'FOO-BAR' ), array( 'foo bar' ) ); } public static function provideBadNames() { - return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); + return array( array( "foo<bar" ), array( "foo>bar" ), array( "foo\nbar" ), array( "foo\rbar" ) ); } - + + protected function setUp() { + parent::setUp(); + + $this->setMwGlobals( 'wgAlwaysUseTidy', false ); + } + /** * @dataProvider provideValidNames */ - function testTagHooks( $tag ) { - global $wgParserConf; + public function testTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setHook( $tag, array( $this, 'tagCallback' ) ); - $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); + $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() ); - + $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle } - + /** * @dataProvider provideBadNames * @expectedException MWException */ - function testBadTagHooks( $tag ) { - global $wgParserConf; + public function testBadTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setHook( $tag, array( $this, 'tagCallback' ) ); - $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); - $this->fail('Exception not thrown.'); + $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); + $this->fail( 'Exception not thrown.' ); } - + /** * @dataProvider provideValidNames */ - function testFunctionTagHooks( $tag ) { - global $wgParserConf; + public function testFunctionTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), 0 ); - $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); + $parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); $this->assertEquals( "<p>FooOneBaz\n</p>", $parserOutput->getText() ); - + $parser->mPreprocessor = null; # Break the Parser <-> Preprocessor cycle } - + /** * @dataProvider provideBadNames * @expectedException MWException */ - function testBadFunctionTagHooks( $tag ) { - global $wgParserConf; + public function testBadFunctionTagHooks( $tag ) { + global $wgParserConf, $wgContLang; $parser = new Parser( $wgParserConf ); - + $parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), SFH_OBJECT_ARGS ); - $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions ); - $this->fail('Exception not thrown.'); + $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), ParserOptions::newFromUserAndLang( new User, $wgContLang ) ); + $this->fail( 'Exception not thrown.' ); } - + function tagCallback( $text, $params, $parser ) { return str_rot13( $text ); } - + function functionTagCallback( &$parser, $frame, $code, $attribs ) { return str_rot13( $code ); } diff --git a/tests/phpunit/includes/parser/TidyTest.php b/tests/phpunit/includes/parser/TidyTest.php new file mode 100644 index 00000000..57a88b9d --- /dev/null +++ b/tests/phpunit/includes/parser/TidyTest.php @@ -0,0 +1,44 @@ +<?php + +/** + * @group Parser + */ +class TidyTest extends MediaWikiTestCase { + public function setUp() { + parent::setUp(); + $check = MWTidy::tidy( '' ); + if ( strpos( $check, '<!--' ) !== false ) { + $this->markTestSkipped( 'Tidy not found' ); + } + } + + /** + * @dataProvider provideTestWrapping + */ + public function testTidyWrapping( $expected, $text, $msg = '' ) { + $text = MWTidy::tidy( $text ); + // We don't care about where Tidy wants to stick is <p>s + $text = trim( preg_replace( '#</?p>#', '', $text ) ); + // Windows, we love you! + $text = str_replace( "\r", '', $text ); + $this->assertEquals( $expected, $text, $msg ); + } + + public function provideTestWrapping() { + return array( + array( + '<mw:editsection page="foo" section="bar">foo</mw:editsection>', + '<mw:editsection page="foo" section="bar">foo</mw:editsection>', + '<mw:editsection> should survive tidy' + ), + array( + '<editsection page="foo" section="bar">foo</editsection>', + '<editsection page="foo" section="bar">foo</editsection>', + '<editsection> should survive tidy' + ), + array( '<mw:toc>foo</mw:toc>', '<mw:toc>foo</mw:toc>', '<mw:toc> should survive tidy' ), + array( "<link foo=\"bar\" />\nfoo", '<link foo="bar"/>foo', '<link> should survive tidy' ), + array( "<meta foo=\"bar\" />\nfoo", '<meta foo="bar"/>foo', '<meta> should survive tidy' ), + ); + } +}
\ No newline at end of file diff --git a/tests/phpunit/includes/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php index 957907c7..87067038 100644 --- a/tests/phpunit/includes/search/SearchEngineTest.php +++ b/tests/phpunit/includes/search/SearchEngineTest.php @@ -1,31 +1,27 @@ <?php /** - * This class is not directly tested. Instead it is extended by SearchDbTest. * @group Search * @group Database */ -class SearchEngineTest extends MediaWikiTestCase { +class SearchEngineTest extends MediaWikiLangTestCase { protected $search, $pageList; - function tearDown() { - unset( $this->search ); - } - /** * Checks for database type & version. * Will skip current test if DB does not support search. */ - function setUp() { + protected function setUp() { parent::setUp(); + // Search tests require MySQL or SQLite with FTS # Get database type and version $dbType = $this->db->getType(); $dbSupported = - ($dbType === 'mysql') - || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' ); + ( $dbType === 'mysql' ) + || ( $dbType === 'sqlite' && $this->db->getFulltextSearchModule() == 'FTS3' ); - if( !$dbSupported ) { + if ( !$dbSupported ) { $this->markTestSkipped( "MySQL or SQLite with FTS3 only" ); } @@ -33,6 +29,12 @@ class SearchEngineTest extends MediaWikiTestCase { $this->search = new $searchType( $this->db ); } + protected function tearDown() { + unset( $this->search ); + + parent::tearDown(); + } + function pageExists( $title ) { return false; } @@ -41,26 +43,37 @@ class SearchEngineTest extends MediaWikiTestCase { if ( $this->pageExists( 'Not_Main_Page' ) ) { return; } - $this->insertPage( "Not_Main_Page", "This is not a main page", 0 ); - $this->insertPage( 'Talk:Not_Main_Page', 'This is not a talk page to the main page, see [[smithee]]', 1 ); - $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 ); - $this->insertPage( 'Talk:Smithee', 'This article sucks.', 1 ); - $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.', 0 ); - $this->insertPage( 'Another_page', 'This page also is unrelated.', 0 ); - $this->insertPage( 'Help:Help', 'Help me!', 4 ); - $this->insertPage( 'Thppt', 'Blah blah', 0 ); - $this->insertPage( 'Alan_Smithee', 'yum', 0 ); - $this->insertPage( 'Pages', 'are\'food', 0 ); - $this->insertPage( 'HalfOneUp', 'AZ', 0 ); - $this->insertPage( 'FullOneUp', 'AZ', 0 ); - $this->insertPage( 'HalfTwoLow', 'az', 0 ); - $this->insertPage( 'FullTwoLow', 'az', 0 ); - $this->insertPage( 'HalfNumbers', '1234567890', 0 ); - $this->insertPage( 'FullNumbers', '1234567890', 0 ); - $this->insertPage( 'DomainName', 'example.com', 0 ); + + if ( !$this->isWikitextNS( NS_MAIN ) ) { + // @todo cover the case of non-wikitext content in the main namespace + return; + } + + $this->insertPage( "Not_Main_Page", "This is not a main page", 0 ); + $this->insertPage( 'Talk:Not_Main_Page', 'This is not a talk page to the main page, see [[smithee]]', 1 ); + $this->insertPage( 'Smithee', 'A smithee is one who smiths. See also [[Alan Smithee]]', 0 ); + $this->insertPage( 'Talk:Smithee', 'This article sucks.', 1 ); + $this->insertPage( 'Unrelated_page', 'Nothing in this page is about the S word.', 0 ); + $this->insertPage( 'Another_page', 'This page also is unrelated.', 0 ); + $this->insertPage( 'Help:Help', 'Help me!', 4 ); + $this->insertPage( 'Thppt', 'Blah blah', 0 ); + $this->insertPage( 'Alan_Smithee', 'yum', 0 ); + $this->insertPage( 'Pages', 'are\'food', 0 ); + $this->insertPage( 'HalfOneUp', 'AZ', 0 ); + $this->insertPage( 'FullOneUp', 'AZ', 0 ); + $this->insertPage( 'HalfTwoLow', 'az', 0 ); + $this->insertPage( 'FullTwoLow', 'az', 0 ); + $this->insertPage( 'HalfNumbers', '1234567890', 0 ); + $this->insertPage( 'FullNumbers', '1234567890', 0 ); + $this->insertPage( 'DomainName', 'example.com', 0 ); } function fetchIds( $results ) { + if ( !$this->isWikitextNS( NS_MAIN ) ) { + $this->markTestIncomplete( __CLASS__ . " does no yet support non-wikitext content " + . "in the main namespace" ); + } + $this->assertTrue( is_object( $results ) ); $matches = array(); @@ -74,6 +87,7 @@ class SearchEngineTest extends MediaWikiTestCase { # sort them numerically so we will compare simply that we received # the expected matches. sort( $matches ); + return $matches; } @@ -85,7 +99,7 @@ class SearchEngineTest extends MediaWikiTestCase { * @param $n Integer: unused */ function insertPage( $pageName, $text, $ns ) { - $title = Title::newFromText( $pageName ); + $title = Title::newFromText( $pageName, $ns ); $user = User::newFromName( 'WikiSysop' ); $comment = 'Search Test'; @@ -94,14 +108,14 @@ class SearchEngineTest extends MediaWikiTestCase { LinkCache::singleton()->clear(); $page = WikiPage::factory( $title ); - $page->doEdit( $text, $comment, 0, false, $user ); + $page->doEditContent( ContentHandler::makeContent( $text, $title ), $comment, 0, false, $user ); $this->pageList[] = array( $title, $page->getId() ); return true; } - function testFullWidth() { + public function testFullWidth() { $this->assertEquals( array( 'FullOneUp', 'FullTwoLow', 'HalfOneUp', 'HalfTwoLow' ), $this->fetchIds( $this->search->searchText( 'AZ' ) ), @@ -120,14 +134,14 @@ class SearchEngineTest extends MediaWikiTestCase { "Search for normalized from Full-width Lower" ); } - function testTextSearch() { + public function testTextSearch() { $this->assertEquals( - array( 'Smithee' ), - $this->fetchIds( $this->search->searchText( 'smithee' ) ), - "Plain search failed" ); + array( 'Smithee' ), + $this->fetchIds( $this->search->searchText( 'smithee' ) ), + "Plain search failed" ); } - function testTextPowerSearch() { + public function testTextPowerSearch() { $this->search->setNamespaces( array( 0, 1, 4 ) ); $this->assertEquals( array( @@ -138,7 +152,7 @@ class SearchEngineTest extends MediaWikiTestCase { "Power search failed" ); } - function testTitleSearch() { + public function testTitleSearch() { $this->assertEquals( array( 'Alan Smithee', @@ -148,7 +162,7 @@ class SearchEngineTest extends MediaWikiTestCase { "Title search failed" ); } - function testTextTitlePowerSearch() { + public function testTextTitlePowerSearch() { $this->search->setNamespaces( array( 0, 1, 4 ) ); $this->assertEquals( array( @@ -159,5 +173,4 @@ class SearchEngineTest extends MediaWikiTestCase { $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), "Title power search failed" ); } - } diff --git a/tests/phpunit/includes/search/SearchUpdateTest.php b/tests/phpunit/includes/search/SearchUpdateTest.php index 6e49a9a1..2f4fd501 100644 --- a/tests/phpunit/includes/search/SearchUpdateTest.php +++ b/tests/phpunit/includes/search/SearchUpdateTest.php @@ -17,36 +17,20 @@ class MockSearch extends SearchEngine { /** * @group Search + * @group Database */ class SearchUpdateTest extends MediaWikiTestCase { - static $searchType; - function update( $text, $title = 'Test', $id = 1 ) { - $u = new SearchUpdate( $id, $title, $text ); - $u->doUpdate(); - return array( MockSearch::$title, MockSearch::$text ); + protected function setUp() { + parent::setUp(); + $this->setMwGlobals( 'wgSearchType', 'MockSearch' ); } function updateText( $text ) { - list( , $resultText ) = $this->update( $text ); - $resultText = trim( $resultText ); // abstract from some implementation details - return $resultText; + return trim( SearchUpdate::updateText( $text ) ); } - function setUp() { - global $wgSearchType; - - self::$searchType = $wgSearchType; - $wgSearchType = 'MockSearch'; - } - - function tearDown() { - global $wgSearchType; - - $wgSearchType = self::$searchType; - } - - function testUpdateText() { + public function testUpdateText() { $this->assertEquals( 'test', $this->updateText( '<div>TeSt</div>' ), @@ -78,7 +62,7 @@ EOT ); } - function testBug32712() { + public function testBug32712() { $text = "text „http://example.com“ text"; $result = $this->updateText( $text ); $processed = preg_replace( '/Q/u', 'Q', $result ); diff --git a/tests/phpunit/includes/site/MediaWikiSiteTest.php b/tests/phpunit/includes/site/MediaWikiSiteTest.php new file mode 100644 index 00000000..c5d52d33 --- /dev/null +++ b/tests/phpunit/includes/site/MediaWikiSiteTest.php @@ -0,0 +1,90 @@ +<?php + +/** + * Tests for the MediaWikiSite class. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class MediaWikiSiteTest extends SiteTest { + + public function testNormalizePageTitle() { + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiki' ); + + //NOTE: this does not actually call out to the enwiki site to perform the normalization, + // but uses a local Title object to do so. This is hardcoded on SiteLink::normalizePageTitle + // for the case that MW_PHPUNIT_TEST is set. + $this->assertEquals( 'Foo', $site->normalizePageName( ' foo ' ) ); + } + + public function fileUrlProvider() { + return array( + // url, filepath, path arg, expected + array( 'https://en.wikipedia.org', '/w/$1', 'api.php', 'https://en.wikipedia.org/w/api.php' ), + array( 'https://en.wikipedia.org', '/w/', 'api.php', 'https://en.wikipedia.org/w/' ), + array( 'https://en.wikipedia.org', '/foo/page.php?name=$1', 'api.php', 'https://en.wikipedia.org/foo/page.php?name=api.php' ), + array( 'https://en.wikipedia.org', '/w/$1', '', 'https://en.wikipedia.org/w/' ), + array( 'https://en.wikipedia.org', '/w/$1', 'foo/bar/api.php', 'https://en.wikipedia.org/w/foo/bar/api.php' ), + ); + } + + /** + * @dataProvider fileUrlProvider + * @covers MediaWikiSite::getFileUrl + */ + public function testGetFileUrl( $url, $filePath, $pathArgument, $expected ) { + $site = new MediaWikiSite(); + $site->setFilePath( $url . $filePath ); + + $this->assertEquals( $expected, $site->getFileUrl( $pathArgument ) ); + } + + public static function provideGetPageUrl() { + return array( + // path, page, expected substring + array( 'http://acme.test/wiki/$1', 'Berlin', '/wiki/Berlin' ), + array( 'http://acme.test/wiki/', 'Berlin', '/wiki/' ), + array( 'http://acme.test/w/index.php?title=$1', 'Berlin', '/w/index.php?title=Berlin' ), + array( 'http://acme.test/wiki/$1', '', '/wiki/' ), + array( 'http://acme.test/wiki/$1', 'Berlin/sub page', '/wiki/Berlin/sub_page' ), + array( 'http://acme.test/wiki/$1', 'Cork (city) ', '/Cork_(city)' ), + array( 'http://acme.test/wiki/$1', 'M&M', '/wiki/M%26M' ), + ); + } + + /** + * @dataProvider provideGetPageUrl + * @covers MediaWikiSite::getPageUrl + */ + public function testGetPageUrl( $path, $page, $expected ) { + $site = new MediaWikiSite(); + $site->setLinkPath( $path ); + + $this->assertContains( $path, $site->getPageUrl() ); + $this->assertContains( $expected, $site->getPageUrl( $page ) ); + } +} diff --git a/tests/phpunit/includes/site/SiteListTest.php b/tests/phpunit/includes/site/SiteListTest.php new file mode 100644 index 00000000..8af2fc1b --- /dev/null +++ b/tests/phpunit/includes/site/SiteListTest.php @@ -0,0 +1,197 @@ +<?php + +/** + * Tests for the SiteList class. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteListTest extends MediaWikiTestCase { + + /** + * Returns instances of SiteList implementing objects. + * @return array + */ + public function siteListProvider() { + $sitesArrays = $this->siteArrayProvider(); + + $listInstances = array(); + + foreach ( $sitesArrays as $sitesArray ) { + $listInstances[] = new SiteList( $sitesArray[0] ); + } + + return $this->arrayWrap( $listInstances ); + } + + /** + * Returns arrays with instances of Site implementing objects. + * @return array + */ + public function siteArrayProvider() { + $sites = TestSites::getSites(); + + $siteArrays = array(); + + $siteArrays[] = $sites; + + $siteArrays[] = array( array_shift( $sites ) ); + + $siteArrays[] = array( array_shift( $sites ), array_shift( $sites ) ); + + return $this->arrayWrap( $siteArrays ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::isEmpty + */ + public function testIsEmpty( SiteList $sites ) { + $this->assertEquals( count( $sites ) === 0, $sites->isEmpty() ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getSite + */ + public function testGetSiteByGlobalId( SiteList $sites ) { + if ( $sites->isEmpty() ) { + $this->assertTrue( true ); + } else { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertEquals( $site, $sites->getSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getSiteByInternalId + */ + public function testGetSiteByInternalId( $sites ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + if ( is_integer( $site->getInternalId() ) ) { + $this->assertEquals( $site, $sites->getSiteByInternalId( $site->getInternalId() ) ); + } + } + + $this->assertTrue( true ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::hasSite + */ + public function testHasGlobalId( $sites ) { + $this->assertFalse( $sites->hasSite( 'non-existing-global-id' ) ); + $this->assertFalse( $sites->hasInternalId( 720101010 ) ); + + if ( !$sites->isEmpty() ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::hasInternalId + */ + public function testHasInternallId( $sites ) { + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + if ( is_integer( $site->getInternalId() ) ) { + $this->assertTrue( $site, $sites->hasInternalId( $site->getInternalId() ) ); + } + } + + $this->assertFalse( $sites->hasInternalId( -1 ) ); + } + + /** + * @dataProvider siteListProvider + * @param SiteList $sites + * @covers SiteList::getGlobalIdentifiers + */ + public function testGetGlobalIdentifiers( SiteList $sites ) { + $identifiers = $sites->getGlobalIdentifiers(); + + $this->assertTrue( is_array( $identifiers ) ); + + $expected = array(); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $expected[] = $site->getGlobalId(); + } + + $this->assertArrayEquals( $expected, $identifiers ); + } + + /** + * @dataProvider siteListProvider + * + * @since 1.21 + * + * @param SiteList $list + * @covers SiteList::getSerializationData + * @covers SiteList::unserialize + */ + public function testSerialization( SiteList $list ) { + $serialization = serialize( $list ); + /** + * @var SiteArray $copy + */ + $copy = unserialize( $serialization ); + + $this->assertArrayEquals( $list->getGlobalIdentifiers(), $copy->getGlobalIdentifiers() ); + + /** + * @var Site $site + */ + foreach ( $list as $site ) { + $this->assertTrue( $copy->hasInternalId( $site->getInternalId() ) ); + } + } +} diff --git a/tests/phpunit/includes/site/SiteSQLStoreTest.php b/tests/phpunit/includes/site/SiteSQLStoreTest.php new file mode 100644 index 00000000..6002c1a1 --- /dev/null +++ b/tests/phpunit/includes/site/SiteSQLStoreTest.php @@ -0,0 +1,134 @@ +<?php + +/** + * Tests for the SiteSQLStore class. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * @group Database + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteSQLStoreTest extends MediaWikiTestCase { + + /** + * @covers SiteSQLStore::getSites + */ + public function testGetSites() { + $expectedSites = TestSites::getSites(); + TestSites::insertIntoDb(); + + $store = SiteSQLStore::newInstance(); + + $sites = $store->getSites(); + + $this->assertInstanceOf( 'SiteList', $sites ); + + /** + * @var Site $site + */ + foreach ( $sites as $site ) { + $this->assertInstanceOf( 'Site', $site ); + } + + foreach ( $expectedSites as $site ) { + if ( $site->getGlobalId() !== null ) { + $this->assertTrue( $sites->hasSite( $site->getGlobalId() ) ); + } + } + } + + /** + * @covers SiteSQLStore::saveSites + */ + public function testSaveSites() { + $store = SiteSQLStore::newInstance(); + + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'ertrywuutr' ); + $site->setLanguageCode( 'en' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'sdfhxujgkfpth' ); + $site->setLanguageCode( 'nl' ); + $sites[] = $site; + + $this->assertTrue( $store->saveSites( $sites ) ); + + $site = $store->getSite( 'ertrywuutr' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + + $site = $store->getSite( 'sdfhxujgkfpth' ); + $this->assertInstanceOf( 'Site', $site ); + $this->assertEquals( 'nl', $site->getLanguageCode() ); + $this->assertTrue( is_integer( $site->getInternalId() ) ); + $this->assertTrue( $site->getInternalId() >= 0 ); + } + + /** + * @covers SiteSQLStore::reset + */ + public function testReset() { + $store1 = SiteSQLStore::newInstance(); + $store2 = SiteSQLStore::newInstance(); + + // initialize internal cache + $this->assertGreaterThan( 0, $store1->getSites()->count() ); + $this->assertGreaterThan( 0, $store2->getSites()->count() ); + + // Clear actual data. Will purge the external cache and reset the internal + // cache in $store1, but not the internal cache in store2. + $this->assertTrue( $store1->clear() ); + + // sanity check: $store2 should have a stale cache now + $this->assertNotNull( $store2->getSite( 'enwiki' ) ); + + // purge cache + $store2->reset(); + + // ...now the internal cache of $store2 should be updated and thus empty. + $site = $store2->getSite( 'enwiki' ); + $this->assertNull( $site ); + } + + /** + * @covers SiteSQLStore::clear + */ + public function testClear() { + $store = SiteSQLStore::newInstance(); + $this->assertTrue( $store->clear() ); + + $site = $store->getSite( 'enwiki' ); + $this->assertNull( $site ); + + $sites = $store->getSites(); + $this->assertEquals( 0, $sites->count() ); + } +} diff --git a/tests/phpunit/includes/site/SiteTest.php b/tests/phpunit/includes/site/SiteTest.php new file mode 100644 index 00000000..29c1ff33 --- /dev/null +++ b/tests/phpunit/includes/site/SiteTest.php @@ -0,0 +1,296 @@ +<?php + +/** + * Tests for the Site class. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class SiteTest extends MediaWikiTestCase { + + public function instanceProvider() { + return $this->arrayWrap( TestSites::getSites() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getInterwikiIds + */ + public function testGetInterwikiIds( Site $site ) { + $this->assertInternalType( 'array', $site->getInterwikiIds() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getNavigationIds + */ + public function testGetNavigationIds( Site $site ) { + $this->assertInternalType( 'array', $site->getNavigationIds() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::addNavigationId + */ + public function testAddNavigationId( Site $site ) { + $site->addNavigationId( 'foobar' ); + $this->assertTrue( in_array( 'foobar', $site->getNavigationIds(), true ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::addInterwikiId + */ + public function testAddInterwikiId( Site $site ) { + $site->addInterwikiId( 'foobar' ); + $this->assertTrue( in_array( 'foobar', $site->getInterwikiIds(), true ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getLanguageCode + */ + public function testGetLanguageCode( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getLanguageCode(), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setLanguageCode + */ + public function testSetLanguageCode( Site $site ) { + $site->setLanguageCode( 'en' ); + $this->assertEquals( 'en', $site->getLanguageCode() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::normalizePageName + */ + public function testNormalizePageName( Site $site ) { + $this->assertInternalType( 'string', $site->normalizePageName( 'Foobar' ) ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getGlobalId + */ + public function testGetGlobalId( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getGlobalId(), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setGlobalId + */ + public function testSetGlobalId( Site $site ) { + $site->setGlobalId( 'foobar' ); + $this->assertEquals( 'foobar', $site->getGlobalId() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getType + */ + public function testGetType( Site $site ) { + $this->assertInternalType( 'string', $site->getType() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getPath + */ + public function testGetPath( Site $site ) { + $this->assertTypeOrValue( 'string', $site->getPath( 'page_path' ), null ); + $this->assertTypeOrValue( 'string', $site->getPath( 'file_path' ), null ); + $this->assertTypeOrValue( 'string', $site->getPath( 'foobar' ), null ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::getAllPaths + */ + public function testGetAllPaths( Site $site ) { + $this->assertInternalType( 'array', $site->getAllPaths() ); + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::setPath + * @covers Site::removePath + */ + public function testSetAndRemovePath( Site $site ) { + $count = count( $site->getAllPaths() ); + + $site->setPath( 'spam', 'http://www.wikidata.org/$1' ); + $site->setPath( 'spam', 'http://www.wikidata.org/foo/$1' ); + $site->setPath( 'foobar', 'http://www.wikidata.org/bar/$1' ); + + $this->assertEquals( $count + 2, count( $site->getAllPaths() ) ); + + $this->assertInternalType( 'string', $site->getPath( 'foobar' ) ); + $this->assertEquals( 'http://www.wikidata.org/foo/$1', $site->getPath( 'spam' ) ); + + $site->removePath( 'spam' ); + $site->removePath( 'foobar' ); + + $this->assertEquals( $count, count( $site->getAllPaths() ) ); + + $this->assertNull( $site->getPath( 'foobar' ) ); + $this->assertNull( $site->getPath( 'spam' ) ); + } + + /** + * @covers Site::setLinkPath + */ + public function testSetLinkPath() { + $site = new Site(); + $path = "TestPath/$1"; + + $site->setLinkPath( $path ); + $this->assertEquals( $path, $site->getLinkPath() ); + } + + /** + * @covers Site::getLinkPathType + */ + public function testGetLinkPathType() { + $site = new Site(); + + $path = 'TestPath/$1'; + $site->setLinkPath( $path ); + $this->assertEquals( $path, $site->getPath( $site->getLinkPathType() ) ); + + $path = 'AnotherPath/$1'; + $site->setPath( $site->getLinkPathType(), $path ); + $this->assertEquals( $path, $site->getLinkPath() ); + } + + /** + * @covers Site::setPath + */ + public function testSetPath() { + $site = new Site(); + + $path = 'TestPath/$1'; + $site->setPath( 'foo', $path ); + + $this->assertEquals( $path, $site->getPath( 'foo' ) ); + } + + /** + * @covers Site::setPath + * @covers Site::getProtocol + */ + public function testProtocolRelativePath() { + $site = new Site(); + + $type = $site->getLinkPathType(); + $path = '//acme.com/'; // protocol-relative URL + $site->setPath( $type, $path ); + + $this->assertEquals( '', $site->getProtocol() ); + } + + public static function provideGetPageUrl() { + //NOTE: the assumption that the URL is built by replacing $1 + // with the urlencoded version of $page + // is true for Site but not guaranteed for subclasses. + // Subclasses need to override this provider appropriately. + + return array( + array( #0 + 'http://acme.test/TestPath/$1', + 'Foo', + '/TestPath/Foo', + ), + array( #1 + 'http://acme.test/TestScript?x=$1&y=bla', + 'Foo', + 'TestScript?x=Foo&y=bla', + ), + array( #2 + 'http://acme.test/TestPath/$1', + 'foo & bar/xyzzy (quux-shmoox?)', + '/TestPath/foo%20%26%20bar%2Fxyzzy%20%28quux-shmoox%3F%29', + ), + ); + } + + /** + * @dataProvider provideGetPageUrl + * @covers Site::getPageUrl + */ + public function testGetPageUrl( $path, $page, $expected ) { + $site = new Site(); + + //NOTE: the assumption that getPageUrl is based on getLinkPath + // is true for Site but not guaranteed for subclasses. + // Subclasses need to override this test case appropriately. + $site->setLinkPath( $path ); + $this->assertContains( $path, $site->getPageUrl() ); + + $this->assertContains( $expected, $site->getPageUrl( $page ) ); + } + + protected function assertTypeOrFalse( $type, $value ) { + if ( $value === false ) { + $this->assertTrue( true ); + } else { + $this->assertInternalType( $type, $value ); + } + } + + /** + * @dataProvider instanceProvider + * @param Site $site + * @covers Site::serialize + * @covers Site::unserialize + */ + public function testSerialization( Site $site ) { + $this->assertInstanceOf( 'Serializable', $site ); + + $serialization = serialize( $site ); + $newInstance = unserialize( $serialization ); + + $this->assertInstanceOf( 'Site', $newInstance ); + + $this->assertEquals( $serialization, serialize( $newInstance ) ); + } +} diff --git a/tests/phpunit/includes/site/TestSites.php b/tests/phpunit/includes/site/TestSites.php new file mode 100644 index 00000000..f224b7d7 --- /dev/null +++ b/tests/phpunit/includes/site/TestSites.php @@ -0,0 +1,100 @@ +<?php + +/** + * Holds sites for testing purposes. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @since 1.21 + * + * @ingroup Site + * @ingroup Test + * + * @group Site + * + * @licence GNU GPL v2+ + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +class TestSites { + + /** + * @since 1.21 + * + * @return array + */ + public static function getSites() { + $sites = array(); + + $site = new Site(); + $site->setGlobalId( 'foobar' ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'enwiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'enwiktionary' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://en.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://en.wiktionary.org/w/$1" ); + $sites[] = $site; + + $site = new MediaWikiSite(); + $site->setGlobalId( 'dewiktionary' ); + $site->setGroup( 'wiktionary' ); + $site->setLanguageCode( 'de' ); + $site->addInterwikiId( 'dewiktionary' ); + $site->addInterwikiId( 'wiktionaryde' ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://de.wiktionary.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://de.wiktionary.org/w/$1" ); + $sites[] = $site; + + $site = new Site(); + $site->setGlobalId( 'spam' ); + $site->setGroup( 'spam' ); + $site->setLanguageCode( 'en' ); + $site->addNavigationId( 'spam' ); + $site->addNavigationId( 'spamz' ); + $site->addInterwikiId( 'spamzz' ); + $site->setLinkPath( "http://spamzz.test/testing/" ); + $sites[] = $site; + + foreach ( array( 'en', 'de', 'nl', 'sv', 'sr', 'no', 'nn' ) as $langCode ) { + $site = new MediaWikiSite(); + $site->setGlobalId( $langCode . 'wiki' ); + $site->setGroup( 'wikipedia' ); + $site->setLanguageCode( $langCode ); + $site->addInterwikiId( $langCode ); + $site->addNavigationId( $langCode ); + $site->setPath( MediaWikiSite::PATH_PAGE, "https://$langCode.wikipedia.org/wiki/$1" ); + $site->setPath( MediaWikiSite::PATH_FILE, "https://$langCode.wikipedia.org/w/$1" ); + $sites[] = $site; + } + + return $sites; + } + + /** + * Inserts sites into the database for the unit tests that need them. + * + * @since 0.1 + */ + public static function insertIntoDb() { + $sitesTable = SiteSQLStore::newInstance(); + $sitesTable->clear(); + $sitesTable->saveSites( TestSites::getSites() ); + } +} diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php index a33c7b68..a806b4ac 100644 --- a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php +++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php @@ -39,9 +39,9 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { parent::__construct(); global $wgQueryPages; - foreach( $wgQueryPages as $page ) { + foreach ( $wgQueryPages as $page ) { $class = $page[0]; - if( ! in_array( $class, $this->manualTest ) ) { + if ( !in_array( $class, $this->manualTest ) ) { $this->queryPages[$class] = new $class; } } @@ -51,25 +51,25 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { * Test SQL for each of our QueryPages objects * @group Database */ - function testQuerypageSqlQuery() { + public function testQuerypageSqlQuery() { global $wgDBtype; - foreach( $this->queryPages as $page ) { + foreach ( $this->queryPages as $page ) { // With MySQL, skips special pages reopening a temporary table // See http://bugs.mysql.com/bug.php?id=10327 - if( + if ( $wgDBtype === 'mysql' && in_array( $page->getName(), $this->reopensTempTable ) ) { - $this->markTestSkipped( "SQL query for page {$page->getName()} can not be tested on MySQL backend (it reopens a temporary table)" ); - continue; - } + $this->markTestSkipped( "SQL query for page {$page->getName()} can not be tested on MySQL backend (it reopens a temporary table)" ); + continue; + } - $msg = "SQL query for page {$page->getName()} should give a result wrapper object" ; + $msg = "SQL query for page {$page->getName()} should give a result wrapper object"; $result = $page->reallyDoQuery( 50 ); - if( $result instanceof ResultWrapper ) { + if ( $result instanceof ResultWrapper ) { $this->assertTrue( true, $msg ); } else { $this->assertFalse( false, $msg ); diff --git a/tests/phpunit/includes/specials/SpecialPreferencesTest.php b/tests/phpunit/includes/specials/SpecialPreferencesTest.php new file mode 100644 index 00000000..6c637c65 --- /dev/null +++ b/tests/phpunit/includes/specials/SpecialPreferencesTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Test class for SpecialPreferences class. + * + * Copyright © 2013, Antoine Musso + * Copyright © 2013, Wikimedia Foundation Inc. + * + */ + +class SpecialPreferencesTest extends MediaWikiTestCase { + + /** + * Make sure a nickname which is longer than $wgMaxSigChars + * is not throwing a fatal error. + * + * Test specifications by Alexandre "ialex" Emsenhuber. + */ + public function testBug41337() { + + // Set a low limit + $this->setMwGlobals( 'wgMaxSigChars', 2 ); + + $user = $this->getMock( 'User' ); + $user->expects( $this->any() ) + ->method( 'isAnon' ) + ->will( $this->returnValue( false ) ); + + # Yeah foreach requires an array, not NULL =( + $user->expects( $this->any() ) + ->method( 'getEffectiveGroups' ) + ->will( $this->returnValue( array() ) ); + + # The mocked user has a long nickname + $user->expects( $this->any() ) + ->method( 'getOption' ) + ->will( $this->returnValueMap( array( + array( 'nickname', null, false, 'superlongnickname' ), + ) + ) ); + + # Validate the mock (FIXME should probably be removed) + $this->assertFalse( $user->isAnon() ); + $this->assertEquals( array(), + $user->getEffectiveGroups() ); + $this->assertEquals( 'superlongnickname', + $user->getOption( 'nickname' ) ); + + # Forge a request to call the special page + $context = new RequestContext(); + $context->setRequest( new FauxRequest() ); + $context->setUser( $user ); + $context->setTitle( Title::newFromText( 'Test' ) ); + + # Do the call, should not spurt a fatal error. + $special = new SpecialPreferences(); + $special->setContext( $context ); + $special->execute( array() ); + } + +} diff --git a/tests/phpunit/includes/specials/SpecialRecentchangesTest.php b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php index 2e4f4b09..436eb2e2 100644 --- a/tests/phpunit/includes/specials/SpecialRecentchangesTest.php +++ b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php @@ -14,9 +14,6 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { */ protected $rc; - function setUp() { - } - /** helper to test SpecialRecentchanges::buildMainQueryConds() */ private function assertConditions( $expected, $requestOptions = null, $message = '' ) { $context = new RequestContext; @@ -44,8 +41,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { /** return false if condition begin with 'rc_timestamp ' */ private static function filterOutRcTimestampCondition( $var ) { - return (false === strpos( $var, 'rc_timestamp ' )); - + return ( false === strpos( $var, 'rc_timestamp ' ) ); } public function testRcNsFilter() { @@ -73,7 +69,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 'namespace' => NS_MAIN, 'invert' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -92,7 +88,7 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 'namespace' => $ns1, 'associated' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -108,11 +104,11 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { 1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ), ), array( - 'namespace' => $ns1, + 'namespace' => $ns1, 'associated' => 1, - 'invert' => 1, + 'invert' => 1, ), - "rc conditions with namespace inverted" + "rc conditions with namespace inverted" ); } @@ -120,13 +116,10 @@ class SpecialRecentchangesTest extends MediaWikiTestCase { * Provides associated namespaces to test recent changes * namespaces association filtering. */ - public function provideNamespacesAssociations() { + public static function provideNamespacesAssociations() { return array( # (NS => Associated_NS) - array( NS_MAIN, NS_TALK), - array( NS_TALK, NS_MAIN), + array( NS_MAIN, NS_TALK ), + array( NS_TALK, NS_MAIN ), ); } - } - - diff --git a/tests/phpunit/includes/specials/SpecialSearchTest.php b/tests/phpunit/includes/specials/SpecialSearchTest.php index 20e42a68..17e883fd 100644 --- a/tests/phpunit/includes/specials/SpecialSearchTest.php +++ b/tests/phpunit/includes/specials/SpecialSearchTest.php @@ -10,9 +10,6 @@ class SpecialSearchTest extends MediaWikiTestCase { private $search; - function setUp() { } - function tearDown() { } - /** * @covers SpecialSearch::load * @dataProvider provideSearchOptionsTests @@ -21,7 +18,7 @@ class SpecialSearchTest extends MediaWikiTestCase { * @param $expectedProfile An expected search profile name * @param $expectedNs Array Expected namespaces */ - function testProfileAndNamespaceLoading( + public function testProfileAndNamespaceLoading( $requested, $userOptions, $expectedProfile, $expectedNS, $message = 'Profile name and namespaces mismatches!' ) { @@ -35,7 +32,7 @@ class SpecialSearchTest extends MediaWikiTestCase { 'ns6'=>true, ) )); */ - $context->setRequest( new FauxRequest( $requested )); + $context->setRequest( new FauxRequest( $requested ) ); $search = new SpecialSearch(); $search->setContext( $context ); $search->load(); @@ -48,28 +45,27 @@ class SpecialSearchTest extends MediaWikiTestCase { $this->assertEquals( array( /** Expected: */ 'ProfileName' => $expectedProfile, - 'Namespaces' => $expectedNS, + 'Namespaces' => $expectedNS, ) , array( /** Actual: */ 'ProfileName' => $search->getProfile(), - 'Namespaces' => $search->getNamespaces(), + 'Namespaces' => $search->getNamespaces(), ) , $message ); - } - function provideSearchOptionsTests() { + public static function provideSearchOptionsTests() { $defaultNS = SearchEngine::defaultNamespaces(); $EMPTY_REQUEST = array(); - $NO_USER_PREF = null; + $NO_USER_PREF = null; return array( /** * Parameters: - * <Web Request>, <User options> + * <Web Request>, <User options> * Followed by expected values: - * <ProfileName>, <NSList> + * <ProfileName>, <NSList> * Then an optional message. */ array( @@ -79,21 +75,19 @@ class SpecialSearchTest extends MediaWikiTestCase { ), array( array( 'ns5' => 1 ), $NO_USER_PREF, - 'advanced', array( 5), + 'advanced', array( 5 ), 'Web request with specific NS should override user preference' ), array( - $EMPTY_REQUEST, array( 'searchNs2' => 1, 'searchNs14' => 1 ), - 'advanced', array( 2, 14 ), - 'Bug 33583: search with no option should honor User search preferences' - ), - array( - $EMPTY_REQUEST, array_fill_keys( array_map( function( $ns ) { - return "searchNs$ns"; - }, $defaultNS ), 0 ) + array( 'searchNs2' => 1, 'searchNs14' => 1 ), + $EMPTY_REQUEST, array( + 'searchNs2' => 1, + 'searchNs14' => 1, + ) + array_fill_keys( array_map( function ( $ns ) { + return "searchNs$ns"; + }, $defaultNS ), 0 ), 'advanced', array( 2, 14 ), 'Bug 33583: search with no option should honor User search preferences' - . 'and have all other namespace disabled' + . ' and have all other namespace disabled' ), ); } @@ -103,14 +97,43 @@ class SpecialSearchTest extends MediaWikiTestCase { * User remains anonymous though */ function newUserWithSearchNS( $opt = null ) { - $u = User::newFromId(0); - if( $opt === null ) { + $u = User::newFromId( 0 ); + if ( $opt === null ) { return $u; } - foreach($opt as $name => $value) { + foreach ( $opt as $name => $value ) { $u->setOption( $name, $value ); } + return $u; } -} + /** + * Verify we do not expand search term in <title> on search result page + * https://gerrit.wikimedia.org/r/4841 + */ + public function testSearchTermIsNotExpanded() { + + # Initialize [[Special::Search]] + $search = new SpecialSearch(); + $search->getContext()->setTitle( Title::newFromText( 'Special:Search' ) ); + $search->load(); + + # Simulate a user searching for a given term + $term = '{{SITENAME}}'; + $search->showResults( $term ); + + # Lookup the HTML page title set for that page + $pageTitle = $search + ->getContext() + ->getOutput() + ->getHTMLTitle(); + + # Compare :-] + $this->assertRegExp( + '/' . preg_quote( $term ) . '/', + $pageTitle, + "Search term '{$term}' should not be expanded in Special:Search <title>" + ); + } +} diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadBaseTest.php index 6948f5b1..982b46b2 100644 --- a/tests/phpunit/includes/upload/UploadTest.php +++ b/tests/phpunit/includes/upload/UploadBaseTest.php @@ -1,33 +1,38 @@ <?php + /** * @group Upload */ -class UploadTest extends MediaWikiTestCase { - protected $upload; +class UploadBaseTest extends MediaWikiTestCase { + /** @var UploadTestHandler */ + protected $upload; - function setUp() { + protected function setUp() { global $wgHooks; parent::setUp(); $this->upload = new UploadTestHandler; $this->hooks = $wgHooks; - $wgHooks['InterwikiLoadPrefix'][] = function( $prefix, &$data ) { + $wgHooks['InterwikiLoadPrefix'][] = function ( $prefix, &$data ) { return false; }; } - function tearDown() { + protected function tearDown() { global $wgHooks; $wgHooks = $this->hooks; + + parent::tearDown(); } /** * First checks the return code * of UploadBase::getTitle() and then the actual returned title - * - * @dataProvider dataTestTitleValidation + * + * @dataProvider provideTestTitleValidation + * @covers UploadBase::getTitle */ public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) { /* Check the result code */ @@ -42,44 +47,45 @@ class UploadTest extends MediaWikiTestCase { "$msg text" ); } } - + /** * Test various forms of valid and invalid titles that can be supplied. */ - public function dataTestTitleValidation() { + public static function provideTestTitleValidation() { return array( /* Test a valid title */ - array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, + array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, 'upload valid title' ), /* A title with a slash */ - array( 'A/B.jpg', 'B.jpg', UploadBase::OK, + array( 'A/B.jpg', 'B.jpg', UploadBase::OK, 'upload title with slash' ), /* A title with illegal char */ - array( 'A:B.jpg', 'A-B.jpg', UploadBase::OK, + array( 'A:B.jpg', 'A-B.jpg', UploadBase::OK, 'upload title with colon' ), /* Stripping leading File: prefix */ - array( 'File:C.jpg', 'C.jpg', UploadBase::OK, + array( 'File:C.jpg', 'C.jpg', UploadBase::OK, 'upload title with File prefix' ), /* Test illegal suggested title (r94601) */ - array( '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, + array( '%281%29.JPG', null, UploadBase::ILLEGAL_FILENAME, 'illegal title for upload' ), /* A title without extension */ - array( 'A', null, UploadBase::FILETYPE_MISSING, + array( 'A', null, UploadBase::FILETYPE_MISSING, 'upload title without extension' ), /* A title with no basename */ - array( '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, + array( '.jpg', null, UploadBase::MIN_LENGTH_PARTNAME, 'upload title without basename' ), /* A title that is longer than 255 bytes */ - array( str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + array( str_repeat( 'a', 255 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 255 bytes' ), /* A title that is longer than 240 bytes */ - array( str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, + array( str_repeat( 'a', 240 ) . '.jpg', null, UploadBase::FILENAME_TOO_LONG, 'upload title longer than 240 bytes' ), ); } /** * Test the upload verification functions + * @covers UploadBase::verifyUpload */ public function testVerifyUpload() { /* Setup with zero file size */ @@ -106,7 +112,6 @@ class UploadTest extends MediaWikiTestCase { * * This method should be abstracted so we can test different settings. */ - public function testMaxUploadSize() { global $wgMaxUploadSize; $savedGlobal = $wgMaxUploadSize; // save global @@ -116,26 +121,27 @@ class UploadTest extends MediaWikiTestCase { $wgMaxUploadSize = 100; $filename = $this->createFileOfSize( $wgMaxUploadSize ); - $this->upload->initializePathInfo( basename($filename) . '.txt', $filename, 100 ); + $this->upload->initializePathInfo( basename( $filename ) . '.txt', $filename, 100 ); $result = $this->upload->verifyUpload(); unlink( $filename ); $this->assertEquals( array( 'status' => UploadBase::OK ), $result ); - $wgMaxUploadSize = $savedGlobal; // restore global + $wgMaxUploadSize = $savedGlobal; // restore global } } class UploadTestHandler extends UploadBase { - public function initializeFromRequest( &$request ) { } - public function testTitleValidation( $name ) { - $this->mTitle = false; - $this->mDesiredDestName = $name; - $this->mTitleError = UploadBase::OK; - $this->getTitle(); - return $this->mTitleError; - } + public function initializeFromRequest( &$request ) { + } + public function testTitleValidation( $name ) { + $this->mTitle = false; + $this->mDesiredDestName = $name; + $this->mTitleError = UploadBase::OK; + $this->getTitle(); + return $this->mTitleError; + } } diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php index f66c387b..a75fba69 100644 --- a/tests/phpunit/includes/upload/UploadFromUrlTest.php +++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php @@ -3,16 +3,17 @@ /** * @group Broken * @group Upload + * @group Database */ class UploadFromUrlTest extends ApiTestCase { - - public function setUp() { - global $wgEnableUploads, $wgAllowCopyUploads, $wgAllowAsyncCopyUploads; + protected function setUp() { parent::setUp(); - $wgEnableUploads = true; - $wgAllowCopyUploads = true; - $wgAllowAsyncCopyUploads = true; + $this->setMwGlobals( array( + 'wgEnableUploads' => true, + 'wgAllowCopyUploads' => true, + 'wgAllowAsyncCopyUploads' => true, + ) ); wfSetupSession(); if ( wfLocalFile( 'UploadFromUrlTest.png' )->exists() ) { @@ -20,7 +21,7 @@ class UploadFromUrlTest extends ApiTestCase { } } - protected function doApiRequest( Array $params, Array $unused = null, $appendModule = false, User $user = null ) { + protected function doApiRequest( array $params, array $unused = null, $appendModule = false, User $user = null ) { $sessionId = session_id(); session_write_close(); @@ -29,6 +30,7 @@ class UploadFromUrlTest extends ApiTestCase { $module->execute(); wfSetupSession( $sessionId ); + return array( $module->getResultData(), $req ); } @@ -36,9 +38,9 @@ class UploadFromUrlTest extends ApiTestCase { * Ensure that the job queue is empty before continuing */ public function testClearQueue() { - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); while ( $job ) { - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); } $this->assertFalse( $job ); } @@ -141,7 +143,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertEquals( $data[0]['upload']['result'], 'Queued', 'Queued upload' ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertThat( $job, $this->isInstanceOf( 'UploadFromUrlJob' ), 'Queued upload inserted' ); } @@ -173,7 +175,6 @@ class UploadFromUrlTest extends ApiTestCase { $this->user->addGroup( 'users' ); - $data = $this->doAsyncUpload( $token ); $this->assertEquals( $data[0]['upload']['result'], 'Warning' ); @@ -202,7 +203,7 @@ class UploadFromUrlTest extends ApiTestCase { public function testSyncDownload( $data ) { $token = $this->user->getEditToken(); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job, 'Starting with an empty jobqueue' ); $this->user->addGroup( 'users' ); @@ -214,7 +215,7 @@ class UploadFromUrlTest extends ApiTestCase { 'token' => $token, ), $data ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job ); $this->assertEquals( 'Success', $data[0]['upload']['result'] ); @@ -234,7 +235,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertFalse( (bool)$talk->getArticleID( Title::GAID_FOR_UPDATE ), 'User talk does not exist' ); - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', @@ -244,7 +245,7 @@ class UploadFromUrlTest extends ApiTestCase { 'ignorewarnings' => 1, ) ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) ); $job->run(); @@ -258,7 +259,7 @@ class UploadFromUrlTest extends ApiTestCase { $exception = false; try { - $data = $this->doApiRequest( array( + $this->doApiRequest( array( 'action' => 'upload', 'filename' => 'UploadFromUrlTest.png', 'url' => 'http://bits.wikimedia.org/skins-1.5/common/images/poweredby_mediawiki_88x31.png', @@ -272,11 +273,10 @@ class UploadFromUrlTest extends ApiTestCase { } $this->assertTrue( $exception ); - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertFalse( $job ); return; - /* // Broken until using leavemessage with ignorewarnings is supported $job->run(); @@ -314,7 +314,7 @@ class UploadFromUrlTest extends ApiTestCase { $this->assertTrue( isset( $data[0]['upload']['statuskey'] ) ); $statusKey = $data[0]['upload']['statuskey']; - $job = Job::pop(); + $job = JobQueueGroup::singleton()->pop(); $this->assertEquals( 'UploadFromUrlJob', get_class( $job ) ); $status = $job->run(); @@ -329,13 +329,12 @@ class UploadFromUrlTest extends ApiTestCase { return $data; } - /** * */ protected function deleteFile( $name ) { $t = Title::newFromText( $name, NS_FILE ); - $this->assertTrue($t->exists(), "File '$name' exists"); + $this->assertTrue( $t->exists(), "File '$name' exists" ); if ( $t->exists() ) { $file = wfFindFile( $name, array( 'ignoreRedirect' => true ) ); @@ -346,6 +345,6 @@ class UploadFromUrlTest extends ApiTestCase { } $t = Title::newFromText( $name, NS_FILE ); - $this->assertFalse($t->exists(), "File '$name' was deleted"); + $this->assertFalse( $t->exists(), "File '$name' was deleted" ); } - } +} diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php index 66fafaaf..7a0fea48 100644 --- a/tests/phpunit/includes/upload/UploadStashTest.php +++ b/tests/phpunit/includes/upload/UploadStashTest.php @@ -8,7 +8,7 @@ class UploadStashTest extends MediaWikiTestCase { */ public static $users; - public function setUp() { + protected function setUp() { parent::setUp(); // Setup a file for bug 29408 @@ -31,9 +31,20 @@ class UploadStashTest extends MediaWikiTestCase { ); } + protected function tearDown() { + if ( file_exists( $this->bug29408File . "." ) ) { + unlink( $this->bug29408File . "." ); + } + + if ( file_exists( $this->bug29408File ) ) { + unlink( $this->bug29408File ); + } + + parent::tearDown(); + } + public function testBug29408() { - global $wgUser; - $wgUser = self::$users['uploader']->user; + $this->setMwGlobals( 'wgUser', self::$users['uploader']->user ); $repo = RepoGroup::singleton()->getLocalRepo(); $stash = new UploadStash( $repo ); @@ -47,31 +58,19 @@ class UploadStashTest extends MediaWikiTestCase { } public function testValidRequest() { - $request = new FauxRequest( array( 'wpFileKey' => 'foo') ); - $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpFileKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'foo' ) ); + $this->assertFalse( UploadFromStash::isValidRequest( $request ), 'Check failure on bad wpFileKey' ); - $request = new FauxRequest( array( 'wpSessionKey' => 'foo') ); - $this->assertFalse( UploadFromStash::isValidRequest($request), 'Check failure on bad wpSessionKey' ); + $request = new FauxRequest( array( 'wpSessionKey' => 'foo' ) ); + $this->assertFalse( UploadFromStash::isValidRequest( $request ), 'Check failure on bad wpSessionKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpFileKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check good wpFileKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check good wpSessionKey' ); + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check good wpSessionKey' ); - $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo') ); - $this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' ); - } - - public function tearDown() { - parent::tearDown(); - - if( file_exists( $this->bug29408File . "." ) ) { - unlink( $this->bug29408File . "." ); - } - - if( file_exists( $this->bug29408File ) ) { - unlink( $this->bug29408File ); - } + $request = new FauxRequest( array( 'wpFileKey' => 'testkey-test.test', 'wpSessionKey' => 'foo' ) ); + $this->assertTrue( UploadFromStash::isValidRequest( $request ), 'Check key precedence' ); } } diff --git a/tests/phpunit/install-phpunit.sh b/tests/phpunit/install-phpunit.sh index 2d2b53ab..1f602935 100644 --- a/tests/phpunit/install-phpunit.sh +++ b/tests/phpunit/install-phpunit.sh @@ -8,7 +8,7 @@ has_binary () { } if [ `id -u` -ne 0 ]; then - echo '*** ERROR' Must be root to run + echo '*** ERROR: Must be root to run' exit 1 fi @@ -18,8 +18,9 @@ else if ( has_binary pear ); then echo Installing phpunit with pear pear channel-discover pear.phpunit.de pear channel-discover components.ez.no - pear channel-discover pear.symfony-project.com - pear install phpunit/PHPUnit + pear channel-discover pear.symfony.com + pear update-channels + pear install --alldeps phpunit/PHPUnit else if ( has_binary apt-get ); then echo Installing phpunit with apt-get apt-get install phpunit diff --git a/tests/phpunit/languages/LanguageAmTest.php b/tests/phpunit/languages/LanguageAmTest.php index 3a648ded..a644f5e0 100644 --- a/tests/phpunit/languages/LanguageAmTest.php +++ b/tests/phpunit/languages/LanguageAmTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageAm.php */ -class LanguageAmTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Am' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageAmTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), diff --git a/tests/phpunit/languages/LanguageArTest.php b/tests/phpunit/languages/LanguageArTest.php index b23e0534..7b48f236 100644 --- a/tests/phpunit/languages/LanguageArTest.php +++ b/tests/phpunit/languages/LanguageArTest.php @@ -5,30 +5,26 @@ */ /** Tests for MediaWiki languages/LanguageAr.php */ -class LanguageArTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Ar' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testFormatNum() { - $this->assertEquals( '١٬٢٣٤٬٥٦٧', $this->lang->formatNum( '1234567' ) ); - $this->assertEquals( '-١٢٫٨٩', $this->lang->formatNum( -12.89 ) ); +class LanguageArTest extends LanguageClassesTestCase { + /** + * @covers Language::formatNum + * @todo split into a test and a dataprovider + */ + public function testFormatNum() { + $this->assertEquals( '١٬٢٣٤٬٥٦٧', $this->getLang()->formatNum( '1234567' ) ); + $this->assertEquals( '-١٢٫٨٩', $this->getLang()->formatNum( -12.89 ) ); } /** * Mostly to test the raw ascii feature. * @dataProvider providerSprintfDate + * @covers Language::sprintfDate */ - function testSprintfDate( $format, $date, $expected ) { - $this->assertEquals( $expected, $this->lang->sprintfDate( $format, $date ) ); + public function testSprintfDate( $format, $date, $expected ) { + $this->assertEquals( $expected, $this->getLang()->sprintfDate( $format, $date ) ); } - function providerSprintfDate() { + public static function providerSprintfDate() { return array( array( 'xg "vs" g', @@ -52,13 +48,26 @@ class LanguageArTest extends MediaWikiTestCase { ), ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePlural() { - return array ( + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'two', 2 ), diff --git a/tests/phpunit/languages/LanguageBeTest.php b/tests/phpunit/languages/LanguageBeTest.php index 735ccc63..7bd586af 100644 --- a/tests/phpunit/languages/LanguageBeTest.php +++ b/tests/phpunit/languages/LanguageBeTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageBe.php */ -class LanguageBeTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Be' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageBeTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), diff --git a/tests/phpunit/languages/LanguageBe_taraskTest.php b/tests/phpunit/languages/LanguageBe_taraskTest.php index 765cdb8f..dbdb5889 100644 --- a/tests/phpunit/languages/LanguageBe_taraskTest.php +++ b/tests/phpunit/languages/LanguageBe_taraskTest.php @@ -1,40 +1,65 @@ <?php -class LanguageBeTaraskTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Be-tarask' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageBe_taraskTest extends LanguageClassesTestCase { + /** + * Make sure the language code we are given is indeed + * be-tarask. This is to ensure LanguageClassesTestCase + * does not give us the wrong language. + */ + public function testBeTaraskTestsUsesBeTaraskCode() { + $this->assertEquals( 'be-tarask', + $this->getLang()->getCode() + ); } - /** see bug 23156 & r64981 */ - function testSearchRightSingleQuotationMarkAsApostroph() { + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testSearchRightSingleQuotationMarkAsApostroph() { $this->assertEquals( "'", - $this->lang->normalizeForSearch( '’' ), + $this->getLang()->normalizeForSearch( '’' ), 'bug 23156: U+2019 conversion to U+0027' ); } - /** see bug 23156 & r64981 */ - function testCommafy() { - $this->assertEquals( '1,234,567', $this->lang->commafy( '1234567' ) ); - $this->assertEquals( '12,345', $this->lang->commafy( '12345' ) ); + + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testCommafy() { + $this->assertEquals( '1,234,567', $this->getLang()->commafy( '1234567' ) ); + $this->assertEquals( '12,345', $this->getLang()->commafy( '12345' ) ); } - /** see bug 23156 & r64981 */ - function testDoesNotCommafyFourDigitsNumber() { - $this->assertEquals( '1234', $this->lang->commafy( '1234' ) ); + + /** + * @see bug 23156 & r64981 + * @covers Language::commafy + */ + public function testDoesNotCommafyFourDigitsNumber() { + $this->assertEquals( '1234', $this->getLang()->commafy( '1234' ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralFourForms() { - return array ( + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), @@ -48,18 +73,23 @@ class LanguageBeTaraskTest extends MediaWikiTestCase { array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), ); } - } diff --git a/tests/phpunit/languages/LanguageBhTest.php b/tests/phpunit/languages/LanguageBhTest.php deleted file mode 100644 index e1e2a13e..00000000 --- a/tests/phpunit/languages/LanguageBhTest.php +++ /dev/null @@ -1,34 +0,0 @@ -<?php -/** - * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal - * @file - */ - -/** Tests for MediaWiki languages/LanguageBh.php */ -class LanguageBhTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Bh' ); - } - function tearDown() { - unset( $this->lang ); - } - - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); - } - - function providePlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'other', 2 ), - array( 'other', 200 ), - ); - } - -} diff --git a/tests/phpunit/languages/LanguageBhoTest.php b/tests/phpunit/languages/LanguageBhoTest.php new file mode 100644 index 00000000..187bfbbc --- /dev/null +++ b/tests/phpunit/languages/LanguageBhoTest.php @@ -0,0 +1,35 @@ +<?php +/** + * @author Santhosh Thottingal + * @copyright Copyright © 2012, Santhosh Thottingal + * @file + */ + +/** Tests for MediaWiki languages/LanguageBho.php */ +class LanguageBhoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 200 ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageBsTest.php b/tests/phpunit/languages/LanguageBsTest.php index b6631c03..7aca2ab1 100644 --- a/tests/phpunit/languages/LanguageBsTest.php +++ b/tests/phpunit/languages/LanguageBsTest.php @@ -5,37 +5,38 @@ * @file */ -/** Tests for MediaWiki languages/LanguageBs.php */ -class LanguageBsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Bs' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Croatian (hrvatski) */ +class LanguageBsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( - array( 'many', 0 ), + public static function providePlural() { + return array( + array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 11 ), - array( 'many', 20 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), array( 'one', 21 ), array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), + array( 'other', 25 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageClassesTestCase.php b/tests/phpunit/languages/LanguageClassesTestCase.php new file mode 100644 index 00000000..632e037f --- /dev/null +++ b/tests/phpunit/languages/LanguageClassesTestCase.php @@ -0,0 +1,74 @@ +<?php +/** + * Helping class to run tests using a clean language instance. + * + * This is intended for the MediaWiki language class tests under + * tests/phpunit/languages. + * + * Before each tests, a new language object is build which you + * can retrieve in your test using the $this->getLang() method: + * + * @par Using the crafted language object: + * @code + * function testHasLanguageObject() { + * $langObject = $this->getLang(); + * $this->assertInstanceOf( 'LanguageFoo', + * $langObject + * ); + * } + * @endcode + */ +abstract class LanguageClassesTestCase extends MediaWikiTestCase { + /** + * Internal language object + * + * A new object is created before each tests thanks to PHPUnit + * setUp() method, it is deleted after each test too. To get + * this object you simply use the getLang method. + * + * You must have setup a language code first. See $LanguageClassCode + * @code + * function testWeAreTheChampions() { + * $this->getLang(); # language object + * } + * @endcode + */ + private $languageObject; + + /** + * @return Language + */ + protected function getLang() { + return $this->languageObject; + } + + /** + * Create a new language object before each test. + */ + protected function setUp() { + parent::setUp(); + $found = preg_match( '/Language(.+)Test/', get_called_class(), $m ); + if ( $found ) { + # Normalize language code since classes uses underscores + $m[1] = str_replace( '_', '-', $m[1] ); + } else { + # Fallback to english language + $m[1] = 'en'; + wfDebug( + __METHOD__ . " could not extract a language name " + . "out of " . get_called_class() . " failling back to 'en'\n" + ); + } + // @todo validate $m[1] which should be a valid language code + $this->languageObject = Language::factory( $m[1] ); + } + + /** + * Delete the internal language object so each test start + * out with a fresh language instance. + */ + protected function tearDown() { + unset( $this->languageObject ); + parent::tearDown(); + } +} diff --git a/tests/phpunit/languages/LanguageCsTest.php b/tests/phpunit/languages/LanguageCsTest.php index dda29f9a..da9e6b88 100644 --- a/tests/phpunit/languages/LanguageCsTest.php +++ b/tests/phpunit/languages/LanguageCsTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/Languagecs.php */ -class LanguageCsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cs' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), @@ -36,5 +38,4 @@ class LanguageCsTest extends MediaWikiTestCase { array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageCuTest.php b/tests/phpunit/languages/LanguageCuTest.php index f8186d7b..07193172 100644 --- a/tests/phpunit/languages/LanguageCuTest.php +++ b/tests/phpunit/languages/LanguageCuTest.php @@ -6,36 +6,37 @@ */ /** Tests for MediaWiki languages/LanguageCu.php */ -class LanguageCuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cu' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), - array( 'few', 2 ), - array( 'many', 3 ), - array( 'many', 4 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), array( 'other', 5 ), array( 'one', 11 ), array( 'other', 20 ), - array( 'few', 22 ), - array( 'many', 223 ), + array( 'two', 22 ), + array( 'few', 223 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageCyTest.php b/tests/phpunit/languages/LanguageCyTest.php index e9f9e410..eaf663a8 100644 --- a/tests/phpunit/languages/LanguageCyTest.php +++ b/tests/phpunit/languages/LanguageCyTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageCy.php */ -class LanguageCyTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'cy' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageCyTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'zero', 'one', 'two', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'two', 2 ), @@ -38,5 +40,4 @@ class LanguageCyTest extends MediaWikiTestCase { array( 'other', 200.00 ), ); } - } diff --git a/tests/phpunit/languages/LanguageDsbTest.php b/tests/phpunit/languages/LanguageDsbTest.php index ab7f9313..94c11bcc 100644 --- a/tests/phpunit/languages/LanguageDsbTest.php +++ b/tests/phpunit/languages/LanguageDsbTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageDsb.php */ -class LanguageDsbTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'dsb' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageDsbTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'one', 101 ), @@ -36,5 +38,4 @@ class LanguageDsbTest extends MediaWikiTestCase { array( 'other', 555 ), ); } - } diff --git a/tests/phpunit/languages/LanguageFrTest.php b/tests/phpunit/languages/LanguageFrTest.php index 8538744e..46b65011 100644 --- a/tests/phpunit/languages/LanguageFrTest.php +++ b/tests/phpunit/languages/LanguageFrTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageFr.php */ -class LanguageFrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'fr' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageFrTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGaTest.php b/tests/phpunit/languages/LanguageGaTest.php index fbd9f11d..c009f56b 100644 --- a/tests/phpunit/languages/LanguageGaTest.php +++ b/tests/phpunit/languages/LanguageGaTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageGa.php */ -class LanguageGaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ga' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageGaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'two', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGdTest.php b/tests/phpunit/languages/LanguageGdTest.php index 24574bda..0b2612b2 100644 --- a/tests/phpunit/languages/LanguageGdTest.php +++ b/tests/phpunit/languages/LanguageGdTest.php @@ -1,38 +1,53 @@ <?php /** * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal + * @copyright Copyright © 2012-2013, Santhosh Thottingal * @file */ /** Tests for MediaWiki languages/classes/LanguageGd.php */ -class LanguageGdTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'gd' ); +class LanguageGdTest extends LanguageClassesTestCase { + /** + * @dataProvider providerPlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + public static function providerPlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'one', 11 ), + array( 'two', 12 ), + array( 'few', 3 ), + array( 'few', 19 ), + array( 'other', 200 ), + ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - // The CLDR ticket for this plural forms is not same as mw plural forms. See http://unicode.org/cldr/trac/ticket/2883 - $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4', 'Form 5', 'Form 6' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providerPluralExplicit + * @covers Language::convertPlural + */ + public function testExplicitPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other', '11=Form11', '12=Form12' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( - array( 'Form 6', 0 ), - array( 'Form 1', 1 ), - array( 'Form 2', 2 ), - array( 'Form 3', 11 ), - array( 'Form 4', 12 ), - array( 'Form 5', 3 ), - array( 'Form 5', 19 ), - array( 'Form 6', 200 ), + + public static function providerPluralExplicit() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'Form11', 11 ), + array( 'Form12', 12 ), + array( 'few', 3 ), + array( 'few', 19 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageGvTest.php b/tests/phpunit/languages/LanguageGvTest.php index 3d298b9b..fc58022a 100644 --- a/tests/phpunit/languages/LanguageGvTest.php +++ b/tests/phpunit/languages/LanguageGvTest.php @@ -1,39 +1,44 @@ <?php /** + * Test for Manx (Gaelg) language + * * @author Santhosh Thottingal - * @copyright Copyright © 2012, Santhosh Thottingal + * @copyright Copyright © 2013, Santhosh Thottingal * @file */ -/** Tests for MediaWiki languages/classes/LanguageGv.php */ -class LanguageGvTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'gv' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageGvTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - // This is not compatible with CLDR plural rules http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html#gv - $forms = array( 'Form 1', 'Form 2', 'Form 3', 'Form 4' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'Form 4', 0 ), - array( 'Form 2', 1 ), - array( 'Form 3', 2 ), - array( 'Form 4', 3 ), - array( 'Form 1', 20 ), - array( 'Form 2', 21 ), - array( 'Form 3', 22 ), - array( 'Form 4', 23 ), - array( 'Form 4', 50 ), + + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'other', 3 ), + array( 'few', 20 ), + array( 'one', 21 ), + array( 'two', 22 ), + array( 'other', 23 ), + array( 'other', 50 ), + array( 'few', 60 ), + array( 'other', 80 ), + array( 'few', 100 ) ); } - } diff --git a/tests/phpunit/languages/LanguageHeTest.php b/tests/phpunit/languages/LanguageHeTest.php index 7833da71..8edc6ddf 100644 --- a/tests/phpunit/languages/LanguageHeTest.php +++ b/tests/phpunit/languages/LanguageHeTest.php @@ -6,43 +6,127 @@ */ /** Tests for MediaWiki languages/classes/LanguageHe.php */ -class LanguageHeTest extends MediaWikiTestCase { - private $lang; +class LanguageHeTest extends LanguageClassesTestCase { + /** + * The most common usage for the plural forms is two forms, + * for singular and plural. In this case, the second form + * is technically dual, but in practice it's used as plural. + * In some cases, usually with expressions of time, three forms + * are needed - singular, dual and plural. + * CLDR also specifies a fourth form for multiples of 10, + * which is very rare. It also has a mistake, because + * the number 10 itself is supposed to be just plural, + * so currently it's overridden in MediaWiki. + */ - function setUp() { - $this->lang = Language::factory( 'he' ); - } - function tearDown() { - unset( $this->lang ); + // @todo the below test*PluralForms test methods can be refactored + // to use a single test method and data provider.. + + /** + * @dataProvider provideTwoPluralForms + * @covers Language::convertPlural + */ + public function testTwoPluralForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralDual */ - function testPluralDual( $result, $value ) { + /** + * @dataProvider provideThreePluralForms + * @covers Language::convertPlural + */ + public function testThreePluralForms( $result, $value ) { $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider provideFourPluralForms + * @covers Language::convertPlural + */ + public function testFourPluralForms( $result, $value ) { + $forms = array( 'one', 'two', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPluralDual() { - return array ( - array( 'other', 0 ), // Zero -> plural + /** + * @dataProvider provideFourPluralForms + * @covers Language::convertPlural + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); + } + + public static function provideTwoPluralForms() { + return array( + array( 'other', 0 ), // Zero - plural array( 'one', 1 ), // Singular - array( 'two', 2 ), // Dual - array( 'other', 3 ), // Plural + array( 'other', 2 ), // No third form provided, use it as plural + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // No fourth form provided, use it as plural + array( 'other', 20 ), // No fourth form provided, use it as plural ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + public static function provideThreePluralForms() { + return array( + array( 'other', 0 ), // Zero - plural + array( 'one', 1 ), // Singular + array( 'two', 2 ), // Dual + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // No fourth form provided, use it as plural + array( 'other', 20 ), // No fourth form provided, use it as plural + ); } - function providerPlural() { - return array ( - array( 'other', 0 ), // Zero -> plural + public static function provideFourPluralForms() { + return array( + array( 'other', 0 ), // Zero - plural array( 'one', 1 ), // Singular - array( 'other', 2 ), // Plural, no dual provided - array( 'other', 3 ), // Plural + array( 'two', 2 ), // Dual + array( 'other', 3 ), // Plural - other + array( 'other', 10 ), // 10 is supposed to be plural (other), not "many" + array( 'many', 20 ), // Fourth form provided - rare, but supported by CLDR + ); + } + + /** + * @dataProvider provideGrammar + * @covers Language::convertGrammar + */ + public function testGrammar( $result, $word, $case ) { + $this->assertEquals( $result, $this->getLang()->convertGrammar( $word, $case ) ); + } + + // The comments in the beginning of the line help avoid RTL problems + // with text editors. + public static function provideGrammar() { + return array( + array( + /* result */'וויקיפדיה', + /* word */'ויקיפדיה', + /* case */'תחילית', + ), + array( + /* result */'וולפגנג', + /* word */'וולפגנג', + /* case */'prefixed', + ), + array( + /* result */'קובץ', + /* word */'הקובץ', + /* case */'תחילית', + ), + array( + /* result */'־Wikipedia', + /* word */'Wikipedia', + /* case */'תחילית', + ), + array( + /* result */'־1995', + /* word */'1995', + /* case */'תחילית', + ), ); } } diff --git a/tests/phpunit/languages/LanguageHiTest.php b/tests/phpunit/languages/LanguageHiTest.php index ead9e020..f6d2c9e9 100644 --- a/tests/phpunit/languages/LanguageHiTest.php +++ b/tests/phpunit/languages/LanguageHiTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/LanguageHi.php */ -class LanguageHiTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Hi' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHiTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHrTest.php b/tests/phpunit/languages/LanguageHrTest.php index 4f1c66bf..644c5255 100644 --- a/tests/phpunit/languages/LanguageHrTest.php +++ b/tests/phpunit/languages/LanguageHrTest.php @@ -6,36 +6,37 @@ */ /** Tests for MediaWiki languages/classes/LanguageHr.php */ -class LanguageHrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hr' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHrTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), + public static function providePlural() { + return array( + array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 11 ), - array( 'many', 20 ), + array( 'other', 5 ), + array( 'other', 11 ), + array( 'other', 20 ), array( 'one', 21 ), array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), + array( 'other', 25 ), + array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHsbTest.php b/tests/phpunit/languages/LanguageHsbTest.php index 803c7721..f95a43bf 100644 --- a/tests/phpunit/languages/LanguageHsbTest.php +++ b/tests/phpunit/languages/LanguageHsbTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageHsb.php */ -class LanguageHsbTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hsb' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHsbTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'one', 101 ), @@ -36,5 +38,4 @@ class LanguageHsbTest extends MediaWikiTestCase { array( 'other', 555 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHuTest.php b/tests/phpunit/languages/LanguageHuTest.php index adbd37ec..ee9197d7 100644 --- a/tests/phpunit/languages/LanguageHuTest.php +++ b/tests/phpunit/languages/LanguageHuTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/LanguageHu.php */ -class LanguageHuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Hu' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageHuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageHyTest.php b/tests/phpunit/languages/LanguageHyTest.php index 7990bdfc..92e0ef94 100644 --- a/tests/phpunit/languages/LanguageHyTest.php +++ b/tests/phpunit/languages/LanguageHyTest.php @@ -5,30 +5,31 @@ * @file */ -/** Tests for MediaWiki languages/LanguageHy.php */ -class LanguageHyTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'hy' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Armenian (Հայերեն) */ +class LanguageHyTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageKshTest.php b/tests/phpunit/languages/LanguageKshTest.php index ab889464..568a3780 100644 --- a/tests/phpunit/languages/LanguageKshTest.php +++ b/tests/phpunit/languages/LanguageKshTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageKsh.php */ -class LanguageKshTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ksh' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageKshTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other', 'zero' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other', 'zero' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'zero', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageLnTest.php b/tests/phpunit/languages/LanguageLnTest.php index 0fd9167e..10b3234f 100644 --- a/tests/phpunit/languages/LanguageLnTest.php +++ b/tests/phpunit/languages/LanguageLnTest.php @@ -6,29 +6,30 @@ */ /** Tests for MediaWiki languages/classes/LanguageLn.php */ -class LanguageLnTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ln' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageLnTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), array( 'other', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageLtTest.php b/tests/phpunit/languages/LanguageLtTest.php index 0d7c7d3e..30642f62 100644 --- a/tests/phpunit/languages/LanguageLtTest.php +++ b/tests/phpunit/languages/LanguageLtTest.php @@ -6,30 +6,26 @@ */ /** Tests for MediaWiki languages/LanguageLt.php */ -class LanguageLtTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Lt' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageLtTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider provideOneFewOtherCases */ - function testOneFewOtherPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); - } - - /** @dataProvider provideOneFewCases */ - function testOneFewPlural( $result, $value ) { - $forms = array( 'one', 'few' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function provideOneFewOtherCases() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), @@ -43,11 +39,25 @@ class LanguageLtTest extends MediaWikiTestCase { array( 'one', 40001 ), ); } - - function provideOneFewCases() { - return array ( + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testOneFewPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + // This fails for 21, but not sure why. + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'few', 15 ), + array( 'other', 2 ), + array( 'other', 15 ), + array( 'other', 20 ), + array( 'one', 21 ), + array( 'other', 22 ), ); } } diff --git a/tests/phpunit/languages/LanguageLvTest.php b/tests/phpunit/languages/LanguageLvTest.php index 0636da5f..7120cfe3 100644 --- a/tests/phpunit/languages/LanguageLvTest.php +++ b/tests/phpunit/languages/LanguageLvTest.php @@ -5,35 +5,40 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageLv.php */ -class LanguageLvTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'lv' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Latvian */ +class LanguageLvTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'zero', 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'other', 0 ), #this must be zero form as per CLDR + public static function providePlural() { + return array( + array( 'zero', 0 ), array( 'one', 1 ), - array( 'other', 11 ), + array( 'zero', 11 ), array( 'one', 21 ), - array( 'other', 411 ), + array( 'zero', 411 ), + array( 'other', 2 ), + array( 'other', 9 ), + array( 'zero', 12 ), array( 'other', 12.345 ), - array( 'other', 20 ), + array( 'zero', 20 ), + array( 'other', 22 ), array( 'one', 31 ), - array( 'other', 200 ), + array( 'zero', 200 ), ); } - } diff --git a/tests/phpunit/languages/LanguageMgTest.php b/tests/phpunit/languages/LanguageMgTest.php index 06b56547..65e8fd7b 100644 --- a/tests/phpunit/languages/LanguageMgTest.php +++ b/tests/phpunit/languages/LanguageMgTest.php @@ -6,24 +6,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageMg.php */ -class LanguageMgTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mg' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMgTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePlural() { - return array ( + public static function providePlural() { + return array( array( 'one', 0 ), array( 'one', 1 ), array( 'other', 2 ), @@ -31,5 +33,4 @@ class LanguageMgTest extends MediaWikiTestCase { array( 'other', 123.3434 ), ); } - } diff --git a/tests/phpunit/languages/LanguageMkTest.php b/tests/phpunit/languages/LanguageMkTest.php index cf5ec3d9..ed155263 100644 --- a/tests/phpunit/languages/LanguageMkTest.php +++ b/tests/phpunit/languages/LanguageMkTest.php @@ -5,37 +5,36 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageMk.php */ -class LanguageMkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mk' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for македонски/Macedonian */ +class LanguageMkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), - array( 'other', 11 ), + array( 'one', 11 ), array( 'one', 21 ), - array( 'other', 411 ), + array( 'one', 411 ), array( 'other', 12.345 ), array( 'other', 20 ), array( 'one', 31 ), array( 'other', 200 ), ); } - - } diff --git a/tests/phpunit/languages/LanguageMlTest.php b/tests/phpunit/languages/LanguageMlTest.php index 8c4b0b2f..4fa45ce3 100644 --- a/tests/phpunit/languages/LanguageMlTest.php +++ b/tests/phpunit/languages/LanguageMlTest.php @@ -6,28 +6,23 @@ */ /** Tests for MediaWiki languages/LanguageMl.php */ -class LanguageMlTest extends MediaWikiTestCase { - private $lang; +class LanguageMlTest extends LanguageClassesTestCase { - function setUp() { - $this->lang = Language::factory( 'Ml' ); - } - function tearDown() { - unset( $this->lang ); - } - - /** see bug 29495 */ - /** @dataProvider providerFormatNum*/ - function testFormatNum( $result, $value ) { - $this->assertEquals( $result, $this->lang->formatNum( $value ) ); + /** + * @dataProvider providerFormatNum + * @see bug 29495 + * @covers Language::formatNum + */ + public function testFormatNum( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->formatNum( $value ) ); } - function providerFormatNum() { + public static function providerFormatNum() { return array( - array( '12,34,567', '1234567' ), + array( '12,34,567', '1234567' ), array( '12,345', '12345' ), array( '1', '1' ), - array( '123', '123' ) , + array( '123', '123' ), array( '1,234', '1234' ), array( '12,345.56', '12345.56' ), array( '12,34,56,79,81,23,45,678', '12345679812345678' ), @@ -35,7 +30,7 @@ class LanguageMlTest extends MediaWikiTestCase { array( '-12,00,000', '-1200000' ), array( '-98', '-98' ), array( '-98', -98 ), - array( '-1,23,45,678', -12345678 ), + array( '-1,23,45,678', -12345678 ), array( '', '' ), array( '', null ), ); diff --git a/tests/phpunit/languages/LanguageMoTest.php b/tests/phpunit/languages/LanguageMoTest.php index 533e590f..e0e54ca8 100644 --- a/tests/phpunit/languages/LanguageMoTest.php +++ b/tests/phpunit/languages/LanguageMoTest.php @@ -6,37 +6,39 @@ */ /** Tests for MediaWiki languages/classes/LanguageMo.php */ -class LanguageMoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mo' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'few', 101 ), - array( 'few', 119 ), + array( 'few', 101 ), + array( 'few', 119 ), array( 'other', 120 ), array( 'other', 200 ), - array( 'few', 201 ), - array( 'few', 219 ), + array( 'few', 201 ), + array( 'few', 219 ), array( 'other', 220 ), ); } diff --git a/tests/phpunit/languages/LanguageMtTest.php b/tests/phpunit/languages/LanguageMtTest.php index 421bb388..96d2bc92 100644 --- a/tests/phpunit/languages/LanguageMtTest.php +++ b/tests/phpunit/languages/LanguageMtTest.php @@ -6,67 +6,72 @@ */ /** Tests for MediaWiki languages/classes/LanguageMt.php */ -class LanguageMtTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'mt' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageMtTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralAllForms */ - function testPluralAllForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralAllForms() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 10 ), - array( 'many', 11 ), - array( 'many', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 10 ), + array( 'many', 11 ), + array( 'many', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), array( 'other', 101 ), - array( 'few', 102 ), - array( 'few', 110 ), - array( 'many', 111 ), - array( 'many', 119 ), + array( 'few', 102 ), + array( 'few', 110 ), + array( 'many', 111 ), + array( 'many', 119 ), array( 'other', 120 ), array( 'other', 201 ), ); } - /** @dataProvider providerPluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPluralTwoForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 19 ), - array( 'many', 20 ), - array( 'many', 99 ), - array( 'many', 100 ), - array( 'many', 101 ), - array( 'many', 102 ), - array( 'many', 110 ), - array( 'many', 111 ), - array( 'many', 119 ), - array( 'many', 120 ), - array( 'many', 201 ), + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 19 ), + array( 'other', 20 ), + array( 'other', 99 ), + array( 'other', 100 ), + array( 'other', 101 ), + array( 'other', 102 ), + array( 'other', 110 ), + array( 'other', 111 ), + array( 'other', 119 ), + array( 'other', 120 ), + array( 'other', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageNlTest.php b/tests/phpunit/languages/LanguageNlTest.php index cf979cd2..26bd691a 100644 --- a/tests/phpunit/languages/LanguageNlTest.php +++ b/tests/phpunit/languages/LanguageNlTest.php @@ -6,23 +6,19 @@ */ /** Tests for MediaWiki languages/LanguageNl.php */ -class LanguageNlTest extends MediaWikiTestCase { - private $lang; +class LanguageNlTest extends LanguageClassesTestCase { - function setUp() { - $this->lang = Language::factory( 'Nl' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testFormatNum() { - $this->assertEquals( '1.234.567', $this->lang->formatNum( '1234567' ) ); - $this->assertEquals( '12.345', $this->lang->formatNum( '12345' ) ); - $this->assertEquals( '1', $this->lang->formatNum( '1' ) ); - $this->assertEquals( '123', $this->lang->formatNum( '123' ) ); - $this->assertEquals( '1.234', $this->lang->formatNum( '1234' ) ); - $this->assertEquals( '12.345,56', $this->lang->formatNum( '12345.56' ) ); - $this->assertEquals( ',1234556', $this->lang->formatNum( '.1234556' ) ); + /** + * @covers Language::formatNum + * @todo split into a test and a dataprovider + */ + public function testFormatNum() { + $this->assertEquals( '1.234.567', $this->getLang()->formatNum( '1234567' ) ); + $this->assertEquals( '12.345', $this->getLang()->formatNum( '12345' ) ); + $this->assertEquals( '1', $this->getLang()->formatNum( '1' ) ); + $this->assertEquals( '123', $this->getLang()->formatNum( '123' ) ); + $this->assertEquals( '1.234', $this->getLang()->formatNum( '1234' ) ); + $this->assertEquals( '12.345,56', $this->getLang()->formatNum( '12345.56' ) ); + $this->assertEquals( ',1234556', $this->getLang()->formatNum( '.1234556' ) ); } } diff --git a/tests/phpunit/languages/LanguageNsoTest.php b/tests/phpunit/languages/LanguageNsoTest.php index ea393628..18efd736 100644 --- a/tests/phpunit/languages/LanguageNsoTest.php +++ b/tests/phpunit/languages/LanguageNsoTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageNso.php */ -class LanguageNsoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'nso' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageNsoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguagePlTest.php b/tests/phpunit/languages/LanguagePlTest.php index e56d4b77..d180037b 100644 --- a/tests/phpunit/languages/LanguagePlTest.php +++ b/tests/phpunit/languages/LanguagePlTest.php @@ -6,67 +6,72 @@ */ /** Tests for MediaWiki languages/classes/LanguagePl.php */ -class LanguagePlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'pl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguagePlTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralFourForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), - array( 'many', 5 ), - array( 'many', 9 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 21 ), - array( 'few', 22 ), - array( 'few', 23 ), - array( 'few', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), - array( 'many', 201 ), + public static function providePlural() { + return array( + array( 'many', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), + array( 'many', 5 ), + array( 'many', 9 ), + array( 'many', 10 ), + array( 'many', 11 ), + array( 'many', 21 ), + array( 'few', 22 ), + array( 'few', 23 ), + array( 'few', 24 ), + array( 'many', 25 ), + array( 'many', 200 ), + array( 'many', 201 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), - array( 'many', 3 ), - array( 'many', 4 ), - array( 'many', 5 ), - array( 'many', 9 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 21 ), - array( 'many', 22 ), - array( 'many', 23 ), - array( 'many', 24 ), - array( 'many', 25 ), - array( 'many', 200 ), - array( 'many', 201 ), + public static function providePluralTwoForms() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), + array( 'other', 3 ), + array( 'other', 4 ), + array( 'other', 5 ), + array( 'other', 9 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 21 ), + array( 'other', 22 ), + array( 'other', 23 ), + array( 'other', 24 ), + array( 'other', 25 ), + array( 'other', 200 ), + array( 'other', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageRoTest.php b/tests/phpunit/languages/LanguageRoTest.php index 5270f6fe..ae7816bc 100644 --- a/tests/phpunit/languages/LanguageRoTest.php +++ b/tests/phpunit/languages/LanguageRoTest.php @@ -6,37 +6,39 @@ */ /** Tests for MediaWiki languages/classes/LanguageRo.php */ -class LanguageRoTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ro' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageRoTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'few', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), - array( 'few', 19 ), + public static function providePlural() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 19 ), array( 'other', 20 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'few', 101 ), - array( 'few', 119 ), + array( 'few', 101 ), + array( 'few', 119 ), array( 'other', 120 ), array( 'other', 200 ), - array( 'few', 201 ), - array( 'few', 219 ), + array( 'few', 201 ), + array( 'few', 219 ), array( 'other', 220 ), ); } diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php index 7a1f193b..e17c7085 100644 --- a/tests/phpunit/languages/LanguageRuTest.php +++ b/tests/phpunit/languages/LanguageRuTest.php @@ -7,48 +7,99 @@ */ /** Tests for MediaWiki languages/classes/LanguageRu.php */ -class LanguageRuTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'ru' ); +class LanguageRuTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + /** + * Test explicit plural forms - n=FormN forms + * @covers Language::convertPlural + */ + public function testExplicitPlural() { + $forms = array( 'one','many', 'other', '12=dozen' ); + $this->assertEquals( 'dozen', $this->getLang()->convertPlural( 12, $forms ) ); + $forms = array( 'one', 'many', '100=hundred', 'other', '12=dozen' ); + $this->assertEquals( 'hundred', $this->getLang()->convertPlural( 100, $forms ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), array( 'one', 121 ), - array( 'few', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), - array( 'few', 334 ), + array( 'other', 2 ), + array( 'other', 3 ), + array( 'other', 4 ), + array( 'other', 334 ), array( 'many', 5 ), array( 'many', 15 ), array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), + ); + } + + /** + * @dataProvider providerGrammar + * @covers Language::convertGrammar + */ + public function testGrammar( $result, $word, $case ) { + $this->assertEquals( $result, $this->getLang()->convertGrammar( $word, $case ) ); + } + + public static function providerGrammar() { + return array( + array( + 'Википедии', + 'Википедия', + 'genitive', + ), + array( + 'Викитеки', + 'Викитека', + 'genitive', + ), + array( + 'Викитеке', + 'Викитека', + 'prepositional', + ), + array( + 'Викиданных', + 'Викиданные', + 'prepositional', + ), ); } } diff --git a/tests/phpunit/languages/LanguageSeTest.php b/tests/phpunit/languages/LanguageSeTest.php index 065ec29e..533aa2bc 100644 --- a/tests/phpunit/languages/LanguageSeTest.php +++ b/tests/phpunit/languages/LanguageSeTest.php @@ -6,41 +6,46 @@ */ /** Tests for MediaWiki languages/classes/LanguageSe.php */ -class LanguageSeTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'se' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSeTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralThreeForms */ - function testPluralThreeForms( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralThreeForms() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), ); diff --git a/tests/phpunit/languages/LanguageSgsTest.php b/tests/phpunit/languages/LanguageSgsTest.php index 931c82f0..fa49a4dd 100644 --- a/tests/phpunit/languages/LanguageSgsTest.php +++ b/tests/phpunit/languages/LanguageSgsTest.php @@ -5,51 +5,56 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageSgs.php */ -class LanguageSgsTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Sgs' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for Samogitian */ +class LanguageSgsTest extends LanguageClassesTestCase { + /** + * @dataProvider providePluralAllForms + * @covers Language::convertPlural + */ + public function testPluralAllForms( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providePluralAllForms */ - function testPluralAllForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralAllForms + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralAllForms() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'few', 2 ), + public static function providePluralAllForms() { + return array( + array( 'few', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), - array( 'many', 10 ), - array( 'many', 11 ), - array( 'many', 12 ), - array( 'many', 19 ), + array( 'few', 10 ), + array( 'few', 11 ), + array( 'few', 12 ), + array( 'few', 19 ), array( 'other', 20 ), - array( 'many', 100 ), - array( 'one', 101 ), - array( 'many', 111 ), - array( 'many', 112 ), + array( 'few', 100 ), + array( 'one', 101 ), + array( 'few', 111 ), + array( 'few', 112 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), array( 'other', 10 ), @@ -58,7 +63,7 @@ class LanguageSgsTest extends MediaWikiTestCase { array( 'other', 19 ), array( 'other', 20 ), array( 'other', 100 ), - array( 'one', 101 ), + array( 'one', 101 ), array( 'other', 111 ), array( 'other', 112 ), ); diff --git a/tests/phpunit/languages/LanguageShTest.php b/tests/phpunit/languages/LanguageShTest.php index b8169aed..1b390872 100644 --- a/tests/phpunit/languages/LanguageShTest.php +++ b/tests/phpunit/languages/LanguageShTest.php @@ -5,28 +5,38 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageSh.php */ -class LanguageShTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sh' ); - } - function tearDown() { - unset( $this->lang ); +/** Tests for srpskohrvatski / српскохрватски / Serbocroatian */ +class LanguageShTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'many', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'few', 2 ), + array( 'few', 4 ), + array( 'other', 5 ), + array( 'other', 10 ), + array( 'other', 11 ), + array( 'other', 12 ), + array( 'one', 101 ), + array( 'few', 102 ), + array( 'other', 111 ), ); } } diff --git a/tests/phpunit/languages/LanguageSkTest.php b/tests/phpunit/languages/LanguageSkTest.php index 4cfd840e..cb8a13b8 100644 --- a/tests/phpunit/languages/LanguageSkTest.php +++ b/tests/phpunit/languages/LanguageSkTest.php @@ -7,24 +7,26 @@ */ /** Tests for MediaWiki languages/classes/LanguageSk.php */ -class LanguageSkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sk' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'few', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), array( 'one', 1 ), array( 'few', 2 ), diff --git a/tests/phpunit/languages/LanguageSlTest.php b/tests/phpunit/languages/LanguageSlTest.php index c1f75691..9783dd80 100644 --- a/tests/phpunit/languages/LanguageSlTest.php +++ b/tests/phpunit/languages/LanguageSlTest.php @@ -7,36 +7,38 @@ */ /** Tests for MediaWiki languages/classes/LanguageSl.php */ -class LanguageSlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSlTest extends LanguageClassesTestCase { + /** + * @dataProvider providerPlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'two', 'few', 'other', 'zero' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providerPlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'zero', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), - array( 'few', 3 ), - array( 'few', 4 ), + public static function providerPlural() { + return array( + array( 'other', 0 ), + array( 'one', 1 ), + array( 'two', 2 ), + array( 'few', 3 ), + array( 'few', 4 ), array( 'other', 5 ), array( 'other', 99 ), array( 'other', 100 ), - array( 'one', 101 ), - array( 'two', 102 ), - array( 'few', 103 ), - array( 'one', 201 ), + array( 'one', 101 ), + array( 'two', 102 ), + array( 'few', 103 ), + array( 'one', 201 ), ); } } diff --git a/tests/phpunit/languages/LanguageSmaTest.php b/tests/phpunit/languages/LanguageSmaTest.php index b7e72e97..95cb333c 100644 --- a/tests/phpunit/languages/LanguageSmaTest.php +++ b/tests/phpunit/languages/LanguageSmaTest.php @@ -6,41 +6,46 @@ */ /** Tests for MediaWiki languages/classes/LanguageSma.php */ -class LanguageSmaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sma' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageSmaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'two', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPluralThreeForms */ - function testPluralThreeForms( $result, $value ) { - $forms = array( 'one', 'two', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPluralThreeForms() { - return array ( + public static function providePlural() { + return array( array( 'other', 0 ), - array( 'one', 1 ), - array( 'two', 2 ), + array( 'one', 1 ), + array( 'two', 2 ), array( 'other', 3 ), ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { $forms = array( 'one', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providerPlural() { - return array ( + public static function providePluralTwoForms() { + return array( array( 'other', 0 ), - array( 'one', 1 ), + array( 'one', 1 ), array( 'other', 2 ), array( 'other', 3 ), ); diff --git a/tests/phpunit/languages/LanguageSrTest.php b/tests/phpunit/languages/LanguageSrTest.php index d44ecf8e..d6fedb57 100644 --- a/tests/phpunit/languages/LanguageSrTest.php +++ b/tests/phpunit/languages/LanguageSrTest.php @@ -10,25 +10,18 @@ * @author Antoine Musso <hashar at free dot fr> * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr> * @file + * + * @todo methods in test class should be tidied: + * - Should be split into separate test methods and data providers + * - Tests for LanguageConverter and Language should probably be separate.. */ -require_once dirname( __DIR__ ) . '/bootstrap.php'; - /** Tests for MediaWiki languages/LanguageSr.php */ -class LanguageSrTest extends MediaWikiTestCase { - /* Language object. Initialized before each test */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'sr' ); - } - function tearDown() { - unset( $this->lang ); - } - - ##### TESTS ####################################################### - - function testEasyConversions( ) { +class LanguageSrTest extends LanguageClassesTestCase { + /** + * @covers LanguageConverter::convertTo + */ + public function testEasyConversions() { $this->assertCyrillic( 'шђчћжШЂЧЋЖ', 'Cyrillic guessing characters' @@ -39,7 +32,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testMixedConversions() { + /** + * @covers LanguageConverter::convertTo + */ + public function testMixedConversions() { $this->assertCyrillic( 'шђчћжШЂЧЋЖ - šđčćž', 'Mostly cyrillic characters' @@ -50,7 +46,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testSameAmountOfLatinAndCyrillicGetConverted() { + /** + * @covers LanguageConverter::convertTo + */ + public function testSameAmountOfLatinAndCyrillicGetConverted() { $this->assertConverted( '4 latin: šđčć | 4 cyrillic: шђчћ', 'sr-ec' @@ -63,8 +62,9 @@ class LanguageSrTest extends MediaWikiTestCase { /** * @author Nikola Smolenski + * @covers LanguageConverter::convertTo */ - function testConversionToCyrillic() { + public function testConversionToCyrillic() { //A simple convertion of Latin to Cyrillic $this->assertEquals( 'абвг', $this->convertToCyrillic( 'abvg' ) @@ -103,7 +103,10 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - function testConversionToLatin() { + /** + * @covers LanguageConverter::convertTo + */ + public function testConversionToLatin() { //A simple convertion of Latin to Latin $this->assertEquals( 'abcd', $this->convertToLatin( 'abcd' ) @@ -122,38 +125,55 @@ class LanguageSrTest extends MediaWikiTestCase { ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); + } + + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), - array( 'many', 11 ), + array( 'other', 11 ), array( 'one', 91 ), array( 'one', 121 ), array( 'few', 2 ), array( 'few', 3 ), array( 'few', 4 ), array( 'few', 334 ), - array( 'many', 5 ), - array( 'many', 15 ), - array( 'many', 120 ), + array( 'other', 5 ), + array( 'other', 15 ), + array( 'other', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 4 ), + array( 'one', 91 ), + array( 'one', 121 ), ); } @@ -164,20 +184,21 @@ class LanguageSrTest extends MediaWikiTestCase { * @param $variant string Language variant 'sr-ec' or 'sr-el' * @param $msg string Optional message */ - function assertUnConverted( $text, $variant, $msg = '' ) { + protected function assertUnConverted( $text, $variant, $msg = '' ) { $this->assertEquals( $text, $this->convertTo( $text, $variant ), $msg ); } + /** * Wrapper to verify a text is different once converted to a variant. * @param $text string Text to convert * @param $variant string Language variant 'sr-ec' or 'sr-el' * @param $msg string Optional message */ - function assertConverted( $text, $variant, $msg = '' ) { + protected function assertConverted( $text, $variant, $msg = '' ) { $this->assertNotEquals( $text, $this->convertTo( $text, $variant ), @@ -190,34 +211,36 @@ class LanguageSrTest extends MediaWikiTestCase { * using the cyrillic variant and converted to Latin when using * the Latin variant. */ - function assertCyrillic( $text, $msg = '' ) { + protected function assertCyrillic( $text, $msg = '' ) { $this->assertUnConverted( $text, 'sr-ec', $msg ); $this->assertConverted( $text, 'sr-el', $msg ); } + /** * Verifiy the given Latin text is not converted when using * using the Latin variant and converted to Cyrillic when using * the Cyrillic variant. */ - function assertLatin( $text, $msg = '' ) { + protected function assertLatin( $text, $msg = '' ) { $this->assertUnConverted( $text, 'sr-el', $msg ); $this->assertConverted( $text, 'sr-ec', $msg ); } /** Wrapper for converter::convertTo() method*/ - function convertTo( $text, $variant ) { - return $this - ->lang + protected function convertTo( $text, $variant ) { + return $this->getLang() ->mConverter ->convertTo( $text, $variant ); } - function convertToCyrillic( $text ) { + + protected function convertToCyrillic( $text ) { return $this->convertTo( $text, 'sr-ec' ); } - function convertToLatin( $text ) { + + protected function convertToLatin( $text ) { return $this->convertTo( $text, 'sr-el' ); } } diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index 2fa3e292..78929e23 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -1,23 +1,14 @@ <?php -class LanguageTest extends MediaWikiTestCase { - +class LanguageTest extends LanguageClassesTestCase { /** - * @var Language + * @covers Language::convertDoubleWidth + * @covers Language::normalizeForSearch */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'en' ); - } - function tearDown() { - unset( $this->lang ); - } - - function testLanguageConvertDoubleWidthToSingleWidth() { + public function testLanguageConvertDoubleWidthToSingleWidth() { $this->assertEquals( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz", - $this->lang->normalizeForSearch( + $this->getLang()->normalizeForSearch( "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ), 'convertDoubleWidth() with the full alphabet and digits' @@ -25,13 +16,14 @@ class LanguageTest extends MediaWikiTestCase { } /** - * @dataProvider provideFormattableTimes + * @dataProvider provideFormattableTimes# + * @covers Language::formatTimePeriod */ - function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { - $this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc ); + public function testFormatTimePeriod( $seconds, $format, $expected, $desc ) { + $this->assertEquals( $expected, $this->getLang()->formatTimePeriod( $seconds, $format ), $desc ); } - function provideFormattableTimes() { + public static function provideFormattableTimes() { return array( array( 9.45, @@ -214,56 +206,59 @@ class LanguageTest extends MediaWikiTestCase { 'formatTimePeriod() rounding, recursion, (>48h)' ), ); - } - function testTruncate() { + /** + * @covers Language::truncate + */ + public function testTruncate() { $this->assertEquals( "XXX", - $this->lang->truncate( "1234567890", 0, 'XXX' ), + $this->getLang()->truncate( "1234567890", 0, 'XXX' ), 'truncate prefix, len 0, small ellipsis' ); $this->assertEquals( "12345XXX", - $this->lang->truncate( "1234567890", 8, 'XXX' ), + $this->getLang()->truncate( "1234567890", 8, 'XXX' ), 'truncate prefix, small ellipsis' ); $this->assertEquals( "123456789", - $this->lang->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), 'truncate prefix, large ellipsis' ); $this->assertEquals( "XXX67890", - $this->lang->truncate( "1234567890", -8, 'XXX' ), + $this->getLang()->truncate( "1234567890", -8, 'XXX' ), 'truncate suffix, small ellipsis' ); $this->assertEquals( "123456789", - $this->lang->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), 'truncate suffix, large ellipsis' ); } /** - * @dataProvider provideHTMLTruncateData() + * @dataProvider provideHTMLTruncateData + * @covers Language::truncateHTML */ - function testTruncateHtml( $len, $ellipsis, $input, $expected ) { + public function testTruncateHtml( $len, $ellipsis, $input, $expected ) { // Actual HTML... $this->assertEquals( $expected, - $this->lang->truncateHTML( $input, $len, $ellipsis ) + $this->getLang()->truncateHTML( $input, $len, $ellipsis ) ); } /** - * Array format is ($len, $ellipsis, $input, $expected) + * @return array format is ($len, $ellipsis, $input, $expected) */ - function provideHTMLTruncateData() { + public static function provideHTMLTruncateData() { return array( array( 0, 'XXX', "1234567890", "XXX" ), array( 8, 'XXX', "1234567890", "12345XXX" ), @@ -320,29 +315,148 @@ class LanguageTest extends MediaWikiTestCase { } /** + * Test Language::isWellFormedLanguageTag() + * @dataProvider provideWellFormedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testWellFormedLanguageTag( $code, $message = '' ) { + $this->assertTrue( + Language::isWellFormedLanguageTag( $code ), + "validating code $code $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr> + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideWellFormedLanguageTags() { + return array( + array( 'fr', 'two-letter code' ), + array( 'fr-latn', 'two-letter code with lower case script code' ), + array( 'fr-Latn-FR', 'two-letter code with title case script code and uppercase country code' ), + array( 'fr-Latn-419', 'two-letter code with title case script code and region number' ), + array( 'fr-FR', 'two-letter code with uppercase' ), + array( 'ax-TZ', 'Not in the registry, but well-formed' ), + array( 'fr-shadok', 'two-letter code with variant' ), + array( 'fr-y-myext-myext2', 'non-x singleton' ), + array( 'fra-Latn', 'ISO 639 can be 3-letters' ), + array( 'fra', 'three-letter language code' ), + array( 'fra-FX', 'three-letter language code with country code' ), + array( 'i-klingon', 'grandfathered with singleton' ), + array( 'I-kLINgon', 'tags are case-insensitive...' ), + array( 'no-bok', 'grandfathered without singleton' ), + array( 'i-enochian', 'Grandfathered' ), + array( 'x-fr-CH', 'private use' ), + array( 'es-419', 'two-letter code with region number' ), + array( 'en-Latn-GB-boont-r-extended-sequence-x-private', 'weird, but well-formed' ), + array( 'ab-x-abc-x-abc', 'anything goes after x' ), + array( 'ab-x-abc-a-a', 'anything goes after x, including several non-x singletons' ), + array( 'i-default', 'grandfathered' ), + array( 'abcd-Latn', 'Language of 4 chars reserved for future use' ), + array( 'AaBbCcDd-x-y-any-x', 'Language of 5-8 chars, registered' ), + array( 'de-CH-1901', 'with country and year' ), + array( 'en-US-x-twain', 'with country and singleton' ), + array( 'zh-cmn', 'three-letter variant' ), + array( 'zh-cmn-Hant', 'three-letter variant and script' ), + array( 'zh-cmn-Hant-HK', 'three-letter variant, script and country' ), + array( 'xr-p-lze', 'Extension' ), + ); + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @dataProvider provideMalformedLanguageTags + * @covers Language::isWellFormedLanguageTag + */ + public function testMalformedLanguageTag( $code, $message = '' ) { + $this->assertFalse( + Language::isWellFormedLanguageTag( $code ), + "validating that code $code is a malformed language tag - $message" + ); + } + + /** + * The test cases are based on the tests in the GaBuZoMeu parser + * written by Stéphane Bortzmeyer <bortzmeyer@nic.fr> + * and distributed as free software, under the GNU General Public Licence. + * http://www.bortzmeyer.org/gabuzomeu-parsing-language-tags.html + */ + public static function provideMalformedLanguageTags() { + return array( + array( 'f', 'language too short' ), + array( 'f-Latn', 'language too short with script' ), + array( 'xr-lxs-qut', 'variants too short' ), # extlangS + array( 'fr-Latn-F', 'region too short' ), + array( 'a-value', 'language too short with region' ), + array( 'tlh-a-b-foo', 'valid three-letter with wrong variant' ), + array( 'i-notexist', 'grandfathered but not registered: invalid, even if we only test well-formedness' ), + array( 'abcdefghi-012345678', 'numbers too long' ), + array( 'ab-abc-abc-abc-abc', 'invalid extensions' ), + array( 'ab-abcd-abc', 'invalid extensions' ), + array( 'ab-ab-abc', 'invalid extensions' ), + array( 'ab-123-abc', 'invalid extensions' ), + array( 'a-Hant-ZH', 'short language with valid extensions' ), + array( 'a1-Hant-ZH', 'invalid character in language' ), + array( 'ab-abcde-abc', 'invalid extensions' ), + array( 'ab-1abc-abc', 'invalid characters in extensions' ), + array( 'ab-ab-abcd', 'invalid order of extensions' ), + array( 'ab-123-abcd', 'invalid order of extensions' ), + array( 'ab-abcde-abcd', 'invalid extensions' ), + array( 'ab-1abc-abcd', 'invalid characters in extensions' ), + array( 'ab-a-b', 'extensions too short' ), + array( 'ab-a-x', 'extensions too short, even with singleton' ), + array( 'ab--ab', 'two separators' ), + array( 'ab-abc-', 'separator in the end' ), + array( '-ab-abc', 'separator in the beginning' ), + array( 'abcd-efg', 'language too long' ), + array( 'aabbccddE', 'tag too long' ), + array( 'pa_guru', 'A tag with underscore is invalid in strict mode' ), + array( 'de-f', 'subtag too short' ), + ); + } + + /** + * Negative test for Language::isWellFormedLanguageTag() + * @covers Language::isWellFormedLanguageTag + */ + public function testLenientLanguageTag() { + $this->assertTrue( + Language::isWellFormedLanguageTag( 'pa_guru', true ), + 'pa_guru is a well-formed language tag in lenient mode' + ); + } + + /** * Test Language::isValidBuiltInCode() * @dataProvider provideLanguageCodes + * @covers Language::isValidBuiltInCode */ - function testBuiltInCodeValidation( $code, $message = '' ) { + public function testBuiltInCodeValidation( $code, $message = '' ) { $this->assertTrue( - (bool) Language::isValidBuiltInCode( $code ), + (bool)Language::isValidBuiltInCode( $code ), "validating code $code $message" ); } - function testBuiltInCodeValidationRejectUnderscore() { + /** + * @covers Language::isValidBuiltInCode + */ + public function testBuiltInCodeValidationRejectUnderscore() { $this->assertFalse( - (bool) Language::isValidBuiltInCode( 'be_tarask' ), + (bool)Language::isValidBuiltInCode( 'be_tarask' ), "reject underscore in language code" ); } - function provideLanguageCodes() { + public static function provideLanguageCodes() { return array( - array( 'fr' , 'Two letters, minor case' ), - array( 'EN' , 'Two letters, upper case' ), - array( 'tyv' , 'Three letters' ), - array( 'tokipona' , 'long language code' ), + array( 'fr', 'Two letters, minor case' ), + array( 'EN', 'Two letters, upper case' ), + array( 'tyv', 'Three letters' ), + array( 'tokipona', 'long language code' ), array( 'be-tarask', 'With dash' ), array( 'Zh-classical', 'Begin with upper case, dash' ), array( 'Be-x-old', 'With extension (two dashes)' ), @@ -350,20 +464,103 @@ class LanguageTest extends MediaWikiTestCase { } /** + * Test Language::isKnownLanguageTag() + * @dataProvider provideKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testKnownLanguageTag( $code, $message = '' ) { + $this->assertTrue( + (bool)Language::isKnownLanguageTag( $code ), + "validating code $code - $message" + ); + } + + public static function provideKnownLanguageTags() { + return array( + array( 'fr', 'simple code' ), + array( 'bat-smg', 'an MW legacy tag' ), + array( 'sgs', 'an internal standard MW name, for which a legacy tag is used externally' ), + ); + } + + /** + * @covers Language::isKnownLanguageTag + */ + public function testKnownCldrLanguageTag() { + if ( !class_exists( 'LanguageNames' ) ) { + $this->markTestSkipped( 'The LanguageNames class is not available. The cldr extension is probably not installed.' ); + } + + $this->assertTrue( + (bool)Language::isKnownLanguageTag( 'pal' ), + 'validating code "pal" an ancient language, which probably will not appear in Names.php, but appears in CLDR in English' + ); + } + + /** + * Negative tests for Language::isKnownLanguageTag() + * @dataProvider provideUnKnownLanguageTags + * @covers Language::isKnownLanguageTag + */ + public function testUnknownLanguageTag( $code, $message = '' ) { + $this->assertFalse( + (bool)Language::isKnownLanguageTag( $code ), + "checking that code $code is invalid - $message" + ); + } + + public static function provideUnknownLanguageTags() { + return array( + array( 'mw', 'non-existent two-letter code' ), + array( 'foo"<bar', 'very invalid language code' ), + ); + } + + /** + * Test too short timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateTooShortTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '1234567890123' ); + } + + /** + * Test too long timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateTooLongTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '123456789012345' ); + } + + /** + * Test too short timestamp + * @expectedException MWException + * @covers Language::sprintfDate + */ + public function testSprintfDateNotAllDigitTimestamp() { + $this->getLang()->sprintfDate( 'xiY', '-1234567890123' ); + } + + /** * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate */ - function testSprintfDate( $format, $ts, $expected, $msg ) { + public function testSprintfDate( $format, $ts, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->sprintfDate( $format, $ts ), + $this->getLang()->sprintfDate( $format, $ts ), "sprintfDate('$format', '$ts'): $msg" ); } + /** - * bug 33454. sprintfDate should always use UTC. + * sprintfDate should always use UTC when no zone is given. * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate */ - function testSprintfDateTZ( $format, $ts, $expected, $msg ) { + public function testSprintfDateNoZone( $format, $ts, $expected, $ignore, $msg ) { $oldTZ = date_default_timezone_get(); $res = date_default_timezone_set( 'Asia/Seoul' ); if ( !$res ) { @@ -372,49 +569,73 @@ class LanguageTest extends MediaWikiTestCase { $this->assertEquals( $expected, - $this->lang->sprintfDate( $format, $ts ), + $this->getLang()->sprintfDate( $format, $ts ), "sprintfDate('$format', '$ts'): $msg" ); date_default_timezone_set( $oldTZ ); } - function provideSprintfDateSamples() { + /** + * sprintfDate should use passed timezone + * @dataProvider provideSprintfDateSamples + * @covers Language::sprintfDate + */ + public function testSprintfDateTZ( $format, $ts, $ignore, $expected, $msg ) { + $tz = new DateTimeZone( 'Asia/Seoul' ); + if ( !$tz ) { + $this->markTestSkipped( "Error getting Timezone" ); + } + + $this->assertEquals( + $expected, + $this->getLang()->sprintfDate( $format, $ts, $tz ), + "sprintfDate('$format', '$ts', 'Asia/Seoul'): $msg" + ); + } + + public static function provideSprintfDateSamples() { return array( array( 'xiY', '20111212000000', '1390', // note because we're testing English locale we get Latin-standard digits + '1390', 'Iranian calendar full year' ), array( 'xiy', '20111212000000', '90', + '90', 'Iranian calendar short year' ), array( 'o', '20120101235000', '2011', + '2011', 'ISO 8601 (week) year' ), array( 'W', '20120101235000', '52', + '52', 'Week number' ), array( 'W', '20120102235000', '1', + '1', 'Week number' ), array( 'o-\\WW-N', '20091231235000', '2009-W53-4', + '2009-W53-4', 'leap week' ), // What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time @@ -422,252 +643,336 @@ class LanguageTest extends MediaWikiTestCase { 'Y', '20120102090705', '2012', + '2012', 'Full year' ), array( 'y', '20120102090705', '12', + '12', '2 digit year' ), array( 'L', '20120102090705', '1', + '1', 'Leap year' ), array( 'n', '20120102090705', '1', + '1', 'Month index, not zero pad' ), array( 'N', '20120102090705', '01', + '01', 'Month index. Zero pad' ), array( 'M', '20120102090705', 'Jan', + 'Jan', 'Month abbrev' ), array( 'F', '20120102090705', 'January', + 'January', 'Full month' ), array( 'xg', '20120102090705', 'January', + 'January', 'Genitive month name (same in EN)' ), array( 'j', '20120102090705', '2', + '2', 'Day of month (not zero pad)' ), array( 'd', '20120102090705', '02', + '02', 'Day of month (zero-pad)' ), array( 'z', '20120102090705', '1', + '1', 'Day of year (zero-indexed)' ), array( 'D', '20120102090705', 'Mon', + 'Mon', 'Day of week (abbrev)' ), array( 'l', '20120102090705', 'Monday', + 'Monday', 'Full day of week' ), array( 'N', '20120101090705', '7', + '7', 'Day of week (Mon=1, Sun=7)' ), array( 'w', '20120101090705', '0', + '0', 'Day of week (Sun=0, Sat=6)' ), array( 'N', '20120102090705', '1', + '1', 'Day of week' ), array( 'a', '20120102090705', 'am', + 'am', 'am vs pm' ), array( 'A', '20120102120000', 'PM', + 'PM', 'AM vs PM' ), array( 'a', '20120102000000', 'am', + 'am', 'AM vs PM' ), array( 'g', '20120102090705', '9', + '9', '12 hour, not Zero' ), array( 'h', '20120102090705', '09', + '09', '12 hour, zero padded' ), array( 'G', '20120102090705', '9', + '9', '24 hour, not zero' ), array( 'H', '20120102090705', '09', + '09', '24 hour, zero' ), array( 'H', '20120102110705', '11', + '11', '24 hour, zero' ), array( 'i', '20120102090705', '07', + '07', 'Minutes' ), array( 's', '20120102090705', '05', + '05', 'seconds' ), array( 'U', '20120102090705', '1325495225', + '1325462825', 'unix time' ), array( 't', '20120102090705', '31', + '31', 'Days in current month' ), array( 'c', '20120102090705', '2012-01-02T09:07:05+00:00', + '2012-01-02T09:07:05+09:00', 'ISO 8601 timestamp' ), array( 'r', '20120102090705', 'Mon, 02 Jan 2012 09:07:05 +0000', + 'Mon, 02 Jan 2012 09:07:05 +0900', 'RFC 5322' ), array( + 'e', + '20120102090705', + 'UTC', + 'Asia/Seoul', + 'Timezone identifier' + ), + array( + 'I', + '19880602090705', + '0', + '1', + 'DST indicator' + ), + array( + 'O', + '20120102090705', + '+0000', + '+0900', + 'Timezone offset' + ), + array( + 'P', + '20120102090705', + '+00:00', + '+09:00', + 'Timezone offset with colon' + ), + array( + 'T', + '20120102090705', + 'UTC', + 'KST', + 'Timezone abbreviation' + ), + array( + 'Z', + '20120102090705', + '0', + '32400', + 'Timezone offset in seconds' + ), + array( 'xmj xmF xmn xmY', '20120102090705', '7 Safar 2 1433', + '7 Safar 2 1433', 'Islamic' ), array( 'xij xiF xin xiY', '20120102090705', '12 Dey 10 1390', + '12 Dey 10 1390', 'Iranian' ), array( 'xjj xjF xjn xjY', '20120102090705', '7 Tevet 4 5772', + '7 Tevet 4 5772', 'Hebrew' ), array( 'xjt', '20120102090705', '29', + '29', 'Hebrew number of days in month' ), array( 'xjx', '20120102090705', 'Tevet', + 'Tevet', 'Hebrew genitive month name (No difference in EN)' ), array( 'xkY', '20120102090705', '2555', + '2555', 'Thai year' ), array( 'xoY', '20120102090705', '101', + '101', 'Minguo' ), array( 'xtY', '20120102090705', '平成24', + '平成24', 'nengo' ), array( 'xrxkYY', '20120102090705', 'MMDLV2012', + 'MMDLV2012', 'Roman numerals' ), array( 'xhxjYY', '20120102090705', 'ה\'תשע"ב2012', + 'ה\'תשע"ב2012', 'Hebrew numberals' ), array( 'xnY', '20120102090705', '2012', + '2012', 'Raw numerals (doesn\'t mean much in EN)' ), array( '[[Y "(yea"\\r)]] \\"xx\\"', '20120102090705', '[[2012 (year)]] "x"', + '[[2012 (year)]] "x"', 'Various escaping' ), @@ -676,16 +981,17 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideFormatSizes + * @covers Language::formatSize */ - function testFormatSize( $size, $expected, $msg ) { + public function testFormatSize( $size, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->formatSize( $size ), + $this->getLang()->formatSize( $size ), "formatSize('$size'): $msg" ); } - function provideFormatSizes() { + public static function provideFormatSizes() { return array( array( 0, @@ -738,16 +1044,17 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideFormatBitrate + * @covers Language::formatBitrate */ - function testFormatBitrate( $bps, $expected, $msg ) { + public function testFormatBitrate( $bps, $expected, $msg ) { $this->assertEquals( $expected, - $this->lang->formatBitrate( $bps ), + $this->getLang()->formatBitrate( $bps ), "formatBitrate('$bps'): $msg" ); } - function provideFormatBitrate() { + public static function provideFormatBitrate() { return array( array( 0, @@ -808,19 +1115,19 @@ class LanguageTest extends MediaWikiTestCase { } - /** * @dataProvider provideFormatDuration + * @covers Language::formatDuration */ - function testFormatDuration( $duration, $expected, $intervals = array() ) { + public function testFormatDuration( $duration, $expected, $intervals = array() ) { $this->assertEquals( $expected, - $this->lang->formatDuration( $duration, $intervals ), + $this->getLang()->formatDuration( $duration, $intervals ), "formatDuration('$duration'): $expected" ); } - function provideFormatDuration() { + public static function provideFormatDuration() { return array( array( 0, @@ -859,35 +1166,36 @@ class LanguageTest extends MediaWikiTestCase { '2 days', ), array( - 365.25 * 86400, // 365.25 * 86400 = 31557600 + // ( 365 + ( 24 * 3 + 25 ) / 400 ) * 86400 = 31556952 + ( 365 + ( 24 * 3 + 25 ) / 400.0 ) * 86400, '1 year', ), array( - 2 * 31557600, + 2 * 31556952, '2 years', ), array( - 10 * 31557600, + 10 * 31556952, '1 decade', ), array( - 20 * 31557600, + 20 * 31556952, '2 decades', ), array( - 100 * 31557600, + 100 * 31556952, '1 century', ), array( - 200 * 31557600, + 200 * 31556952, '2 centuries', ), array( - 1000 * 31557600, + 1000 * 31556952, '1 millennium', ), array( - 2000 * 31557600, + 2000 * 31556952, '2 millennia', ), array( @@ -899,11 +1207,11 @@ class LanguageTest extends MediaWikiTestCase { '1 hour and 1 second' ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days, 2 hours and 30 minutes' ), array( - 42 * 1000 * 31557600 + 42, + 42 * 1000 * 31556952 + 42, '42 millennia and 42 seconds' ), array( @@ -922,7 +1230,7 @@ class LanguageTest extends MediaWikiTestCase { array( 'seconds' ), ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days and 150 minutes', array( 'years', 'days', 'minutes' ), ), @@ -932,7 +1240,7 @@ class LanguageTest extends MediaWikiTestCase { array( 'years', 'days' ), ), array( - 31557600 + 2 * 86400 + 9000, + 31556952 + 2 * 86400 + 9000, '1 year, 2 days and 150 minutes', array( 'minutes', 'days', 'years' ), ), @@ -946,17 +1254,18 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideCheckTitleEncodingData + * @covers Language::checkTitleEncoding */ - function testCheckTitleEncoding( $s ) { + public function testCheckTitleEncoding( $s ) { $this->assertEquals( $s, - $this->lang->checkTitleEncoding($s), + $this->getLang()->checkTitleEncoding( $s ), "checkTitleEncoding('$s')" ); } - function provideCheckTitleEncodingData() { - return array ( + public static function provideCheckTitleEncodingData() { + return array( array( "" ), array( "United States of America" ), // 7bit ASCII array( rawurldecode( "S%C3%A9rie%20t%C3%A9l%C3%A9vis%C3%A9e" ) ), @@ -1010,8 +1319,9 @@ class LanguageTest extends MediaWikiTestCase { /** * @dataProvider provideRomanNumeralsData + * @covers Language::romanNumeral */ - function testRomanNumerals( $num, $numerals ) { + public function testRomanNumerals( $num, $numerals ) { $this->assertEquals( $numerals, Language::romanNumeral( $num ), @@ -1019,7 +1329,7 @@ class LanguageTest extends MediaWikiTestCase { ); } - function provideRomanNumeralsData() { + public static function provideRomanNumeralsData() { return array( array( 1, 'I' ), array( 2, 'II' ), @@ -1061,9 +1371,197 @@ class LanguageTest extends MediaWikiTestCase { array( 7000, 'MMMMMMM' ), array( 8000, 'MMMMMMMM' ), array( 9000, 'MMMMMMMMM' ), - array( 9999, 'MMMMMMMMMCMXCIX'), + array( 9999, 'MMMMMMMMMCMXCIX' ), array( 10000, 'MMMMMMMMMM' ), ); } -} + /** + * @dataProvider providePluralData + * @covers Language::convertPlural + */ + public function testConvertPlural( $expected, $number, $forms ) { + $chosen = $this->getLang()->convertPlural( $number, $forms ); + $this->assertEquals( $expected, $chosen ); + } + + public static function providePluralData() { + // Params are: [expected text, number given, [the plural forms]] + return array( + array( 'plural', 0, array( + 'singular', 'plural' + ) ), + array( 'explicit zero', 0, array( + '0=explicit zero', 'singular', 'plural' + ) ), + array( 'explicit one', 1, array( + 'singular', 'plural', '1=explicit one', + ) ), + array( 'singular', 1, array( + 'singular', 'plural', '0=explicit zero', + ) ), + array( 'plural', 3, array( + '0=explicit zero', '1=explicit one', 'singular', 'plural' + ) ), + array( 'explicit eleven', 11, array( + 'singular', 'plural', '11=explicit eleven', + ) ), + array( 'plural', 12, array( + 'singular', 'plural', '11=explicit twelve', + ) ), + array( 'plural', 12, array( + 'singular', 'plural', '=explicit form', + ) ), + array( 'other', 2, array( + 'kissa=kala', '1=2=3', 'other', + ) ), + array( '', 2, array( + '0=explicit zero', '1=explicit one', + ) ), + ); + } + + /** + * @covers Language::translateBlockExpiry() + * @dataProvider provideTranslateBlockExpiry + */ + public function testTranslateBlockExpiry( $expectedData, $str, $desc ) { + $lang = $this->getLang(); + if ( is_array( $expectedData ) ) { + list( $func, $arg ) = $expectedData; + $expected = $lang->$func( $arg ); + } else { + $expected = $expectedData; + } + $this->assertEquals( $expected, $lang->translateBlockExpiry( $str ), $desc ); + } + + public static function provideTranslateBlockExpiry() { + return array( + array( '2 hours', '2 hours', 'simple data from ipboptions' ), + array( 'indefinite', 'infinite', 'infinite from ipboptions' ), + array( 'indefinite', 'infinity', 'alternative infinite from ipboptions' ), + array( 'indefinite', 'indefinite', 'another alternative infinite from ipboptions' ), + array( array( 'formatDuration', 1023 * 60 * 60 ), '1023 hours', 'relative' ), + array( array( 'formatDuration', -1023 ), '-1023 seconds', 'negative relative' ), + array( array( 'formatDuration', 0 ), 'now', 'now' ), + array( array( 'timeanddate', '20120102070000' ), '2012-1-1 7:00 +1 day', 'mixed, handled as absolute' ), + array( array( 'timeanddate', '19910203040506' ), '1991-2-3 4:05:06', 'absolute' ), + array( array( 'timeanddate', '19700101000000' ), '1970-1-1 0:00:00', 'absolute at epoch' ), + array( array( 'timeanddate', '19691231235959' ), '1969-12-31 23:59:59', 'time before epoch' ), + array( 'dummy', 'dummy', 'return garbage as is' ), + ); + } + + /** + * @covers Language::commafy() + * @dataProvider provideCommafyData + */ + public function testCommafy( $number, $numbersWithCommas ) { + $this->assertEquals( + $numbersWithCommas, + $this->getLang()->commafy( $number ), + "commafy('$number')" + ); + } + + public static function provideCommafyData() { + return array( + array( 1, '1' ), + array( 10, '10' ), + array( 100, '100' ), + array( 1000, '1,000' ), + array( 10000, '10,000' ), + array( 100000, '100,000' ), + array( 1000000, '1,000,000' ), + array( 1.0001, '1.0001' ), + array( 10.0001, '10.0001' ), + array( 100.0001, '100.0001' ), + array( 1000.0001, '1,000.0001' ), + array( 10000.0001, '10,000.0001' ), + array( 100000.0001, '100,000.0001' ), + array( 1000000.0001, '1,000,000.0001' ), + ); + } + + /** + * @covers Language::listToText + */ + public function testListToText() { + $lang = $this->getLang(); + $and = $lang->getMessageFromDB( 'and' ); + $s = $lang->getMessageFromDB( 'word-separator' ); + $c = $lang->getMessageFromDB( 'comma-separator' ); + + $this->assertEquals( '', $lang->listToText( array() ) ); + $this->assertEquals( 'a', $lang->listToText( array( 'a' ) ) ); + $this->assertEquals( "a{$and}{$s}b", $lang->listToText( array( 'a', 'b' ) ) ); + $this->assertEquals( "a{$c}b{$and}{$s}c", $lang->listToText( array( 'a', 'b', 'c' ) ) ); + $this->assertEquals( "a{$c}b{$c}c{$and}{$s}d", $lang->listToText( array( 'a', 'b', 'c', 'd' ) ) ); + } + + /** + * @dataProvider provideIsSupportedLanguage + * @covers Language::isSupportedLanguage + */ + public function testIsSupportedLanguage( $code, $expected, $comment ) { + $this->assertEquals( $expected, Language::isSupportedLanguage( $code ), $comment ); + } + + public static function provideIsSupportedLanguage() { + return array( + array( 'en', true, 'is supported language' ), + array( 'fi', true, 'is supported language' ), + array( 'bunny', false, 'is not supported language' ), + array( 'FI', false, 'is not supported language, input should be in lower case' ), + ); + } + + /** + * @dataProvider provideGetParentLanguage + * @covers Language::getParentLanguage + */ + public function testGetParentLanguage( $code, $expected, $comment ) { + $lang = Language::factory( $code ); + if ( is_null( $expected ) ) { + $this->assertNull( $lang->getParentLanguage(), $comment ); + } else { + $this->assertEquals( $expected, $lang->getParentLanguage()->getCode(), $comment ); + } + } + + public static function provideGetParentLanguage() { + return array( + array( 'zh-cn', 'zh', 'zh is the parent language of zh-cn' ), + array( 'zh', 'zh', 'zh is defined as the parent language of zh, because zh converter can convert zh-cn to zh' ), + array( 'zh-invalid', null, 'do not be fooled by arbitrarily composed language codes' ), + array( 'en-gb', null, 'en does not have converter' ), + array( 'en', null, 'en does not have converter. Although FakeConverter handles en -> en conversion but it is useless' ), + ); + } + + /** + * @dataProvider provideGetNamespaceAliases + * @covers Language::getNamespaceAliases + */ + public function testGetNamespaceAliases( $languageCode, $subset ) { + $language = Language::factory( $languageCode ); + $aliases = $language->getNamespaceAliases(); + foreach ( $subset as $alias => $nsId ) { + $this->assertEquals( $nsId, $aliases[$alias] ); + } + } + + public static function provideGetNamespaceAliases() { + // TODO: Add tests for NS_PROJECT_TALK and GenderNamespaces + return array( + array( + 'zh', + array( + '文件' => NS_FILE, + '檔案' => NS_FILE, + ), + ), + ); + } +} diff --git a/tests/phpunit/languages/LanguageTiTest.php b/tests/phpunit/languages/LanguageTiTest.php index 4bfaa009..e225af97 100644 --- a/tests/phpunit/languages/LanguageTiTest.php +++ b/tests/phpunit/languages/LanguageTiTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageTi.php */ -class LanguageTiTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Ti' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageTiTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguageTlTest.php b/tests/phpunit/languages/LanguageTlTest.php index a1facd14..7ac51c69 100644 --- a/tests/phpunit/languages/LanguageTlTest.php +++ b/tests/phpunit/languages/LanguageTlTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageTl.php */ -class LanguageTlTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Tl' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageTlTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/LanguageTrTest.php b/tests/phpunit/languages/LanguageTrTest.php index bda4c9d9..8fc2795c 100644 --- a/tests/phpunit/languages/LanguageTrTest.php +++ b/tests/phpunit/languages/LanguageTrTest.php @@ -6,15 +6,7 @@ */ /** Tests for MediaWiki languages/LanguageTr.php */ -class LanguageTrTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Tr' ); - } - function tearDown() { - unset( $this->lang ); - } +class LanguageTrTest extends LanguageClassesTestCase { /** * See @bug 28040 @@ -25,11 +17,11 @@ class LanguageTrTest extends MediaWikiTestCase { * @see http://en.wikipedia.org/wiki/Dotted_and_dotless_I * @dataProvider provideDottedAndDotlessI */ - function testDottedAndDotlessI( $func, $input, $inputCase, $expected ) { - if( $func == 'ucfirst' ) { - $res = $this->lang->ucfirst( $input ); - } elseif( $func == 'lcfirst' ) { - $res = $this->lang->lcfirst( $input ); + public function testDottedAndDotlessI( $func, $input, $inputCase, $expected ) { + if ( $func == 'ucfirst' ) { + $res = $this->getLang()->ucfirst( $input ); + } elseif ( $func == 'lcfirst' ) { + $res = $this->getLang()->lcfirst( $input ); } else { throw new MWException( __METHOD__ . " given an invalid function name '$func'" ); } @@ -39,7 +31,7 @@ class LanguageTrTest extends MediaWikiTestCase { $this->assertEquals( $expected, $res, $msg ); } - function provideDottedAndDotlessI() { + public static function provideDottedAndDotlessI() { return array( # function, input, input case, expected # Case changed: @@ -64,5 +56,4 @@ class LanguageTrTest extends MediaWikiTestCase { ); } - } diff --git a/tests/phpunit/languages/LanguageUkTest.php b/tests/phpunit/languages/LanguageUkTest.php index 60fafb0d..9051bcff 100644 --- a/tests/phpunit/languages/LanguageUkTest.php +++ b/tests/phpunit/languages/LanguageUkTest.php @@ -6,25 +6,38 @@ * @file */ -/** Tests for MediaWiki languages/classes/LanguageUk.php */ -class LanguageUkTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Uk' ); +/** Tests for Ukrainian */ +class LanguageUkTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'few', 'many', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function tearDown() { - unset( $this->lang ); + + /** + * Test explicit plural forms - n=FormN forms + * @covers Language::convertPlural + */ + public function testExplicitPlural() { + $forms = array( 'one', 'few', 'many', 'other', '12=dozen' ); + $this->assertEquals( 'dozen', $this->getLang()->convertPlural( 12, $forms ) ); + $forms = array( 'one', 'few', 'many', '100=hundred', 'other', '12=dozen' ); + $this->assertEquals( 'hundred', $this->getLang()->convertPlural( 100, $forms ) ); } - /** @dataProvider providePluralFourForms */ - function testPluralFourForms( $result, $value ) { - $forms = array( 'one', 'few', 'many', 'other' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providePluralFourForms() { - return array ( + public static function providePlural() { + return array( array( 'one', 1 ), array( 'many', 11 ), array( 'one', 91 ), @@ -38,17 +51,22 @@ class LanguageUkTest extends MediaWikiTestCase { array( 'many', 120 ), ); } - /** @dataProvider providePluralTwoForms */ - function testPluralTwoForms( $result, $value ) { - $forms = array( 'one', 'several' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + + /** + * @dataProvider providePluralTwoForms + * @covers Language::convertPlural + */ + public function testPluralTwoForms( $result, $value ) { + $forms = array( '1=one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - function providePluralTwoForms() { - return array ( + + public static function providePluralTwoForms() { + return array( array( 'one', 1 ), - array( 'several', 11 ), - array( 'several', 91 ), - array( 'several', 121 ), + array( 'other', 11 ), + array( 'other', 91 ), + array( 'other', 121 ), ); } } diff --git a/tests/phpunit/languages/LanguageUzTest.php b/tests/phpunit/languages/LanguageUzTest.php index 72387283..13f57c16 100644 --- a/tests/phpunit/languages/LanguageUzTest.php +++ b/tests/phpunit/languages/LanguageUzTest.php @@ -10,26 +10,20 @@ * @copyright Copyright © 2012, Robin Pepermans * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr> * @file + * + * @todo methods in test class should be tidied: + * - Should be split into separate test methods and data providers + * - Tests for LanguageConverter and Language should probably be separate.. */ -require_once dirname( __DIR__ ) . '/bootstrap.php'; - /** Tests for MediaWiki languages/LanguageUz.php */ -class LanguageUzTest extends MediaWikiTestCase { - /* Language object. Initialized before each test */ - private $lang; - - function setUp() { - $this->lang = Language::factory( 'uz' ); - } - function tearDown() { - unset( $this->lang ); - } +class LanguageUzTest extends LanguageClassesTestCase { /** * @author Nikola Smolenski + * @covers LanguageConverter::convertTo */ - function testConversionToCyrillic() { + public function testConversionToCyrillic() { // A convertion of Latin to Cyrillic $this->assertEquals( 'абвгғ', $this->convertToCyrillic( 'abvggʻ' ) @@ -48,7 +42,10 @@ class LanguageUzTest extends MediaWikiTestCase { ); } - function testConversionToLatin() { + /** + * @covers LanguageConverter::convertTo + */ + public function testConversionToLatin() { // A simple convertion of Latin to Latin $this->assertEquals( 'abdef', $this->convertToLatin( 'abdef' ) @@ -66,20 +63,21 @@ class LanguageUzTest extends MediaWikiTestCase { * @param $variant string Language variant 'uz-cyrl' or 'uz-latn' * @param $msg string Optional message */ - function assertUnConverted( $text, $variant, $msg = '' ) { + protected function assertUnConverted( $text, $variant, $msg = '' ) { $this->assertEquals( $text, $this->convertTo( $text, $variant ), $msg ); } + /** * Wrapper to verify a text is different once converted to a variant. * @param $text string Text to convert * @param $variant string Language variant 'uz-cyrl' or 'uz-latn' * @param $msg string Optional message */ - function assertConverted( $text, $variant, $msg = '' ) { + protected function assertConverted( $text, $variant, $msg = '' ) { $this->assertNotEquals( $text, $this->convertTo( $text, $variant ), @@ -92,29 +90,32 @@ class LanguageUzTest extends MediaWikiTestCase { * using the cyrillic variant and converted to Latin when using * the Latin variant. */ - function assertCyrillic( $text, $msg = '' ) { + protected function assertCyrillic( $text, $msg = '' ) { $this->assertUnConverted( $text, 'uz-cyrl', $msg ); $this->assertConverted( $text, 'uz-latn', $msg ); } + /** * Verifiy the given Latin text is not converted when using * using the Latin variant and converted to Cyrillic when using * the Cyrillic variant. */ - function assertLatin( $text, $msg = '' ) { + protected function assertLatin( $text, $msg = '' ) { $this->assertUnConverted( $text, 'uz-latn', $msg ); $this->assertConverted( $text, 'uz-cyrl', $msg ); } /** Wrapper for converter::convertTo() method*/ - function convertTo( $text, $variant ) { - return $this->lang->mConverter->convertTo( $text, $variant ); + protected function convertTo( $text, $variant ) { + return $this->getLang()->mConverter->convertTo( $text, $variant ); } - function convertToCyrillic( $text ) { + + protected function convertToCyrillic( $text ) { return $this->convertTo( $text, 'uz-cyrl' ); } - function convertToLatin( $text ) { + + protected function convertToLatin( $text ) { return $this->convertTo( $text, 'uz-latn' ); } } diff --git a/tests/phpunit/languages/LanguageWaTest.php b/tests/phpunit/languages/LanguageWaTest.php index 172f19b9..d05196c0 100644 --- a/tests/phpunit/languages/LanguageWaTest.php +++ b/tests/phpunit/languages/LanguageWaTest.php @@ -6,27 +6,29 @@ */ /** Tests for MediaWiki languages/classes/LanguageWa.php */ -class LanguageWaTest extends MediaWikiTestCase { - private $lang; - - function setUp() { - $this->lang = Language::factory( 'Wa' ); - } - function tearDown() { - unset( $this->lang ); +class LanguageWaTest extends LanguageClassesTestCase { + /** + * @dataProvider providePlural + * @covers Language::convertPlural + */ + public function testPlural( $result, $value ) { + $forms = array( 'one', 'other' ); + $this->assertEquals( $result, $this->getLang()->convertPlural( $value, $forms ) ); } - /** @dataProvider providerPlural */ - function testPlural( $result, $value ) { - $forms = array( 'one', 'many' ); - $this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) ); + /** + * @dataProvider providePlural + * @covers Language::getPluralRuleType + */ + public function testGetPluralRuleType( $result, $value ) { + $this->assertEquals( $result, $this->getLang()->getPluralRuleType( $value ) ); } - function providerPlural() { - return array ( - array( 'one', 0 ), - array( 'one', 1 ), - array( 'many', 2 ), + public static function providePlural() { + return array( + array( 'one', 0 ), + array( 'one', 1 ), + array( 'other', 2 ), ); } } diff --git a/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php b/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php index 033164b0..bd3809d7 100644 --- a/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php +++ b/tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php @@ -9,7 +9,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { * @dataProvider validTestCases */ function testValidRules( $expected, $rules, $number, $comment ) { - $result = CLDRPluralRuleEvaluator::evaluate( $number, (array) $rules ); + $result = CLDRPluralRuleEvaluator::evaluate( $number, (array)$rules ); $this->assertEquals( $expected, $result, $comment ); } @@ -18,7 +18,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { * @expectedException CLDRPluralRuleError */ function testInvalidRules( $rules, $comment ) { - CLDRPluralRuleEvaluator::evaluate( 1, (array) $rules ); + CLDRPluralRuleEvaluator::evaluate( 1, (array)$rules ); } function validTestCases() { @@ -31,19 +31,19 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 1, 'n is 1', 1.1, 'float number and is' ), array( 1, 'n is 1', 2, 'float number and is' ), - array( 0, 'n in 1,3,5', 3, '' ), + array( 0, 'n in 1,3,5', 3, '' ), array( 1, 'n not in 1,3,5', 5, '' ), - array( 1, 'n in 1,3,5', 2, '' ), + array( 1, 'n in 1,3,5', 2, '' ), array( 0, 'n not in 1,3,5', 4, '' ), - array( 0, 'n in 1..3', 2, '' ), - array( 0, 'n in 1..3', 3, 'in is inclusive' ), - array( 1, 'n in 1..3', 0, '' ), + array( 0, 'n in 1..3', 2, '' ), + array( 0, 'n in 1..3', 3, 'in is inclusive' ), + array( 1, 'n in 1..3', 0, '' ), - array( 1, 'n not in 1..3', 2, '' ), - array( 1, 'n not in 1..3', 3, 'in is inclusive' ), - array( 0, 'n not in 1..3', 0, '' ), + array( 1, 'n not in 1..3', 2, '' ), + array( 1, 'n not in 1..3', 3, 'in is inclusive' ), + array( 0, 'n not in 1..3', 0, '' ), array( 1, 'n is not 1 and n is not 2 and n is not 3', 1, 'and relation' ), array( 0, 'n is not 1 and n is not 2 and n is not 4', 3, 'and relation' ), @@ -78,6 +78,56 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 0, 'n in 3..10,13..19', 13, 'scottish rule - ranges with comma' ), array( 0, '5 mod 3 is n', 2, 'n as result of mod - no need to pass' ), + + # Revision 33 new operand examples + # expected, rule, number, comment + array( 0, 'i is 1', '1.00', 'new operand i' ), + array( 0, 'v is 2', '1.00', 'new operand v' ), + array( 0, 'w is 0', '1.00', 'new operand w' ), + array( 0, 'f is 0', '1.00', 'new operand f' ), + array( 0, 't is 0', '1.00', 'new operand t' ), + + array( 0, 'i is 1', '1.30', 'new operand i' ), + array( 0, 'v is 2', '1.30', 'new operand v' ), + array( 0, 'w is 1', '1.30', 'new operand w' ), + array( 0, 'f is 30', '1.30', 'new operand f' ), + array( 0, 't is 3', '1.30', 'new operand t' ), + + array( 0, 'i is 1', '1.03', 'new operand i' ), + array( 0, 'v is 2', '1.03', 'new operand v' ), + array( 0, 'w is 2', '1.03', 'new operand w' ), + array( 0, 'f is 3', '1.03', 'new operand f' ), + array( 0, 't is 3', '1.03', 'new operand t' ), + + # Revision 33 new operator aliases + # expected, rule, number, comment + array( 0, 'n % 3 is 1', 7, 'new % operator' ), + array( 0, 'n = 1,3,5', 3, 'new = operator' ), + array( 1, 'n != 1,3,5', 5, 'new != operator' ), + + # Revision 33 samples + # expected, rule, number, comment + array( 0, 'n in 1,3,5@integer 3~10, 103~110, 1003, … @decimal 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 103.0, 1003.0, …', 3, 'samples' ), + + # Revision 33 some test cases from CLDR + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.1', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.01', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.10', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.010', 'pt one' ), + array( 0, 'i = 1 and v = 0 or i = 0 and t = 1', '0.100', 'pt one' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '0.0', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '0.2', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '10.0', 'pt other' ), + array( 1, 'i = 1 and v = 0 or i = 0 and t = 1', '100.0', 'pt other' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '2', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '4', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '22', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '102', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '0.2', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '0.4', 'bs few' ), + array( 0, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '10.2', 'bs few' ), + array( 1, 'v = 0 and i % 10 = 2..4 and i % 100 != 12..14 or f % 10 = 2..4 and f % 100 != 12..14', '10.0', 'bs other' ), + ); return $tests; @@ -89,7 +139,7 @@ class CLDRPluralRuleEvaluatorTest extends MediaWikiTestCase { array( 'n', 'just n' ), array( 'n is in 5', 'is in' ), ); + return $tests; } - } diff --git a/tests/phpunit/maintenance/DumpTestCase.php b/tests/phpunit/maintenance/DumpTestCase.php index d1344389..83d8c71d 100644 --- a/tests/phpunit/maintenance/DumpTestCase.php +++ b/tests/phpunit/maintenance/DumpTestCase.php @@ -35,7 +35,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @throws MWExcepion */ protected function addRevision( Page $page, $text, $summary ) { - $status = $page->doEdit( $text, $summary ); + $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary ); if ( $status->isGood() ) { $value = $status->getValue(); $revision = $value['revision']; @@ -57,14 +57,17 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { */ protected function gunzip( $fname ) { $gzipped_contents = file_get_contents( $fname ); - if ( $gzipped_contents === FALSE ) { + if ( $gzipped_contents === false ) { $this->fail( "Could not get contents of $fname" ); } - // We resort to use gzinflate instead of gzdecode, as gzdecode - // need not be available - $contents = gzinflate( substr( $gzipped_contents, 10, -8 ) ); - $this->assertEquals( strlen( $contents ), - file_put_contents( $fname, $contents ), "# bytes written" ); + + $contents = gzdecode( $gzipped_contents ); + + $this->assertEquals( + strlen( $contents ), + file_put_contents( $fname, $contents ), + '# bytes written' + ); } /** @@ -72,9 +75,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * * Clears $wgUser, and reports errors from addDBData to PHPUnit */ - public function setUp() { - global $wgUser; - + protected function setUp() { parent::setUp(); // Check if any Exception is stored for rethrowing from addDBData @@ -83,7 +84,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { throw $this->exceptionFromAddDBData; } - $wgUser = new User(); + $this->setMwGlobals( 'wgUser', new User() ); } /** @@ -116,15 +117,17 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @param $name string: name of the closing element to look for * (e.g.: "mediawiki" when looking for </mediawiki>) * - * @return bool: true iff the end node could be found. false otherwise. + * @return bool: true if the end node could be found. false otherwise. */ protected function skipToNodeEnd( $name ) { while ( $this->xml->read() ) { if ( $this->xml->nodeType == XMLReader::END_ELEMENT && - $this->xml->name == $name ) { + $this->xml->name == $name + ) { return true; } } + return false; } @@ -146,6 +149,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { return true; } } + return false; } @@ -189,7 +193,7 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { protected function skipWhitespace() { $cont = true; while ( $cont && ( ( $this->xml->nodeType == XMLReader::WHITESPACE ) - || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { + || ( $this->xml->nodeType == XMLReader::SIGNIFICANT_WHITESPACE ) ) ) { $cont = $this->xml->read(); } } @@ -272,7 +276,6 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertTextNode( "title", $name ); $this->assertTextNode( "ns", $ns ); $this->assertTextNode( "id", $id ); - } /** @@ -295,10 +298,13 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { * @param $text_sha1 string: the base36 SHA-1 of the revision's text * @param $text string|false: (optional) The revision's string, or false to check for a * revision stub + * @param $model String: the expected content model id (default: CONTENT_MODEL_WIKITEXT) + * @param $format String: the expected format model id (default: CONTENT_FORMAT_WIKITEXT) * @param $parentid int|false: (optional) id of the parent revision */ - protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false ) { - + protected function assertRevision( $id, $summary, $text_id, $text_bytes, $text_sha1, $text = false, $parentid = false, + $model = CONTENT_MODEL_WIKITEXT, $format = CONTENT_FORMAT_WIKITEXT + ) { $this->assertNodeStart( "revision" ); $this->skipWhitespace(); @@ -315,9 +321,33 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->skipWhitespace(); $this->assertTextNode( "comment", $summary ); + $this->skipWhitespace(); + + if ( $this->xml->name == "text" ) { + // note: <text> tag may occur here or at the very end. + $text_found = true; + $this->assertText( $id, $text_id, $text_bytes, $text ); + } else { + $text_found = false; + } $this->assertTextNode( "sha1", $text_sha1 ); + $this->assertTextNode( "model", $model ); + $this->skipWhitespace(); + + $this->assertTextNode( "format", $format ); + $this->skipWhitespace(); + + if ( !$text_found ) { + $this->assertText( $id, $text_id, $text_bytes, $text ); + } + + $this->assertNodeEnd( "revision" ); + $this->skipWhitespace(); + } + + protected function assertText( $id, $text_id, $text_bytes, $text ) { $this->assertNodeStart( "text", false ); if ( $text_bytes !== false ) { $this->assertEquals( $this->xml->getAttribute( "bytes" ), $text_bytes, @@ -331,7 +361,8 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertFalse( $this->xml->hasValue, "Revision has text" ); $this->assertTrue( $this->xml->read(), "Skipping text start tag" ); if ( ( $this->xml->nodeType == XMLReader::END_ELEMENT ) - && ( $this->xml->name == "text" ) ) { + && ( $this->xml->name == "text" ) + ) { $this->xml->read(); } @@ -344,9 +375,5 @@ abstract class DumpTestCase extends MediaWikiLangTestCase { $this->assertNodeEnd( "text" ); $this->skipWhitespace(); } - - $this->assertNodeEnd( "revision" ); - $this->skipWhitespace(); } - } diff --git a/tests/phpunit/maintenance/MaintenanceTest.php b/tests/phpunit/maintenance/MaintenanceTest.php index 4a6f08fa..318ce0da 100644 --- a/tests/phpunit/maintenance/MaintenanceTest.php +++ b/tests/phpunit/maintenance/MaintenanceTest.php @@ -43,7 +43,7 @@ class MaintenanceFixup extends Maintenance { private $testCase; /** - * shutdownSimulated === true iff simulateShutdown has done it's work + * shutdownSimulated === true if simulateShutdown has done it's work * * @var bool */ @@ -81,22 +81,23 @@ class MaintenanceFixup extends Maintenance { return; } - return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() ); + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); } /** * Safety net around register_shutdown_function of Maintenance.php */ public function __destruct() { - if ( ( ! $this->shutdownSimulated ) && ( ! $this->testCase->hasFailed() ) ) { + if ( !$this->shutdownSimulated ) { // Someone generated a MaintenanceFixup instance without calling // simulateShutdown. We'd have to raise a PHPUnit exception to correctly // flag this illegal usage. However, we are already in a destruktor, which - // would trigger undefined behaviour. Hence, we can only report to the + // would trigger undefined behavior. Hence, we can only report to the // error output :( Hopefully people read the PHPUnit output. - fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " destructed without " - . "calling simulateShutdown method. Call simulateShutdown on the " - . "instance before it gets destructed." ); + $name = $this->testCase->getName(); + fwrite( STDERR, "ERROR! Instance of " . __CLASS__ . " for test $name " + . "destructed without calling simulateShutdown method. Call " + . "simulateShutdown on the instance before it gets destructed." ); } // The following guard is required, as PHP does not offer default destructors :( @@ -111,7 +112,6 @@ class MaintenanceFixup extends Maintenance { } - // --- Making protected functions visible for test public function output( $out, $channel = null ) { @@ -119,17 +119,15 @@ class MaintenanceFixup extends Maintenance { // Maintenance::output signature. However, we do not use (or rely on) // those variables. Instead we pass to Maintenance::output whatever we // receive at runtime. - return call_user_func_array ( array( "parent", __FUNCTION__ ), func_get_args() ); + return call_user_func_array( array( "parent", __FUNCTION__ ), func_get_args() ); } - // --- Requirements for getting instance of abstract class public function execute() { $this->testCase->fail( __METHOD__ . " called unexpectedly" ); } - } class MaintenanceTest extends MediaWikiTestCase { @@ -148,6 +146,14 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m = new MaintenanceFixup( $this ); } + protected function tearDown() { + if ( $this->m ) { + $this->m->simulateShutdown(); + $this->m = null; + } + parent::tearDown(); + } + /** * asserts the output before and after simulating shutdown @@ -164,9 +170,10 @@ class MaintenanceTest extends MediaWikiTestCase { private function assertOutputPrePostShutdown( $preShutdownOutput, $expectNLAppending ) { $this->assertEquals( $preShutdownOutput, $this->getActualOutput(), - "Output before shutdown simulation" ); + "Output before shutdown simulation" ); $this->m->simulateShutdown(); + $this->m = null; $postShutdownOutput = $preShutdownOutput . ( $expectNLAppending ? "\n" : "" ); $this->expectOutputString( $postShutdownOutput ); @@ -176,49 +183,48 @@ class MaintenanceTest extends MediaWikiTestCase { // Although the following tests do not seem to be too consistent (compare for // example the newlines within the test.*StringString tests, or the // test.*Intermittent.* tests), the objective of these tests is not to describe - // consistent behaviour, but rather currently existing behaviour. - + // consistent behavior, but rather currently existing behavior. function testOutputEmpty() { $this->m->output( "" ); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testOutputString() { $this->m->output( "foo" ); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testOutputStringString() { $this->m->output( "foo" ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputStringNL() { $this->m->output( "foo\n" ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputStringNLNL() { $this->m->output( "foo\n\n" ); - $this->assertOutputPrePostShutdown( "foo\n\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\n", false ); } function testOutputStringNLString() { $this->m->output( "foo\nbar" ); - $this->assertOutputPrePostShutdown( "foo\nbar", False ); + $this->assertOutputPrePostShutdown( "foo\nbar", false ); } function testOutputStringNLStringNL() { $this->m->output( "foo\nbar\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLLinewise() { $this->m->output( "foo\n" ); $this->m->output( "bar\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLArbitrary() { @@ -229,7 +235,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba" ); $this->m->output( "" ); $this->m->output( "r\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputStringNLStringNLArbitraryAgain() { @@ -240,49 +246,49 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a" ); $this->m->output( "" ); $this->m->output( "r\n" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelEmpty() { $this->m->output( "", null ); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testOutputWNullChannelString() { $this->m->output( "foo", null ); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testOutputWNullChannelStringString() { $this->m->output( "foo", null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWNullChannelStringNL() { $this->m->output( "foo\n", null ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputWNullChannelStringNLNL() { $this->m->output( "foo\n\n", null ); - $this->assertOutputPrePostShutdown( "foo\n\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\n", false ); } function testOutputWNullChannelStringNLString() { $this->m->output( "foo\nbar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar", False ); + $this->assertOutputPrePostShutdown( "foo\nbar", false ); } function testOutputWNullChannelStringNLStringNL() { $this->m->output( "foo\nbar\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLLinewise() { $this->m->output( "foo\n", null ); $this->m->output( "bar\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLArbitrary() { @@ -293,7 +299,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba", null ); $this->m->output( "", null ); $this->m->output( "r\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWNullChannelStringNLStringNLArbitraryAgain() { @@ -304,17 +310,17 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a", null ); $this->m->output( "", null ); $this->m->output( "r\n", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputWChannelString() { $this->m->output( "foo", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputWChannelStringNL() { $this->m->output( "foo\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputWChannelStringNLNL() { @@ -323,23 +329,23 @@ class MaintenanceTest extends MediaWikiTestCase { // outputChanneled with a string ending in a nl ... which is not allowed // according to the documentation of outputChanneled) $this->m->output( "foo\n\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\n", True ); + $this->assertOutputPrePostShutdown( "foo\n", true ); } function testOutputWChannelStringNLString() { $this->m->output( "foo\nbar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWChannelStringNLStringNL() { $this->m->output( "foo\nbar\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWChannelStringNLStringNLLinewise() { $this->m->output( "foo\n", "bazChannel" ); $this->m->output( "bar\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelStringNLStringNLArbitrary() { @@ -350,7 +356,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "ba", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "r\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelStringNLStringNLArbitraryAgain() { @@ -361,7 +367,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "a", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "r\n", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputWMultipleChannelsChannelChange() { @@ -369,7 +375,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar", "bazChannel" ); $this->m->output( "qux", "quuxChannel" ); $this->m->output( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputWMultipleChannelsChannelChangeNL() { @@ -377,7 +383,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar\n", "bazChannel" ); $this->m->output( "qux\n", "quuxChannel" ); $this->m->output( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputWAndWOChannelStringStartWO() { @@ -385,7 +391,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar", "bazChannel" ); $this->m->output( "qux" ); $this->m->output( "quux", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nquxquux", True ); + $this->assertOutputPrePostShutdown( "foobar\nquxquux", true ); } function testOutputWAndWOChannelStringStartW() { @@ -393,27 +399,27 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "bar" ); $this->m->output( "qux", "bazChannel" ); $this->m->output( "quux" ); - $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", False ); + $this->assertOutputPrePostShutdown( "foo\nbarqux\nquux", false ); } function testOutputWChannelTypeSwitch() { $this->m->output( "foo", 1 ); $this->m->output( "bar", 1.0 ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputIntermittentEmpty() { $this->m->output( "foo" ); $this->m->output( "" ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputIntermittentFalse() { $this->m->output( "foo" ); $this->m->output( false ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputIntermittentFalseAfterOtherChannel() { @@ -421,35 +427,35 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->output( "foo" ); $this->m->output( false ); $this->m->output( "bar" ); - $this->assertOutputPrePostShutdown( "qux\nfoobar", False ); + $this->assertOutputPrePostShutdown( "qux\nfoobar", false ); } function testOutputWNullChannelIntermittentEmpty() { $this->m->output( "foo", null ); $this->m->output( "", null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWNullChannelIntermittentFalse() { $this->m->output( "foo", null ); $this->m->output( false, null ); $this->m->output( "bar", null ); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testOutputWChannelIntermittentEmpty() { $this->m->output( "foo", "bazChannel" ); $this->m->output( "", "bazChannel" ); $this->m->output( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputWChannelIntermittentFalse() { $this->m->output( "foo", "bazChannel" ); $this->m->output( false, "bazChannel" ); $this->m->output( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } // Note that (per documentation) outputChanneled does take strings that end @@ -457,23 +463,23 @@ class MaintenanceTest extends MediaWikiTestCase { function testOutputChanneledEmpty() { $this->m->outputChanneled( "" ); - $this->assertOutputPrePostShutdown( "\n", False ); + $this->assertOutputPrePostShutdown( "\n", false ); } function testOutputChanneledString() { $this->m->outputChanneled( "foo" ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputChanneledStringString() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledStringNLString() { $this->m->outputChanneled( "foo\nbar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledStringNLStringNLArbitraryAgain() { @@ -484,28 +490,28 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a" ); $this->m->outputChanneled( "" ); $this->m->outputChanneled( "r" ); - $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False ); + $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false ); } function testOutputChanneledWNullChannelEmpty() { $this->m->outputChanneled( "", null ); - $this->assertOutputPrePostShutdown( "\n", False ); + $this->assertOutputPrePostShutdown( "\n", false ); } function testOutputChanneledWNullChannelString() { $this->m->outputChanneled( "foo", null ); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testOutputChanneledWNullChannelStringString() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelStringNLString() { $this->m->outputChanneled( "foo\nbar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelStringNLStringNLArbitraryAgain() { @@ -516,23 +522,23 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a", null ); $this->m->outputChanneled( "", null ); $this->m->outputChanneled( "r", null ); - $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", False ); + $this->assertOutputPrePostShutdown( "\nfoo\n\n\nb\na\n\nr\n", false ); } function testOutputChanneledWChannelString() { $this->m->outputChanneled( "foo", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo", True ); + $this->assertOutputPrePostShutdown( "foo", true ); } function testOutputChanneledWChannelStringNLString() { $this->m->outputChanneled( "foo\nbar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWChannelStringString() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputChanneledWChannelStringNLStringNLArbitraryAgain() { @@ -543,7 +549,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "a", "bazChannel" ); $this->m->outputChanneled( "", "bazChannel" ); $this->m->outputChanneled( "r", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWMultipleChannelsChannelChange() { @@ -551,7 +557,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", "bazChannel" ); $this->m->outputChanneled( "qux", "quuxChannel" ); $this->m->outputChanneled( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foobar\nqux\ncorge", true ); } function testOutputChanneledWMultipleChannelsChannelChangeEnclosedNull() { @@ -559,7 +565,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", null ); $this->m->outputChanneled( "qux", null ); $this->m->outputChanneled( "corge", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true ); } function testOutputChanneledWMultipleChannelsChannelAfterNullChange() { @@ -567,7 +573,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", null ); $this->m->outputChanneled( "qux", null ); $this->m->outputChanneled( "corge", "quuxChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\ncorge", true ); } function testOutputChanneledWAndWOChannelStringStartWO() { @@ -575,7 +581,7 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar", "bazChannel" ); $this->m->outputChanneled( "qux" ); $this->m->outputChanneled( "quux", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", True ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux", true ); } function testOutputChanneledWAndWOChannelStringStartW() { @@ -583,114 +589,114 @@ class MaintenanceTest extends MediaWikiTestCase { $this->m->outputChanneled( "bar" ); $this->m->outputChanneled( "qux", "bazChannel" ); $this->m->outputChanneled( "quux" ); - $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\nqux\nquux\n", false ); } function testOutputChanneledWChannelTypeSwitch() { $this->m->outputChanneled( "foo", 1 ); $this->m->outputChanneled( "bar", 1.0 ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testOutputChanneledWOChannelIntermittentEmpty() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( "" ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false ); } function testOutputChanneledWOChannelIntermittentFalse() { $this->m->outputChanneled( "foo" ); $this->m->outputChanneled( false ); $this->m->outputChanneled( "bar" ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWNullChannelIntermittentEmpty() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( "", null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\n\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\n\nbar\n", false ); } function testOutputChanneledWNullChannelIntermittentFalse() { $this->m->outputChanneled( "foo", null ); $this->m->outputChanneled( false, null ); $this->m->outputChanneled( "bar", null ); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testOutputChanneledWChannelIntermittentEmpty() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( "", "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foobar", True ); + $this->assertOutputPrePostShutdown( "foobar", true ); } function testOutputChanneledWChannelIntermittentFalse() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->outputChanneled( false, "bazChannel" ); $this->m->outputChanneled( "bar", "bazChannel" ); - $this->assertOutputPrePostShutdown( "foo\nbar", True ); + $this->assertOutputPrePostShutdown( "foo\nbar", true ); } function testCleanupChanneledClean() { $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "", False ); + $this->assertOutputPrePostShutdown( "", false ); } function testCleanupChanneledAfterOutput() { $this->m->output( "foo" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testCleanupChanneledAfterOutputWNullChannel() { $this->m->output( "foo", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo", False ); + $this->assertOutputPrePostShutdown( "foo", false ); } function testCleanupChanneledAfterOutputWChannel() { $this->m->output( "foo", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutput() { $this->m->output( "foo\n" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutputWNullChannel() { $this->m->output( "foo\n", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterNLOutputWChannel() { $this->m->output( "foo\n", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWOChannel() { $this->m->outputChanneled( "foo" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWNullChannel() { $this->m->outputChanneled( "foo", null ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testCleanupChanneledAfterOutputChanneledWChannel() { $this->m->outputChanneled( "foo", "bazChannel" ); $this->m->cleanupChanneled(); - $this->assertOutputPrePostShutdown( "foo\n", False ); + $this->assertOutputPrePostShutdown( "foo\n", false ); } function testMultipleMaintenanceObjectsInteractionOutput() { @@ -700,9 +706,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testMultipleMaintenanceObjectsInteractionOutputWNullChannel() { @@ -712,9 +718,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar", null ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar", False ); + $this->assertOutputPrePostShutdown( "foobar", false ); } function testMultipleMaintenanceObjectsInteractionOutputWChannel() { @@ -724,9 +730,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionOutputWNullChannelNL() { @@ -736,9 +742,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar\n", null ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputWChannelNL() { @@ -748,9 +754,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->output( "bar\n", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionOutputChanneled() { @@ -760,9 +766,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar" ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputChanneledWNullChannel() { @@ -772,9 +778,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", null ); $this->assertEquals( "foo\nbar\n", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foo\nbar\n", False ); + $this->assertOutputPrePostShutdown( "foo\nbar\n", false ); } function testMultipleMaintenanceObjectsInteractionOutputChanneledWChannel() { @@ -784,9 +790,9 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before shutdown simulation (m2)" ); + "Output before shutdown simulation (m2)" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n", True ); + $this->assertOutputPrePostShutdown( "foobar\n", true ); } function testMultipleMaintenanceObjectsInteractionCleanupChanneledWChannel() { @@ -796,17 +802,15 @@ class MaintenanceTest extends MediaWikiTestCase { $m2->outputChanneled( "bar", "bazChannel" ); $this->assertEquals( "foobar", $this->getActualOutput(), - "Output before first cleanup" ); + "Output before first cleanup" ); $this->m->cleanupChanneled(); $this->assertEquals( "foobar\n", $this->getActualOutput(), - "Output after first cleanup" ); + "Output after first cleanup" ); $m2->cleanupChanneled(); $this->assertEquals( "foobar\n\n", $this->getActualOutput(), - "Output after second cleanup" ); + "Output after second cleanup" ); $m2->simulateShutdown(); - $this->assertOutputPrePostShutdown( "foobar\n\n", False ); + $this->assertOutputPrePostShutdown( "foobar\n\n", false ); } - - -}
\ No newline at end of file +} diff --git a/tests/phpunit/maintenance/backupPrefetchTest.php b/tests/phpunit/maintenance/backupPrefetchTest.php index 8ff85574..bc2d7375 100644 --- a/tests/phpunit/maintenance/backupPrefetchTest.php +++ b/tests/phpunit/maintenance/backupPrefetchTest.php @@ -36,7 +36,6 @@ class BaseDumpTest extends MediaWikiTestCase { private function assertPrefetchEquals( $expected, $page, $revision ) { $this->assertEquals( $expected, $this->dump->prefetch( $page, $revision ), "Prefetch of page $page revision $revision" ); - } function testSequential() { @@ -156,7 +155,7 @@ class BaseDumpTest extends MediaWikiTestCase { <siteinfo> <sitename>wikisvn</sitename> <base>http://localhost/wiki-svn/index.php/Main_Page</base> - <generator>MediaWiki 1.20alpha</generator> + <generator>MediaWiki 1.21alpha</generator> <case>first-letter</case> <namespaces> <namespace key="-2" case="first-letter">Media</namespace> @@ -181,7 +180,6 @@ class BaseDumpTest extends MediaWikiTestCase { </siteinfo> '; - // An array holding the pages that are available for prefetch $available_pages = array(); @@ -199,6 +197,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP1Summary1</comment> <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1> <text xml:space="preserve">BackupDumperTestP1Text1</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> </page> '; @@ -216,6 +216,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP2Summary1</comment> <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1> <text xml:space="preserve">BackupDumperTestP2Text1</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> <revision> <id>5</id> @@ -227,6 +229,8 @@ class BaseDumpTest extends MediaWikiTestCase { <comment>BackupDumperTestP2Summary4 extra</comment> <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1> <text xml:space="preserve">BackupDumperTestP2Text4 some additional Text</text> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> </revision> </page> '; @@ -243,6 +247,8 @@ class BaseDumpTest extends MediaWikiTestCase { </contributor> <comment>Talk BackupDumperTestP1 Summary1</comment> <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1> + <model name="wikitext">1</model> + <format mime="text/x-wiki">1</format> <text xml:space="preserve">Talk about BackupDumperTestP1 Text1</text> </revision> </page> @@ -257,14 +263,13 @@ class BaseDumpTest extends MediaWikiTestCase { foreach ( $requested_pages as $i ) { $this->assertTrue( array_key_exists( $i, $available_pages ), "Check for availability of requested page " . $i ); - $content .= $available_pages[ $i ]; + $content .= $available_pages[$i]; } $content .= $tail; $this->assertEquals( strlen( $content ), file_put_contents( - $fname, $content ), "Length of prepared prefetch" ); + $fname, $content ), "Length of prepared prefetch" ); return $fname; } - } diff --git a/tests/phpunit/maintenance/backupTextPassTest.php b/tests/phpunit/maintenance/backupTextPassTest.php index a0bbadf9..653a1145 100644 --- a/tests/phpunit/maintenance/backupTextPassTest.php +++ b/tests/phpunit/maintenance/backupTextPassTest.php @@ -26,16 +26,18 @@ class TextPassDumperTest extends DumpTestCase { $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; + $ns = $this->getDefaultWikitextNS(); + try { // Simple page - $title = Title::newFromText( 'BackupDumperTestP1' ); + $title = Title::newFromText( 'BackupDumperTestP1', $ns ); $page = WikiPage::factory( $title ); list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page, "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" ); $this->pageId1 = $page->getId(); // Page with more than one revision - $title = Title::newFromText( 'BackupDumperTestP2' ); + $title = Title::newFromText( 'BackupDumperTestP2', $ns ); $page = WikiPage::factory( $title ); list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page, "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" ); @@ -49,7 +51,7 @@ class TextPassDumperTest extends DumpTestCase { $this->pageId2 = $page->getId(); // Deleted page. - $title = Title::newFromText( 'BackupDumperTestP3' ); + $title = Title::newFromText( 'BackupDumperTestP3', $ns ); $page = WikiPage::factory( $title ); list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page, "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" ); @@ -59,6 +61,13 @@ class TextPassDumperTest extends DumpTestCase { $page->doDeleteArticle( "Testing ;)" ); // Page from non-default namespace + + if ( $ns === NS_TALK ) { + // @todo work around this. + throw new MWException( "The default wikitext namespace is the talk namespace. " + . " We can't currently deal with that." ); + } + $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK ); $page = WikiPage::factory( $title ); list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page, @@ -71,10 +80,9 @@ class TextPassDumperTest extends DumpTestCase { // DumpTestCase $this->exceptionFromAddDBData = $e; } - } - public function setUp() { + protected function setUp() { parent::setUp(); // Since we will restrict dumping by page ranges (to allow @@ -85,15 +93,14 @@ class TextPassDumperTest extends DumpTestCase { array( $this->pageId2, $this->pageId3, $this->pageId4 ), array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ), "Page ids increasing without holes" ); - } function testPlain() { // Setting up the dump $nameStub = $this->setUpStub(); $nameFull = $this->getNewTempFile(); - $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub, - "--output=file:" . $nameFull ) ); + $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + "--output=file:" . $nameFull ) ); $dumper->reporting = false; $dumper->setDb( $this->db ); @@ -147,7 +154,7 @@ class TextPassDumperTest extends DumpTestCase { ); // The mock itself - $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', FALSE ); + $prefetchMock = $this->getMock( 'BaseDump', array( 'prefetch' ), array(), '', false ); $prefetchMock->expects( $this->exactly( 6 ) ) ->method( 'prefetch' ) ->will( $this->returnValueMap( $prefetchMap ) ); @@ -155,8 +162,8 @@ class TextPassDumperTest extends DumpTestCase { // Setting up of the dump $nameStub = $this->setUpStub(); $nameFull = $this->getNewTempFile(); - $dumper = new TextPassDumper( array ( "--stub=file:" - . $nameStub, "--output=file:" . $nameFull ) ); + $dumper = new TextPassDumper( array( "--stub=file:" + . $nameStub, "--output=file:" . $nameFull ) ); $dumper->prefetch = $prefetchMock; $dumper->reporting = false; $dumper->setDb( $this->db ); @@ -205,7 +212,6 @@ class TextPassDumperTest extends DumpTestCase { $this->assertPageEnd(); $this->assertDumpEnd(); - } /** @@ -221,7 +227,7 @@ class TextPassDumperTest extends DumpTestCase { $nameOutputDir = $this->getNewTempDirectory(); $stderr = fopen( 'php://output', 'a' ); - if ( $stderr === FALSE ) { + if ( $stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -230,7 +236,6 @@ class TextPassDumperTest extends DumpTestCase { $minDuration = 2; // We want the dump to take at least this many seconds $checkpointAfter = 0.5; // Generate checkpoint after this many seconds - // Until a dump takes at least $minDuration seconds, perform a dump and check // duration. If the dump did not take long enough increase the iteration // count, to generate a bigger stub file next time. @@ -241,10 +246,10 @@ class TextPassDumperTest extends DumpTestCase { $this->assertTrue( wfMkdirParents( $nameOutputDir ), "Creating temporary output directory " ); $this->setUpStub( $nameStub, $iterations ); - $dumper = new TextPassDumper( array ( "--stub=file:" . $nameStub, - "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full", - "--maxtime=1" /*This is in minutes. Fixup is below*/, - "--checkpointfile=checkpoint-%s-%s.xml.gz" ) ); + $dumper = new TextPassDumper( array( "--stub=file:" . $nameStub, + "--output=" . $checkpointFormat . ":" . $nameOutputDir . "/full", + "--maxtime=1" /*This is in minutes. Fixup is below*/, + "--checkpointfile=checkpoint-%s-%s.xml.gz" ) ); $dumper->setDb( $this->db ); $dumper->maxTimeAllowed = $checkpointAfter; // Patching maxTime from 1 minute $dumper->stderr = $stderr; @@ -274,7 +279,7 @@ class TextPassDumperTest extends DumpTestCase { $this->assertLessThan( 50000, $iterations, "Emergency stop against infinitely increasing iteration " - . "count ( last duration: $lastDuration )" ); + . "count ( last duration: $lastDuration )" ); } } @@ -291,11 +296,11 @@ class TextPassDumperTest extends DumpTestCase { // Each run of the following loop body tries to handle exactly 1 /page/ (not // iteration of stub content). $i is only increased after having treated page 4. - for ( $i = 0 ; $i < $iterations ; ) { + for ( $i = 0; $i < $iterations; ) { // 1. Assuring a file is opened and ready. Skipping across header if // necessary. - if ( ! $fileOpened ) { + if ( !$fileOpened ) { $this->assertNotEmpty( $files, "No more existing dump files, " . "but not yet all pages found" ); $fname = array_shift( $files ); @@ -314,65 +319,65 @@ class TextPassDumperTest extends DumpTestCase { // 2. Performing a single page check switch ( $lookingForPage ) { - case 1: - // Page 1 - $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP1" ); - $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1", - $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", - "BackupDumperTestP1Text1" ); - $this->assertPageEnd(); - - $lookingForPage = 2; - break; - - case 2: - // Page 2 - $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN, - "BackupDumperTestP2" ); - $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1", - $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", - "BackupDumperTestP2Text1" ); - $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2", - $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", - "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3", - $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", - "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs ); - $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs, - "BackupDumperTestP2Summary4 extra", - $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", - "BackupDumperTestP2Text4 some additional Text", - $this->revId2_3 + $i * self::$numOfRevs ); - $this->assertPageEnd(); - - $lookingForPage = 4; - break; - - case 4: - // Page 4 - $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK, - "Talk:BackupDumperTestP1" ); - $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs, - "Talk BackupDumperTestP1 Summary1", - $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", - "Talk about BackupDumperTestP1 Text1" ); - $this->assertPageEnd(); - - $lookingForPage = 1; - - // We dealt with the whole iteration. - $i++; - break; - - default: - $this->fail( "Bad setting for lookingForPage ($lookingForPage)" ); + case 1: + // Page 1 + $this->assertPageStart( $this->pageId1 + $i * self::$numOfPages, NS_MAIN, + "BackupDumperTestP1" ); + $this->assertRevision( $this->revId1_1 + $i * self::$numOfRevs, "BackupDumperTestP1Summary1", + $this->textId1_1, false, "0bolhl6ol7i6x0e7yq91gxgaan39j87", + "BackupDumperTestP1Text1" ); + $this->assertPageEnd(); + + $lookingForPage = 2; + break; + + case 2: + // Page 2 + $this->assertPageStart( $this->pageId2 + $i * self::$numOfPages, NS_MAIN, + "BackupDumperTestP2" ); + $this->assertRevision( $this->revId2_1 + $i * self::$numOfRevs, "BackupDumperTestP2Summary1", + $this->textId2_1, false, "jprywrymfhysqllua29tj3sc7z39dl2", + "BackupDumperTestP2Text1" ); + $this->assertRevision( $this->revId2_2 + $i * self::$numOfRevs, "BackupDumperTestP2Summary2", + $this->textId2_2, false, "b7vj5ks32po5m1z1t1br4o7scdwwy95", + "BackupDumperTestP2Text2", $this->revId2_1 + $i * self::$numOfRevs ); + $this->assertRevision( $this->revId2_3 + $i * self::$numOfRevs, "BackupDumperTestP2Summary3", + $this->textId2_3, false, "jfunqmh1ssfb8rs43r19w98k28gg56r", + "BackupDumperTestP2Text3", $this->revId2_2 + $i * self::$numOfRevs ); + $this->assertRevision( $this->revId2_4 + $i * self::$numOfRevs, + "BackupDumperTestP2Summary4 extra", + $this->textId2_4, false, "6o1ciaxa6pybnqprmungwofc4lv00wv", + "BackupDumperTestP2Text4 some additional Text", + $this->revId2_3 + $i * self::$numOfRevs ); + $this->assertPageEnd(); + + $lookingForPage = 4; + break; + + case 4: + // Page 4 + $this->assertPageStart( $this->pageId4 + $i * self::$numOfPages, NS_TALK, + "Talk:BackupDumperTestP1" ); + $this->assertRevision( $this->revId4_1 + $i * self::$numOfRevs, + "Talk BackupDumperTestP1 Summary1", + $this->textId4_1, false, "nktofwzd0tl192k3zfepmlzxoax1lpe", + "Talk about BackupDumperTestP1 Text1" ); + $this->assertPageEnd(); + + $lookingForPage = 1; + + // We dealt with the whole iteration. + $i++; + break; + + default: + $this->fail( "Bad setting for lookingForPage ($lookingForPage)" ); } // 3. Checking for the end of the current checkpoint file if ( $this->xml->nodeType == XMLReader::END_ELEMENT - && $this->xml->name == "mediawiki" ) { - + && $this->xml->name == "mediawiki" + ) { $this->assertDumpEnd(); $fileOpened = false; } @@ -383,7 +388,7 @@ class TextPassDumperTest extends DumpTestCase { $this->assertEmpty( $files, "Remaining unchecked files" ); // ... and have dealt with more than one checkpoint file - $this->assertGreaterThan( 1, $checkpointFiles, "# of checkpoint files" ); + $this->assertGreaterThan( 1, $checkpointFiles, "expected more than 1 checkpoint to have been created. Checkpoint interval is $checkpointAfter seconds, maybe your computer is too fast?" ); $this->expectETAOutput(); } @@ -408,6 +413,7 @@ class TextPassDumperTest extends DumpTestCase { * @group large */ function testCheckpointGzip() { + $this->checkHasGzip(); $this->checkpointHelper( "gzip" ); } @@ -438,7 +444,7 @@ class TextPassDumperTest extends DumpTestCase { <siteinfo> <sitename>wikisvn</sitename> <base>http://localhost/wiki-svn/index.php/Main_Page</base> - <generator>MediaWiki 1.20alpha</generator> + <generator>MediaWiki 1.21alpha</generator> <case>first-letter</case> <namespaces> <namespace key="-2" case="first-letter">Media</namespace> @@ -481,6 +487,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP1Summary1</comment> <sha1>0bolhl6ol7i6x0e7yq91gxgaan39j87</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId1_1 . '" bytes="23" /> </revision> </page> @@ -497,6 +505,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary1</comment> <sha1>jprywrymfhysqllua29tj3sc7z39dl2</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_1 . '" bytes="23" /> </revision> <revision> @@ -508,6 +518,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary2</comment> <sha1>b7vj5ks32po5m1z1t1br4o7scdwwy95</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_2 . '" bytes="23" /> </revision> <revision> @@ -519,6 +531,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary3</comment> <sha1>jfunqmh1ssfb8rs43r19w98k28gg56r</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_3 . '" bytes="23" /> </revision> <revision> @@ -530,6 +544,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>BackupDumperTestP2Summary4 extra</comment> <sha1>6o1ciaxa6pybnqprmungwofc4lv00wv</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId2_4 . '" bytes="44" /> </revision> </page> @@ -548,6 +564,8 @@ class TextPassDumperTest extends DumpTestCase { </contributor> <comment>Talk BackupDumperTestP1 Summary1</comment> <sha1>nktofwzd0tl192k3zfepmlzxoax1lpe</sha1> + <model>wikitext</model> + <format>text/x-wiki</format> <text id="' . $this->textId4_1 . '" bytes="35" /> </revision> </page> @@ -556,8 +574,8 @@ class TextPassDumperTest extends DumpTestCase { } $content .= $tail; $this->assertEquals( strlen( $content ), file_put_contents( - $fname, $content ), "Length of prepared stub" ); + $fname, $content ), "Length of prepared stub" ); + return $fname; } - } diff --git a/tests/phpunit/maintenance/backup_LogTest.php b/tests/phpunit/maintenance/backup_LogTest.php index 8a8dea5a..98d81653 100644 --- a/tests/phpunit/maintenance/backup_LogTest.php +++ b/tests/phpunit/maintenance/backup_LogTest.php @@ -28,18 +28,19 @@ class BackupDumperLoggerTest extends DumpTestCase { * @return int id of the added log entry */ private function addLogEntry( $type, $subtype, User $user, $ns, $title, - $comment = null, $parameters = null ) { - - $logEntry = new ManualLogEntry( $type, $subtype ); + $comment = null, $parameters = null + ) { + $logEntry = new ManualLogEntry( $type, $subtype ); $logEntry->setPerformer( $user ); $logEntry->setTarget( Title::newFromText( $title, $ns ) ); - if ( $comment !== null ) { + if ( $comment !== null ) { $logEntry->setComment( $comment ); } - if ( $parameters !== null ) { + if ( $parameters !== null ) { $logEntry->setParameters( $parameters ); } - return $logEntry->insert(); + + return $logEntry->insert(); } function addDBData() { @@ -73,16 +74,14 @@ class BackupDumperLoggerTest extends DumpTestCase { $this->logId3 = $this->addLogEntry( 'move', 'delete', $user2, NS_MAIN, "PageA", "SomeOtherComment", - array( 'key1' => 1, 3 => 'value3' ) ); + array( 'key1' => 1, 3 => 'value3' ) ); $this->assertGreaterThan( 0, $this->logId3 ); - } catch ( Exception $e ) { // We'd love to pass $e directly. However, ... see // documentation of exceptionFromAddDBData in // DumpTestCase $this->exceptionFromAddDBData = $e; } - } @@ -101,7 +100,8 @@ class BackupDumperLoggerTest extends DumpTestCase { * @param $parameters array: (optional) unserialized data accompanying the log entry */ private function assertLogItem( $id, $user_name, $user_id, $comment, $type, - $subtype, $title, $parameters = array() ) { + $subtype, $title, $parameters = array() + ) { $this->assertNodeStart( "logitem" ); $this->skipWhitespace(); @@ -134,12 +134,12 @@ class BackupDumperLoggerTest extends DumpTestCase { $this->skipWhitespace(); } - function testPlain () { + function testPlain() { global $wgContLang; // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->reporting = false; @@ -172,10 +172,12 @@ class BackupDumperLoggerTest extends DumpTestCase { function testXmlDumpsBackupUseCaseLogging() { global $wgContLang; + $this->checkHasGzip(); + // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fname, - "--reporting=2" ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fname, + "--reporting=2" ) ); $dumper->startId = $this->logId1; $dumper->endId = $this->logId3 + 1; $dumper->setDb( $this->db ); @@ -186,7 +188,7 @@ class BackupDumperLoggerTest extends DumpTestCase { // to be able to alert (once dumping produces reports) that this test // needs updates. $dumper->stderr = fopen( 'php://output', 'a' ); - if ( $dumper->stderr === FALSE ) { + if ( $dumper->stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -223,5 +225,4 @@ class BackupDumperLoggerTest extends DumpTestCase { // the following statement to catch good output $this->expectOutputString( '' ); } - } diff --git a/tests/phpunit/maintenance/backup_PageTest.php b/tests/phpunit/maintenance/backup_PageTest.php index 925e277d..99bd2700 100644 --- a/tests/phpunit/maintenance/backup_PageTest.php +++ b/tests/phpunit/maintenance/backup_PageTest.php @@ -10,26 +10,43 @@ class BackupDumperPageTest extends DumpTestCase { // We'll add several pages, revision and texts. The following variables hold the // corresponding ids. private $pageId1, $pageId2, $pageId3, $pageId4, $pageId5; + private $pageTitle1, $pageTitle2, $pageTitle3, $pageTitle4, $pageTitle5; private $revId1_1, $textId1_1; private $revId2_1, $textId2_1, $revId2_2, $textId2_2; private $revId2_3, $textId2_3, $revId2_4, $textId2_4; private $revId3_1, $textId3_1, $revId3_2, $textId3_2; private $revId4_1, $textId4_1; + private $namespace, $talk_namespace; function addDBData() { + // be sure, titles created here using english namespace names + $this->setMwGlobals( array( + 'wgLanguageCode' => 'en', + 'wgContLang' => Language::factory( 'en' ), + ) ); + $this->tablesUsed[] = 'page'; $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; try { - $title = Title::newFromText( 'BackupDumperTestP1' ); - $page = WikiPage::factory( $title ); + $this->namespace = $this->getDefaultWikitextNS(); + $this->talk_namespace = NS_TALK; + + if ( $this->namespace === $this->talk_namespace ) { + // @todo work around this. + throw new MWException( "The default wikitext namespace is the talk namespace. " + . " We can't currently deal with that." ); + } + + $this->pageTitle1 = Title::newFromText( 'BackupDumperTestP1', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle1 ); list( $this->revId1_1, $this->textId1_1 ) = $this->addRevision( $page, "BackupDumperTestP1Text1", "BackupDumperTestP1Summary1" ); $this->pageId1 = $page->getId(); - $title = Title::newFromText( 'BackupDumperTestP2' ); - $page = WikiPage::factory( $title ); + $this->pageTitle2 = Title::newFromText( 'BackupDumperTestP2', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle2 ); list( $this->revId2_1, $this->textId2_1 ) = $this->addRevision( $page, "BackupDumperTestP2Text1", "BackupDumperTestP2Summary1" ); list( $this->revId2_2, $this->textId2_2 ) = $this->addRevision( $page, @@ -41,8 +58,8 @@ class BackupDumperPageTest extends DumpTestCase { "BackupDumperTestP2Summary4 extra " ); $this->pageId2 = $page->getId(); - $title = Title::newFromText( 'BackupDumperTestP3' ); - $page = WikiPage::factory( $title ); + $this->pageTitle3 = Title::newFromText( 'BackupDumperTestP3', $this->namespace ); + $page = WikiPage::factory( $this->pageTitle3 ); list( $this->revId3_1, $this->textId3_1 ) = $this->addRevision( $page, "BackupDumperTestP3Text1", "BackupDumperTestP2Summary1" ); list( $this->revId3_2, $this->textId3_2 ) = $this->addRevision( $page, @@ -50,8 +67,8 @@ class BackupDumperPageTest extends DumpTestCase { $this->pageId3 = $page->getId(); $page->doDeleteArticle( "Testing ;)" ); - $title = Title::newFromText( 'BackupDumperTestP1', NS_TALK ); - $page = WikiPage::factory( $title ); + $this->pageTitle4 = Title::newFromText( 'BackupDumperTestP1', $this->talk_namespace ); + $page = WikiPage::factory( $this->pageTitle4 ); list( $this->revId4_1, $this->textId4_1 ) = $this->addRevision( $page, "Talk about BackupDumperTestP1 Text1", "Talk BackupDumperTestP1 Summary1" ); @@ -62,10 +79,9 @@ class BackupDumperPageTest extends DumpTestCase { // DumpTestCase $this->exceptionFromAddDBData = $e; } - } - public function setUp() { + protected function setUp() { parent::setUp(); // Since we will restrict dumping by page ranges (to allow @@ -76,13 +92,12 @@ class BackupDumperPageTest extends DumpTestCase { array( $this->pageId2, $this->pageId3, $this->pageId4 ), array( $this->pageId1 + 1, $this->pageId2 + 1, $this->pageId3 + 1 ), "Page ids increasing without holes" ); - } - function testFullTextPlain () { + function testFullTextPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -95,14 +110,14 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87", "BackupDumperTestP1Text1" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2", "BackupDumperTestP2Text1" ); @@ -121,7 +136,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe", "Talk about BackupDumperTestP1 Text1" ); @@ -130,10 +145,10 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testFullStubPlain () { + function testFullStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -146,13 +161,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", @@ -167,7 +182,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -175,10 +190,10 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testCurrentStubPlain () { + function testCurrentStubPlain() { // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=file:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=file:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -191,13 +206,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -206,7 +221,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -214,10 +229,12 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpEnd(); } - function testCurrentStubGzip () { + function testCurrentStubGzip() { + $this->checkHasGzip(); + // Preparing the dump $fname = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fname ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fname ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->reporting = false; @@ -231,13 +248,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fname ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -246,7 +263,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -255,8 +272,7 @@ class BackupDumperPageTest extends DumpTestCase { } - - function testXmlDumpsBackupUseCase () { + function testXmlDumpsBackupUseCase() { // xmldumps-backup typically performs a single dump that that writes // out three files // * gzipped stubs of everything (meta-history) @@ -267,15 +283,17 @@ class BackupDumperPageTest extends DumpTestCase { // We reproduce such a setup with our mini fixture, although we omit // chunks, and all the other gimmicks of xmldumps-backup. // + $this->checkHasGzip(); + $fnameMetaHistory = $this->getNewTempFile(); $fnameMetaCurrent = $this->getNewTempFile(); $fnameArticles = $this->getNewTempFile(); - $dumper = new BackupDumper( array ( "--output=gzip:" . $fnameMetaHistory, - "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", - "--output=gzip:" . $fnameArticles, "--filter=latest", - "--filter=notalk", "--filter=namespace:!NS_USER", - "--reporting=1000" ) ); + $dumper = new BackupDumper( array( "--output=gzip:" . $fnameMetaHistory, + "--output=gzip:" . $fnameMetaCurrent, "--filter=latest", + "--output=gzip:" . $fnameArticles, "--filter=latest", + "--filter=notalk", "--filter=namespace:!NS_USER", + "--reporting=1000" ) ); $dumper->startId = $this->pageId1; $dumper->endId = $this->pageId4 + 1; $dumper->setDb( $this->db ); @@ -285,7 +303,7 @@ class BackupDumperPageTest extends DumpTestCase { // computer. We only check that reporting does not crash the dumping // and that something is reported $dumper->stderr = fopen( 'php://output', 'a' ); - if ( $dumper->stderr === FALSE ) { + if ( $dumper->stderr === false ) { $this->fail( "Could not open stream for stderr" ); } @@ -300,13 +318,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameMetaHistory ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_1, "BackupDumperTestP2Summary1", $this->textId2_1, 23, "jprywrymfhysqllua29tj3sc7z39dl2" ); $this->assertRevision( $this->revId2_2, "BackupDumperTestP2Summary2", @@ -321,7 +339,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -334,13 +352,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameMetaCurrent ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -349,7 +367,7 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - $this->assertPageStart( $this->pageId4, NS_TALK, "Talk:BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId4, $this->talk_namespace, $this->pageTitle4->getPrefixedText() ); $this->assertRevision( $this->revId4_1, "Talk BackupDumperTestP1 Summary1", $this->textId4_1, 35, "nktofwzd0tl192k3zfepmlzxoax1lpe" ); $this->assertPageEnd(); @@ -362,13 +380,13 @@ class BackupDumperPageTest extends DumpTestCase { $this->assertDumpStart( $fnameArticles ); // Page 1 - $this->assertPageStart( $this->pageId1, NS_MAIN, "BackupDumperTestP1" ); + $this->assertPageStart( $this->pageId1, $this->namespace, $this->pageTitle1->getPrefixedText() ); $this->assertRevision( $this->revId1_1, "BackupDumperTestP1Summary1", $this->textId1_1, 23, "0bolhl6ol7i6x0e7yq91gxgaan39j87" ); $this->assertPageEnd(); // Page 2 - $this->assertPageStart( $this->pageId2, NS_MAIN, "BackupDumperTestP2" ); + $this->assertPageStart( $this->pageId2, $this->namespace, $this->pageTitle2->getPrefixedText() ); $this->assertRevision( $this->revId2_4, "BackupDumperTestP2Summary4 extra", $this->textId2_4, 44, "6o1ciaxa6pybnqprmungwofc4lv00wv", false, $this->revId2_3 ); $this->assertPageEnd(); @@ -377,13 +395,10 @@ class BackupDumperPageTest extends DumpTestCase { // -> Page is marked deleted. Hence not visible // Page 4 - // -> Page is not in NS_MAIN. Hence not visible + // -> Page is not in $this->namespace. Hence not visible $this->assertDumpEnd(); $this->expectETAOutput(); } - - - } diff --git a/tests/phpunit/maintenance/fetchTextTest.php b/tests/phpunit/maintenance/fetchTextTest.php index e7ffa01c..e8df199e 100644 --- a/tests/phpunit/maintenance/fetchTextTest.php +++ b/tests/phpunit/maintenance/fetchTextTest.php @@ -18,7 +18,7 @@ class SemiMockedFetchText extends FetchText { /** * @var bool Whether or not a text for stdin has been provided */ - private $mockSetUp = False; + private $mockSetUp = false; /** * @var Array Invocation counters for the mocked aspects @@ -26,16 +26,14 @@ class SemiMockedFetchText extends FetchText { private $mockInvocations = array( 'getStdin' => 0 ); - /** * Data for the fake stdin * * @param $stdin String The string to be used instead of stdin */ - function mockStdin( $stdin ) - { + function mockStdin( $stdin ) { $this->mockStdinText = $stdin; - $this->mockSetUp = True; + $this->mockSetUp = true; } /** @@ -44,30 +42,27 @@ class SemiMockedFetchText extends FetchText { * @return Array An array, whose keys are function names. The corresponding values * denote the number of times the function has been invoked. */ - function mockGetInvocations() - { + function mockGetInvocations() { return $this->mockInvocations; } // ----------------------------------------------------------------- // Mocked functions from FetchText follow. - function getStdin( $len = null ) - { + function getStdin( $len = null ) { $this->mockInvocations['getStdin']++; if ( $len !== null ) { throw new PHPUnit_Framework_ExpectationFailedException( "Tried to get stdin with non null parameter" ); } - if ( ! $this->mockSetUp ) { + if ( !$this->mockSetUp ) { throw new PHPUnit_Framework_ExpectationFailedException( "Tried to get stdin before setting up rerouting" ); } return fopen( 'data://text/plain,' . $this->mockStdinText, 'r' ); } - } /** @@ -111,7 +106,7 @@ class FetchTextTest extends MediaWikiTestCase { * @throws MWExcepion */ private function addRevision( $page, $text, $summary ) { - $status = $page->doEdit( $text, $summary ); + $status = $page->doEditContent( ContentHandler::makeContent( $text, $page->getTitle() ), $summary ); if ( $status->isGood() ) { $value = $status->getValue(); $revision = $value['revision']; @@ -129,12 +124,14 @@ class FetchTextTest extends MediaWikiTestCase { $this->tablesUsed[] = 'revision'; $this->tablesUsed[] = 'text'; + $wikitextNamespace = $this->getDefaultWikitextNS(); + try { - $title = Title::newFromText( 'FetchTextTestPage1' ); + $title = Title::newFromText( 'FetchTextTestPage1', $wikitextNamespace ); $page = WikiPage::factory( $title ); $this->textId1 = $this->addRevision( $page, "FetchTextTestPage1Text1", "FetchTextTestPage1Summary1" ); - $title = Title::newFromText( 'FetchTextTestPage2' ); + $title = Title::newFromText( 'FetchTextTestPage2', $wikitextNamespace ); $page = WikiPage::factory( $title ); $this->textId2 = $this->addRevision( $page, "FetchTextTestPage2Text1", "FetchTextTestPage2Summary1" ); $this->textId3 = $this->addRevision( $page, "FetchTextTestPage2Text2", "FetchTextTestPage2Summary2" ); @@ -173,7 +170,6 @@ class FetchTextTest extends MediaWikiTestCase { } - // Instead of the following functions, a data provider would be great. // However, as data providers are evaluated /before/ addDBData, a data // provider would not know the required ids. @@ -190,14 +186,14 @@ class FetchTextTest extends MediaWikiTestCase { function testExistingSeveral() { $this->assertFilter( "$this->textId1\n$this->textId5\n" - . "$this->textId3\n$this->textId3", + . "$this->textId3\n$this->textId3", implode( "", array( - $this->textId1 . "\n23\nFetchTextTestPage1Text1", - $this->textId5 . "\n44\nFetchTextTestPage2Text4 " + $this->textId1 . "\n23\nFetchTextTestPage1Text1", + $this->textId5 . "\n44\nFetchTextTestPage2Text4 " . "some additional Text", - $this->textId3 . "\n23\nFetchTextTestPage2Text2", - $this->textId3 . "\n23\nFetchTextTestPage2Text2" - ) ) ); + $this->textId3 . "\n23\nFetchTextTestPage2Text2", + $this->textId3 . "\n23\nFetchTextTestPage2Text2" + ) ) ); } function testEmpty() { @@ -229,15 +225,14 @@ class FetchTextTest extends MediaWikiTestCase { function testMix() { $this->assertFilter( "ab\n" . $this->textId4 . ".5cd\n\nefg\n" . $this->textId2 - . "\n" . $this->textId3, + . "\n" . $this->textId3, implode( "", array( - "0\n-1\n", - $this->textId4 . "\n23\nFetchTextTestPage2Text3", - "0\n-1\n", - "0\n-1\n", - $this->textId2 . "\n23\nFetchTextTestPage2Text1", - $this->textId3 . "\n23\nFetchTextTestPage2Text2" - ) ) ); + "0\n-1\n", + $this->textId4 . "\n23\nFetchTextTestPage2Text3", + "0\n-1\n", + "0\n-1\n", + $this->textId2 . "\n23\nFetchTextTestPage2Text1", + $this->textId3 . "\n23\nFetchTextTestPage2Text2" + ) ) ); } - } diff --git a/tests/phpunit/maintenance/getSlaveServerTest.php b/tests/phpunit/maintenance/getSlaveServerTest.php index 0b7c758c..2c848862 100644 --- a/tests/phpunit/maintenance/getSlaveServerTest.php +++ b/tests/phpunit/maintenance/getSlaveServerTest.php @@ -52,11 +52,11 @@ class GetSlaveServerTest extends MediaWikiTestCase { // The main answer $output = $this->getActualOutput(); - $firstLineEndPos = strpos( $output,"\n"); - if ( $firstLineEndPos === FALSE ) { + $firstLineEndPos = strpos( $output, "\n" ); + if ( $firstLineEndPos === false ) { $this->fail( "Could not find end of first line of output" ); } - $firstLine = substr( $output, 0 , $firstLineEndPos ); + $firstLine = substr( $output, 0, $firstLineEndPos ); $this->assertRegExp( "/^" . self::getServerRE() . "$/D", $firstLine, "DB Server" ); @@ -64,6 +64,4 @@ class GetSlaveServerTest extends MediaWikiTestCase { $this->expectOutputRegex( "/^[[:space:]]*\[wgDBprefix\][[:space:]]*=> " . $wgDBprefix . "$/m" ); } - - } diff --git a/tests/phpunit/mocks/filebackend/MockFSFile.php b/tests/phpunit/mocks/filebackend/MockFSFile.php new file mode 100644 index 00000000..e0463281 --- /dev/null +++ b/tests/phpunit/mocks/filebackend/MockFSFile.php @@ -0,0 +1,69 @@ +<?php +/** + * Mock of a filesystem file. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup FileBackend + */ + +/** + * Class representing an in memory fake file. + * This is intended for unit testing / developement when you do not want + * to hit the filesystem. + * + * It reimplements abstract methods with some hardcoded values. Might + * not be suitable for all tests but is good enough for the parser tests. + * + * @ingroup FileBackend + */ +class MockFSFile extends FSFile { + protected $sha1Base36 = null; // File Sha1Base36 + + public function exists() { + return true; + } + + /** + * August 22 – The theft of the Mona Lisa is discovered in the Louvre." + * @bug 20281 + */ + public function getSize() { + return 1911; + } + + public function getTimestamp() { + return wfTimestamp( TS_MW ); + } + + public function getMimeType() { + return 'text/mock'; + } + + public function getProps( $ext = true ) { + return array( + 'fileExists' => $this->exists(), + 'size' => $this->getSize(), + 'file-mime' => $this->getMimeType(), + 'sha1' => $this->getSha1Base36(), + ); + } + + public function getSha1Base36( $recache = false ) { + return '1234567890123456789012345678901'; + } +} diff --git a/tests/phpunit/mocks/filebackend/MockFileBackend.php b/tests/phpunit/mocks/filebackend/MockFileBackend.php new file mode 100644 index 00000000..49aefbd1 --- /dev/null +++ b/tests/phpunit/mocks/filebackend/MockFileBackend.php @@ -0,0 +1,122 @@ +<?php +/** + * Simulation (mock) of a backend storage. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup FileBackend + * @author Antoine Musso <hashar@free.fr> + */ + +/** + * Class simulating a backend store. + * + * @ingroup FileBackend + * @since 1.22 + */ +class MockFileBackend extends FileBackendStore { + + protected $mocked = array(); + + /** Poor man debugging */ + protected function debug( $msg = '' ) { + wfDebug( wfGetCaller() . "$msg\n" ); + } + + public function isPathUsableInternal( $storagePath ) { + return true; + } + + protected function doCreateInternal( array $params ) { + if ( isset( $params['content'] ) ) { + $content = $params['content']; + } else { + $content = 'Default mocked file content'; + } + $this->debug( serialize( $params ) ); + $dst = $params['dst']; + $this->mocked[$dst] = $content; + return Status::newGood(); + } + + protected function doStoreInternal( array $params ) { + $this->debug( serialize( $params ) ); + return $this->doCreateInternal( $params ); + } + + protected function doCopyInternal( array $params ) { + $this->debug( serialize( $params ) ); + $src = $params['src']; + $dst = $params['dst']; + $this->mocked[$dst] = $this->mocked[$src]; + return Status::newGood(); + } + + protected function doDeleteInternal( array $params ) { + $this->debug( serialize( $params ) ); + $src = $params['src']; + unset( $this->mocked[$src] ); + return Status::newGood(); + } + + protected function doGetFileStat( array $params ) { + $src = $params['src']; + if ( array_key_exists( $src, $this->mocked ) ) { + $this->debug( "('$src') found" ); + return array( + 'mtime' => wfTimestamp( TS_MW ), + 'size' => strlen( $this->mocked[$src] ), + # No sha1, stat does not need it. + ); + } else { + $this->debug( "('$src') not found" ); + return false; + } + } + + protected function doGetLocalCopyMulti( array $params ) { + $tmpFiles = array(); // (path => MockFSFile) + + $this->debug( '(' . serialize( $params ) . ')' ); + foreach ( $params['srcs'] as $src ) { + $tmpFiles[$src] = new MockFSFile( + wfTempDir() . '/' . wfRandomString( 32 ) + ); + } + return $tmpFiles; + } + + protected function doDirectoryExists( $container, $dir, array $params ) { + $this->debug(); + return true; + } + + public function getDirectoryListInternal( $container, $dir, array $params ) { + $this->debug(); + return array(); + } + + public function getFileListInternal( $container, $dir, array $params ) { + $this->debug(); + return array(); + } + + protected function directoriesAreVirtual() { + $this->debug(); + return true; + } +} diff --git a/tests/phpunit/mocks/media/MockBitmapHandler.php b/tests/phpunit/mocks/media/MockBitmapHandler.php new file mode 100644 index 00000000..742b41e4 --- /dev/null +++ b/tests/phpunit/mocks/media/MockBitmapHandler.php @@ -0,0 +1,92 @@ +<?php +/** + * Fake handler for images. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Media + */ + +class MockBitmapHandler extends BitmapHandler { + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + return MockImageHandler::doFakeTransform( $this, $image, $dstPath, $dstUrl, $params, $flags ); + } + function doClientImage( $image, $scalerParams ) { + return $this->getClientScalingThumbnailImage( $image, $scalerParams ); + } +} +class MockSvgHandler extends SvgHandler { + function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + return MockImageHandler::doFakeTransform( $this, $image, $dstPath, $dstUrl, $params, $flags ); + } +} +/** + * Mock handler for images. + * + * This is really intended for unit testing. + * + * @ingroup Media + */ +class MockImageHandler { + + /** + * Override BitmapHandler::doTransform() making sure we do not generate + * a thumbnail at all. That is merely returning a ThumbnailImage that + * will be consumed by the unit test. There is no need to create a real + * thumbnail on the filesystem. + */ + static function doFakeTransform( $that, $image, $dstPath, $dstUrl, $params, $flags = 0 ) { + # Example of what we receive: + # $image: LocalFile + # $dstPath: /tmp/transform_7d0a7a2f1a09-1.jpg + # $dstUrl : http://example.com/images/thumb/0/09/Bad.jpg/320px-Bad.jpg + # $params: width: 320, descriptionUrl http://trunk.dev/wiki/File:Bad.jpg + + $that->normaliseParams( $image, $params ); + + $scalerParams = array( + # The size to which the image will be resized + 'physicalWidth' => $params['physicalWidth'], + 'physicalHeight' => $params['physicalHeight'], + 'physicalDimensions' => "{$params['physicalWidth']}x{$params['physicalHeight']}", + # The size of the image on the page + 'clientWidth' => $params['width'], + 'clientHeight' => $params['height'], + # Comment as will be added to the EXIF of the thumbnail + 'comment' => isset( $params['descriptionUrl'] ) ? + "File source: {$params['descriptionUrl']}" : '', + # Properties of the original image + 'srcWidth' => $image->getWidth(), + 'srcHeight' => $image->getHeight(), + 'mimeType' => $image->getMimeType(), + 'dstPath' => $dstPath, + 'dstUrl' => $dstUrl, + ); + + # In some cases, we do not bother generating a thumbnail. + if ( !$image->mustRender() && + $scalerParams['physicalWidth'] == $scalerParams['srcWidth'] + && $scalerParams['physicalHeight'] == $scalerParams['srcHeight'] + ) { + wfDebug( __METHOD__ . ": returning unscaled image\n" ); + // getClientScalingThumbnailImage is protected + return $that->doClientImage( $image, $scalerParams ); + } + + return new ThumbnailImage( $image, $dstUrl, false, $params ); + } +} diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php index bcbf4ec1..1d65e521 100644 --- a/tests/phpunit/phpunit.php +++ b/tests/phpunit/phpunit.php @@ -8,23 +8,20 @@ /* Configuration */ -// Evaluate the include path relative to this file -$IP = dirname( dirname( __DIR__ ) ); - // Set a flag which can be used to detect when other scripts have been entered through this entry point or not define( 'MW_PHPUNIT_TEST', true ); // Start up MediaWiki in command-line mode -require_once( "$IP/maintenance/Maintenance.php" ); +require_once dirname( dirname( __DIR__ ) ) . "/maintenance/Maintenance.php"; class PHPUnitMaintClass extends Maintenance { function __construct() { parent::__construct(); - $this->addOption( 'with-phpunitdir' - , 'Directory to include PHPUnit from, for example when using a git fetchout from upstream. Path will be prepended to PHP `include_path`.' - , false # not required - , true # need arg + $this->addOption( 'with-phpunitdir', + 'Directory to include PHPUnit from, for example when using a git fetchout from upstream. Path will be prepended to PHP `include_path`.', + false, # not required + true # need arg ); } @@ -36,6 +33,9 @@ class PHPUnitMaintClass extends Maintenance { global $wgLocaltimezone, $wgLocalisationCacheConf; global $wgDevelopmentWarnings; + // Inject test autoloader + require_once __DIR__ . '/../TestsAutoLoader.php'; + // wfWarn should cause tests to fail $wgDevelopmentWarnings = true; @@ -49,26 +49,35 @@ class PHPUnitMaintClass extends Maintenance { // Assume UTC for testing purposes $wgLocaltimezone = 'UTC'; - $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + $wgLocalisationCacheConf['storeClass'] = 'LCStore_Null'; + + // Bug 44192 Do not attempt to send a real e-mail + Hooks::clear( 'AlternateUserMailer' ); + Hooks::register( + 'AlternateUserMailer', + function () { + return false; + } + ); } public function execute() { global $IP; # Make sure we have --configuration or PHPUnit might complain - if( !in_array( '--configuration', $_SERVER['argv'] ) ) { + if ( !in_array( '--configuration', $_SERVER['argv'] ) ) { //Hack to eliminate the need to use the Makefile (which sucks ATM) array_splice( $_SERVER['argv'], 1, 0, array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) ); } # --with-phpunitdir let us override the default PHPUnit version - if( $phpunitDir = $this->getOption( 'with-phpunitdir' ) ) { + if ( $phpunitDir = $this->getOption( 'with-phpunitdir' ) ) { # Sanity checks - if( !is_dir($phpunitDir) ) { + if ( !is_dir( $phpunitDir ) ) { $this->error( "--with-phpunitdir should be set to an existing directory", 1 ); } - if( !is_readable( $phpunitDir."/PHPUnit/Runner/Version.php" ) ) { + if ( !is_readable( $phpunitDir . "/PHPUnit/Runner/Version.php" ) ) { $this->error( "No usable PHPUnit installation in $phpunitDir.\nAborting.\n", 1 ); } @@ -80,10 +89,9 @@ class PHPUnitMaintClass extends Maintenance { # Cleanup $args array so the option and its value do not # pollute PHPUnit $key = array_search( '--with-phpunitdir', $_SERVER['argv'] ); - unset( $_SERVER['argv'][$key] ); // the option - unset( $_SERVER['argv'][$key+1] ); // its value + unset( $_SERVER['argv'][$key] ); // the option + unset( $_SERVER['argv'][$key + 1] ); // its value $_SERVER['argv'] = array_values( $_SERVER['argv'] ); - } } @@ -93,15 +101,19 @@ class PHPUnitMaintClass extends Maintenance { } $maintClass = 'PHPUnitMaintClass'; -require( RUN_MAINTENANCE_IF_MAIN ); +require RUN_MAINTENANCE_IF_MAIN; -require_once( 'PHPUnit/Runner/Version.php' ); +if ( !class_exists( 'PHPUnit_Runner_Version' ) ) { + require_once 'PHPUnit/Runner/Version.php'; +} -if( PHPUnit_Runner_Version::id() !== '@package_version@' - && version_compare( PHPUnit_Runner_Version::id(), '3.6.7', '<' ) ) { +if ( PHPUnit_Runner_Version::id() !== '@package_version@' + && version_compare( PHPUnit_Runner_Version::id(), '3.6.7', '<' ) +) { die( 'PHPUnit 3.6.7 or later required, you have ' . PHPUnit_Runner_Version::id() . ".\n" ); } -require_once( 'PHPUnit/Autoload.php' ); -require_once( "$IP/tests/TestsAutoLoader.php" ); +if ( !class_exists( 'PHPUnit_TextUI_Command' ) ) { + require_once 'PHPUnit/Autoload.php'; +} MediaWikiPHPUnitCommand::main(); diff --git a/tests/phpunit/skins/SideBarTest.php b/tests/phpunit/skins/SideBarTest.php index 912d7602..a385320f 100644 --- a/tests/phpunit/skins/SideBarTest.php +++ b/tests/phpunit/skins/SideBarTest.php @@ -5,15 +5,14 @@ */ class SideBarTest extends MediaWikiLangTestCase { - /** A skin template, reinitialized before each test */ + /** + * A skin template, reinitialized before each test + * @var SkinTemplate + */ private $skin; /** Local cache for sidebar messages */ private $messages; - function __construct() { - parent::__construct(); - } - /** Build $this->messages array */ private function initMessagesHref() { # List of default messages for the sidebar: @@ -26,29 +25,26 @@ class SideBarTest extends MediaWikiLangTestCase { 'helppage', ); - foreach( $URL_messages as $m ) { - $titleName = MessageCache::singleton()->get($m); + foreach ( $URL_messages as $m ) { + $titleName = MessageCache::singleton()->get( $m ); $title = Title::newFromText( $titleName ); $this->messages[$m]['href'] = $title->getLocalURL(); } } - function setUp() { + protected function setUp() { parent::setUp(); $this->initMessagesHref(); $this->skin = new SkinTemplate(); $this->skin->getContext()->setLanguage( Language::factory( 'en' ) ); } - function tearDown() { - parent::tearDown(); - $this->skin = null; - } /** * Internal helper to test the sidebar * @param $expected * @param $text * @param $message (Default: '') + * @todo this assert method to should be converted to a test using a dataprovider.. */ private function assertSideBar( $expected, $text, $message = '' ) { $bar = array(); @@ -56,93 +52,101 @@ class SideBarTest extends MediaWikiLangTestCase { $this->assertEquals( $expected, $bar, $message ); } - function testSidebarWithOnlyTwoTitles() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testSidebarWithOnlyTwoTitles() { $this->assertSideBar( - array( - 'Title1' => array(), - 'Title2' => array(), - ), -'* Title1 + array( + 'Title1' => array(), + 'Title2' => array(), + ), + '* Title1 * Title2 ' ); } - function testExpandMessages() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testExpandMessages() { $this->assertSidebar( - array( 'Title' => array( - array( - 'text' => 'Help', - 'href' => $this->messages['helppage']['href'], - 'id' => 'n-help', - 'active' => null - ) - )), -'* Title + array( 'Title' => array( + array( + 'text' => 'Help', + 'href' => $this->messages['helppage']['href'], + 'id' => 'n-help', + 'active' => null + ) + ) ), + '* Title ** helppage|help ' ); } - function testExternalUrlsRequireADescription() { + /** + * @covers SkinTemplate::addToSidebarPlain + */ + public function testExternalUrlsRequireADescription() { $this->assertSidebar( - array( 'Title' => array( - # ** http://www.mediawiki.org/| Home - array( - 'text' => 'Home', - 'href' => 'http://www.mediawiki.org/', - 'id' => 'n-Home', - 'active' => null, - 'rel' => 'nofollow', - ), - # ** http://valid.no.desc.org/ - # ... skipped since it is missing a pipe with a description - )), -'* Title + array( 'Title' => array( + # ** http://www.mediawiki.org/| Home + array( + 'text' => 'Home', + 'href' => 'http://www.mediawiki.org/', + 'id' => 'n-Home', + 'active' => null, + 'rel' => 'nofollow', + ), + # ** http://valid.no.desc.org/ + # ... skipped since it is missing a pipe with a description + ) ), + '* Title ** http://www.mediawiki.org/| Home ** http://valid.no.desc.org/ ' - ); - } + /** * bug 33321 - Make sure there's a | after transforming. * @group Database + * @covers SkinTemplate::addToSidebarPlain */ - function testTrickyPipe() { + public function testTrickyPipe() { $this->assertSidebar( - array( 'Title' => array( - # The first 2 are skipped - # Doesn't really test the url properly - # because it will vary with $wgArticlePath et al. - # ** Baz|Fred - array( - 'text' => 'Fred', - 'href' => Title::newFromText( 'Baz' )->getLocalUrl(), - 'id' => 'n-Fred', - 'active' => null, - ), - array( - 'text' => 'title-to-display', - 'href' => Title::newFromText( 'page-to-go-to' )->getLocalUrl(), - 'id' => 'n-title-to-display', - 'active' => null, - ), - )), -'* Title + array( 'Title' => array( + # The first 2 are skipped + # Doesn't really test the url properly + # because it will vary with $wgArticlePath et al. + # ** Baz|Fred + array( + 'text' => 'Fred', + 'href' => Title::newFromText( 'Baz' )->getLocalURL(), + 'id' => 'n-Fred', + 'active' => null, + ), + array( + 'text' => 'title-to-display', + 'href' => Title::newFromText( 'page-to-go-to' )->getLocalURL(), + 'id' => 'n-title-to-display', + 'active' => null, + ), + ) ), + '* Title ** {{PAGENAME|Foo}} ** Bar ** Baz|Fred ** {{PLURAL:1|page-to-go-to{{int:pipe-separator/en}}title-to-display|branch not taken}} ' ); - } #### Attributes for external links ########################## - private function getAttribs( ) { + private function getAttribs() { # Sidebar text we will use everytime $text = '* Title ** http://www.mediawiki.org/| Home'; @@ -155,11 +159,12 @@ class SideBarTest extends MediaWikiLangTestCase { /** * Simple test to verify our helper assertAttribs() is functional - * Please note this assume MediaWiki default settings: - * $wgNoFollowLinks = true - * $wgExternalLinkTarget = false */ - function testTestAttributesAssertionHelper() { + public function testTestAttributesAssertionHelper() { + $this->setMwGlobals( array( + 'wgNoFollowLinks' => true, + 'wgExternalLinkTarget' => false, + ) ); $attribs = $this->getAttribs(); $this->assertArrayHasKey( 'rel', $attribs ); @@ -171,39 +176,31 @@ class SideBarTest extends MediaWikiLangTestCase { /** * Test $wgNoFollowLinks in sidebar */ - function testRespectWgnofollowlinks() { - global $wgNoFollowLinks; - $saved = $wgNoFollowLinks; - $wgNoFollowLinks = false; + public function testRespectWgnofollowlinks() { + $this->setMwGlobals( 'wgNoFollowLinks', false ); $attribs = $this->getAttribs(); $this->assertArrayNotHasKey( 'rel', $attribs, 'External URL in sidebar do not have rel=nofollow when $wgNoFollowLinks = false' ); - - // Restore global - $wgNoFollowLinks = $saved; } /** * Test $wgExternaLinkTarget in sidebar + * @dataProvider dataRespectExternallinktarget */ - function testRespectExternallinktarget() { - global $wgExternalLinkTarget; - $saved = $wgExternalLinkTarget; - - $wgExternalLinkTarget = '_blank'; - $attribs = $this->getAttribs(); - $this->assertArrayHasKey( 'target', $attribs ); - $this->assertEquals( $attribs['target'], '_blank' ); + public function testRespectExternallinktarget( $externalLinkTarget ) { + $this->setMwGlobals( 'wgExternalLinkTarget', $externalLinkTarget ); - $wgExternalLinkTarget = '_self'; $attribs = $this->getAttribs(); $this->assertArrayHasKey( 'target', $attribs ); - $this->assertEquals( $attribs['target'], '_self' ); - - // Restore global - $wgExternalLinkTarget = $saved; + $this->assertEquals( $attribs['target'], $externalLinkTarget ); } + public static function dataRespectExternallinktarget() { + return array( + array( '_blank' ), + array( '_self' ), + ); + } } diff --git a/tests/phpunit/structure/AutoLoaderTest.php b/tests/phpunit/structure/AutoLoaderTest.php new file mode 100644 index 00000000..205ea360 --- /dev/null +++ b/tests/phpunit/structure/AutoLoaderTest.php @@ -0,0 +1,56 @@ +<?php +class AutoLoaderTest extends MediaWikiTestCase { + + /** + * Assert that there were no classes loaded that are not registered with the AutoLoader. + * + * For example foo.php having class Foo and class Bar but only registering Foo. + * This is important because we should not be relying on Foo being used before Bar. + */ + public function testAutoLoadConfig() { + $results = self::checkAutoLoadConf(); + + $this->assertEquals( + $results['expected'], + $results['actual'] + ); + } + + protected static function checkAutoLoadConf() { + global $wgAutoloadLocalClasses, $wgAutoloadClasses, $IP; + $supportsParsekit = function_exists( 'parsekit_compile_file' ); + + // wgAutoloadLocalClasses has precedence, just like in includes/AutoLoader.php + $expected = $wgAutoloadLocalClasses + $wgAutoloadClasses; + $actual = array(); + + $files = array_unique( $expected ); + + foreach ( $files as $file ) { + // Only prefix $IP if it doesn't have it already. + // Generally local classes don't have it, and those from extensions and test suites do. + if ( substr( $file, 0, 1 ) != '/' && substr( $file, 1, 1 ) != ':' ) { + $filePath = "$IP/$file"; + } else { + $filePath = $file; + } + if ( $supportsParsekit ) { + $parseInfo = parsekit_compile_file( "$filePath" ); + $classes = array_keys( $parseInfo['class_table'] ); + } else { + $contents = file_get_contents( "$filePath" ); + $m = array(); + preg_match_all( '/\n\s*(?:final)?\s*(?:abstract)?\s*(?:class|interface)\s+([a-zA-Z0-9_]+)/', $contents, $m, PREG_PATTERN_ORDER ); + $classes = $m[1]; + } + foreach ( $classes as $class ) { + $actual[$class] = $file; + } + } + + return array( + 'expected' => $expected, + 'actual' => $actual, + ); + } +} diff --git a/tests/phpunit/structure/ResourcesTest.php b/tests/phpunit/structure/ResourcesTest.php new file mode 100644 index 00000000..fe823fa4 --- /dev/null +++ b/tests/phpunit/structure/ResourcesTest.php @@ -0,0 +1,131 @@ +<?php +/** + * Sanity checks for making sure registered resources are sane. + * + * @file + * @author Antoine Musso + * @author Niklas Laxström + * @author Santhosh Thottingal + * @author Timo Tijhof + * @copyright © 2012, Antoine Musso + * @copyright © 2012, Niklas Laxström + * @copyright © 2012, Santhosh Thottingal + * @copyright © 2012, Timo Tijhof + * + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + */ +class ResourcesTest extends MediaWikiTestCase { + + /** + * @dataProvider provideResourceFiles + */ + public function testFileExistence( $filename, $module, $resource ) { + $this->assertFileExists( $filename, + "File '$resource' referenced by '$module' must exist." + ); + } + + /** + * This ask the ResouceLoader for all registered files from modules + * created by ResourceLoaderFileModule (or one of its descendants). + * + * + * Since the raw data is stored in protected properties, we have to + * overrride this through ReflectionObject methods. + */ + public static function provideResourceFiles() { + global $wgEnableJavaScriptTest; + + // Test existance of test suite files as well + // (can't use setUp or setMwGlobals because providers are static) + $live_wgEnableJavaScriptTest = $wgEnableJavaScriptTest; + $wgEnableJavaScriptTest = true; + + // Array with arguments for the test function + $cases = array(); + + // Initialize ResourceLoader + $rl = new ResourceLoader(); + + // See also ResourceLoaderFileModule::__construct + $filePathProps = array( + // Lists of file paths + 'lists' => array( + 'scripts', + 'debugScripts', + 'loaderScripts', + 'styles', + ), + + // Collated lists of file paths + 'nested-lists' => array( + 'languageScripts', + 'skinScripts', + 'skinStyles', + ), + ); + + foreach ( $rl->getModuleNames() as $moduleName ) { + $module = $rl->getModule( $moduleName ); + if ( !$module instanceof ResourceLoaderFileModule ) { + continue; + } + + $reflectedModule = new ReflectionObject( $module ); + + $files = array(); + + foreach ( $filePathProps['lists'] as $propName ) { + $property = $reflectedModule->getProperty( $propName ); + $property->setAccessible( true ); + $list = $property->getValue( $module ); + foreach ( $list as $key => $value ) { + // 'scripts' are numeral arrays. + // 'styles' can be numeral or associative. + // In case of associative the key is the file path + // and the value is the 'media' attribute. + if ( is_int( $key ) ) { + $files[] = $value; + } else { + $files[] = $key; + } + } + } + + foreach ( $filePathProps['nested-lists'] as $propName ) { + $property = $reflectedModule->getProperty( $propName ); + $property->setAccessible( true ); + $lists = $property->getValue( $module ); + foreach ( $lists as $list ) { + foreach ( $list as $key => $value ) { + // We need the same filter as for 'lists', + // due to 'skinStyles'. + if ( is_int( $key ) ) { + $files[] = $value; + } else { + $files[] = $key; + } + } + } + } + + // Get method for resolving the paths to full paths + $method = $reflectedModule->getMethod( 'getLocalPath' ); + $method->setAccessible( true ); + + // Populate cases + foreach ( $files as $file ) { + $cases[] = array( + $method->invoke( $module, $file ), + $module->getName(), + $file, + ); + } + } + + // Restore settings + $wgEnableJavaScriptTest = $live_wgEnableJavaScriptTest; + + return $cases; + } +} diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/structure/StructureTest.php index 17ea06c4..df00d4df 100644 --- a/tests/phpunit/StructureTest.php +++ b/tests/phpunit/structure/StructureTest.php @@ -8,15 +8,18 @@ class StructureTest extends MediaWikiTestCase { /** * Verify all files that appear to be tests have file names ending in * Test. If the file names do not end in Test, they will not be run. + * @group medium */ public function testUnitTestFileNamesEndWithTest() { if ( wfIsWindows() ) { $this->markTestSkipped( 'This test does not work on Windows' ); } - $rootPath = escapeshellarg( __DIR__ ); + $rootPath = escapeshellarg( __DIR__ . '/..' ); $testClassRegex = implode( '|', array( 'ApiFormatTestBase', 'ApiTestCase', + 'ApiQueryTestBase', + 'ApiQueryContinueTestBase', 'MediaWikiLangTestCase', 'MediaWikiTestCase', 'PHPUnit_Framework_TestCase', @@ -28,7 +31,7 @@ class StructureTest extends MediaWikiTestCase { $results = null; $exitCode = null; - exec($finder, $results, $exitCode); + exec( $finder, $results, $exitCode ); $this->assertEquals( 0, @@ -41,7 +44,7 @@ class StructureTest extends MediaWikiTestCase { array( $this, 'filterSuites' ) ); $strip = strlen( $rootPath ) - 1; - foreach( $results as $k => $v) { + foreach ( $results as $k => $v ) { $results[$k] = substr( $v, $strip ); } $this->assertEquals( @@ -55,6 +58,6 @@ class StructureTest extends MediaWikiTestCase { * Filter to remove testUnitTestFileNamesEndWithTest false positives. */ public function filterSuites( $filename ) { - return strpos( $filename, __DIR__ . '/suites/' ) !== 0; + return strpos( $filename, __DIR__ . '/../suites/' ) !== 0; } } diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml index 56f64477..7a9122fa 100644 --- a/tests/phpunit/suite.xml +++ b/tests/phpunit/suite.xml @@ -2,17 +2,18 @@ <!-- colors don't work on Windows! --> <phpunit bootstrap="./bootstrap.php" - colors="true" - backupGlobals="false" - convertErrorsToExceptions="true" - convertNoticesToExceptions="true" - convertWarningsToExceptions="true" - stopOnFailure="false" - timeoutForSmallTests="10" - timeoutForMediumTests="30" - timeoutForLargeTests="60" - strict="true" - verbose="true"> + colors="true" + backupGlobals="false" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + forceCoversAnnotation="true" + stopOnFailure="false" + timeoutForSmallTests="10" + timeoutForMediumTests="30" + timeoutForLargeTests="60" + strict="true" + verbose="true"> <testsuites> <testsuite name="includes"> <directory>includes</directory> @@ -29,13 +30,14 @@ <directory>maintenance</directory> </testsuite> <testsuite name="structure"> - <file>StructureTest.php</file> + <directory>structure</directory> </testsuite> <testsuite name="uploadfromurl"> <file>suites/UploadFromUrlTestSuite.php</file> </testsuite> <testsuite name="extensions"> <file>suites/ExtensionsTestSuite.php</file> + <file>suites/ExtensionsParserTestSuite.php</file> </testsuite> </testsuites> <groups> diff --git a/tests/phpunit/suites/ExtensionsParserTestSuite.php b/tests/phpunit/suites/ExtensionsParserTestSuite.php new file mode 100644 index 00000000..3d68b241 --- /dev/null +++ b/tests/phpunit/suites/ExtensionsParserTestSuite.php @@ -0,0 +1,8 @@ +<?php +class ExtensionsParserTestSuite extends PHPUnit_Framework_TestSuite { + + public static function suite() { + return MediaWikiParserTest::suite( MediaWikiParserTest::NO_CORE ); + } + +} diff --git a/tests/phpunit/suites/ExtensionsTestSuite.php b/tests/phpunit/suites/ExtensionsTestSuite.php index d728807f..eec773db 100644 --- a/tests/phpunit/suites/ExtensionsTestSuite.php +++ b/tests/phpunit/suites/ExtensionsTestSuite.php @@ -1,4 +1,4 @@ -<?php +<?php /** * This test suite runs unit tests registered by extensions. * See http://www.mediawiki.org/wiki/Manual:Hooks/UnitTestsList for details of how to register your tests. diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php index f2638111..7eb599e3 100644 --- a/tests/phpunit/suites/UploadFromUrlTestSuite.php +++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php @@ -1,6 +1,6 @@ <?php -require_once( dirname( __DIR__ ) . '/includes/upload/UploadFromUrlTest.php' ); +require_once dirname( __DIR__ ) . '/includes/upload/UploadFromUrlTest.php'; class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { public $savedGlobals = array(); @@ -15,32 +15,32 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { return true; } - function setUp() { - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, - $wgNamespaceAliases, $wgNamespaceProtection, $parserMemc; + protected function setUp() { + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgUser, + $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, + $wgEnableParserCache, $wgNamespaceAliases, $wgNamespaceProtection, + $parserMemc; $tmpGlobals = array(); $tmpGlobals['wgScript'] = '/index.php'; $tmpGlobals['wgScriptPath'] = '/'; $tmpGlobals['wgArticlePath'] = '/wiki/$1'; - $tmpGlobals['wgStyleSheetPath'] = '/skins'; $tmpGlobals['wgStylePath'] = '/skins'; $tmpGlobals['wgThumbnailScriptPath'] = false; $tmpGlobals['wgLocalFileRepo'] = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, + 'class' => 'LocalRepo', + 'name' => 'local', + 'url' => 'http://example.com/images', + 'hashLevels' => 2, 'transformVia404' => false, - 'backend' => new FSFileBackend( array( - 'name' => 'local-backend', + 'backend' => new FSFileBackend( array( + 'name' => 'local-backend', 'lockManager' => 'fsLockManager', 'containerPaths' => array( - 'local-public' => wfTempDir() . '/test-repo/public', - 'local-thumb' => wfTempDir() . '/test-repo/thumb', - 'local-temp' => wfTempDir() . '/test-repo/temp', + 'local-public' => wfTempDir() . '/test-repo/public', + 'local-thumb' => wfTempDir() . '/test-repo/thumb', + 'local-temp' => wfTempDir() . '/test-repo/temp', 'local-deleted' => wfTempDir() . '/test-repo/delete', ) ) ), @@ -56,7 +56,6 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgNamespaceAliases['Image'] = NS_FILE; $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - $wgEnableParserCache = false; DeferredUpdates::clearPendingUpdates(); $wgMemc = wfGetMainCache(); @@ -72,14 +71,14 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { $wgRequest = $context->getRequest(); if ( $wgStyleDirectory === false ) { - $wgStyleDirectory = "$IP/skins"; + $wgStyleDirectory = "$IP/skins"; } RepoGroup::destroySingleton(); FileBackendGroup::destroySingleton(); } - public function tearDown() { + protected function tearDown() { foreach ( $this->savedGlobals as $var => $val ) { $GLOBALS[$var] = $val; } @@ -88,6 +87,8 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { FileBackendGroup::destroySingleton(); $this->teardownUploadDir( $this->uploadDir ); + + parent::tearDown(); } private $uploadDir; @@ -103,7 +104,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { // delete the files first, then the dirs. self::deleteFiles( - array ( + array( "$dir/3/3a/Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", @@ -115,7 +116,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { ); self::deleteDirs( - array ( + array( "$dir/3/3a", "$dir/3", "$dir/thumb/6/65", @@ -182,6 +183,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { if ( file_exists( $dir ) ) { wfDebug( "Already exists!\n" ); + return $dir; } @@ -199,6 +201,7 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite { // the UploadFromUrlTest class class_exists( 'UploadFromUrlTest' ); $suite = new UploadFromUrlTestSuite( 'UploadFromUrlTest' ); + return $suite; } } |