From d9022f63880ce039446fba8364f68e656b7bf4cb Mon Sep 17 00:00:00 2001
From: Pierre Schmitz <pierre@archlinux.de>
Date: Thu, 3 May 2012 13:01:35 +0200
Subject: Update to MediaWiki 1.19.0

---
 tests/phpunit/Makefile                             |   17 +-
 tests/phpunit/MediaWikiLangTestCase.php            |    6 +-
 tests/phpunit/MediaWikiPHPUnitCommand.php          |   30 +
 tests/phpunit/MediaWikiTestCase.php                |  113 +-
 tests/phpunit/StructureTest.php                    |   56 +
 tests/phpunit/data/db/mysql/functions.sql          |   12 +
 tests/phpunit/data/db/postgres/functions.sql       |   12 +
 tests/phpunit/data/db/sqlite/tables-1.13.sql       |  342 +++++
 tests/phpunit/data/db/sqlite/tables-1.15.sql       |  454 +++++++
 tests/phpunit/data/db/sqlite/tables-1.16.sql       |  483 +++++++
 tests/phpunit/data/db/sqlite/tables-1.17.sql       |  516 ++++++++
 tests/phpunit/data/db/sqlite/tables-1.18.sql       |  535 ++++++++
 tests/phpunit/data/media/80x60-2layers.xcf         |  Bin 0 -> 1162 bytes
 tests/phpunit/data/media/80x60-Greyscale.xcf       |  Bin 0 -> 667 bytes
 tests/phpunit/data/media/80x60-RGB.xcf             |  Bin 0 -> 677 bytes
 tests/phpunit/data/media/Toll_Texas_1.svg          |  150 +++
 tests/phpunit/data/media/iptc-invalid-psir.jpg     |  Bin 0 -> 9574 bytes
 tests/phpunit/includes/ArticleTablesTest.php       |   31 +-
 tests/phpunit/includes/ArticleTest.php             |   14 +-
 tests/phpunit/includes/BlockTest.php               |   17 +-
 tests/phpunit/includes/EditPageTest.php            |   33 +
 tests/phpunit/includes/ExtraParserTest.php         |   64 +-
 .../includes/FormOptionsInitializationTest.php     |    4 +-
 tests/phpunit/includes/FormOptionsTest.php         |    4 +-
 .../includes/GlobalFunctions/GlobalTest.php        |  524 ++------
 .../includes/GlobalFunctions/GlobalWithDBTest.php  |   29 +
 .../includes/GlobalFunctions/wfAssembleUrlTest.php |  111 ++
 .../includes/GlobalFunctions/wfBCP47Test.php       |  133 ++
 .../includes/GlobalFunctions/wfBaseNameTest.php    |   36 +
 .../includes/GlobalFunctions/wfExpandUrl.php       |   78 --
 .../includes/GlobalFunctions/wfExpandUrlTest.php   |   80 ++
 .../GlobalFunctions/wfRemoveDotSegmentsTest.php    |   90 ++
 .../GlobalFunctions/wfShorthandToIntegerTest.php   |   28 +
 .../includes/GlobalFunctions/wfTimestampTest.php   |  134 ++
 tests/phpunit/includes/HtmlTest.php                |  277 +++-
 tests/phpunit/includes/HttpTest.php                |  556 +-------
 tests/phpunit/includes/IPTest.php                  |   32 +-
 tests/phpunit/includes/ImageFunctionsTest.php      |   48 -
 tests/phpunit/includes/LocalFileTest.php           |   33 +-
 tests/phpunit/includes/MWNamespaceTest.php         |  329 +++--
 tests/phpunit/includes/MessageTest.php             |    2 +-
 tests/phpunit/includes/ParserOptionsTest.php       |    9 +-
 tests/phpunit/includes/PathRouterTest.php          |  254 ++++
 tests/phpunit/includes/Providers.php               |    4 +-
 tests/phpunit/includes/ResourceLoaderTest.php      |    2 +-
 tests/phpunit/includes/SanitizerTest.php           |   43 +
 .../includes/SanitizerValidateEmailTest.php        |   79 ++
 .../phpunit/includes/SeleniumConfigurationTest.php |   24 +-
 tests/phpunit/includes/TemplateCategoriesTest.php  |   38 +
 tests/phpunit/includes/TitleMethodsTest.php        |   78 ++
 tests/phpunit/includes/TitlePermissionTest.php     |   34 +-
 tests/phpunit/includes/TitleTest.php               |    4 +-
 .../phpunit/includes/UserIsValidEmailAddrTest.php  |   79 --
 tests/phpunit/includes/UserTest.php                |  105 +-
 tests/phpunit/includes/WebRequestTest.php          |   97 ++
 tests/phpunit/includes/XmlSelectTest.php           |    4 +-
 tests/phpunit/includes/XmlTest.php                 |   99 +-
 tests/phpunit/includes/api/ApiBlockTest.php        |   28 +-
 tests/phpunit/includes/api/ApiPurgeTest.php        |   34 +-
 tests/phpunit/includes/api/ApiQueryTest.php        |    8 +-
 tests/phpunit/includes/api/ApiTest.php             |   36 +-
 tests/phpunit/includes/api/ApiTestCase.php         |   51 +-
 tests/phpunit/includes/api/ApiTestCaseUpload.php   |   40 +-
 tests/phpunit/includes/api/ApiTestUser.php         |    2 +-
 tests/phpunit/includes/api/ApiUploadTest.php       |  197 ++-
 tests/phpunit/includes/api/ApiWatchTest.php        |    5 +-
 .../phpunit/includes/api/RandomImageGenerator.php  |  124 +-
 tests/phpunit/includes/db/DatabaseSqliteTest.php   |   14 +-
 tests/phpunit/includes/db/DatabaseTest.php         |   32 +-
 tests/phpunit/includes/db/sqlite/tables-1.13.sql   |  342 -----
 tests/phpunit/includes/db/sqlite/tables-1.15.sql   |  454 -------
 tests/phpunit/includes/db/sqlite/tables-1.16.sql   |  483 -------
 tests/phpunit/includes/db/sqlite/tables-1.17.sql   |  516 --------
 tests/phpunit/includes/debug/MWDebugTest.php       |   63 +
 .../phpunit/includes/filerepo/FileBackendTest.php  | 1358 ++++++++++++++++++++
 tests/phpunit/includes/filerepo/FileRepoTest.php   |   41 +
 tests/phpunit/includes/filerepo/StoreBatchTest.php |  122 ++
 tests/phpunit/includes/json/ServicesJsonTest.php   |   93 ++
 .../includes/libs/JavaScriptMinifierTest.php       |   43 +
 .../includes/media/BitmapMetadataHandlerTest.php   |   26 +-
 tests/phpunit/includes/media/BitmapScalingTest.php |   25 +-
 tests/phpunit/includes/media/ExifBitmapTest.php    |   24 +-
 tests/phpunit/includes/media/ExifRotationTest.php  |   59 +-
 tests/phpunit/includes/media/ExifTest.php          |    9 +-
 .../phpunit/includes/media/FormatMetadataTest.php  |   39 +-
 .../includes/media/GIFMetadataExtractorTest.php    |    1 +
 tests/phpunit/includes/media/GIFTest.php           |   31 +-
 .../includes/media/JpegMetadataExtractorTest.php   |   10 +-
 tests/phpunit/includes/media/JpegTest.php          |   18 +-
 tests/phpunit/includes/media/MediaHandlerTest.php  |   50 +
 tests/phpunit/includes/media/PNGTest.php           |   30 +-
 .../includes/media/SVGMetadataExtractorTest.php    |   24 +-
 tests/phpunit/includes/media/TiffTest.php          |    7 +-
 tests/phpunit/includes/media/XMPTest.php           |    9 +-
 tests/phpunit/includes/media/XMPValidateTest.php   |   47 +
 .../phpunit/includes/parser/MagicVariableTest.php  |   10 +-
 .../includes/parser/MediaWikiParserTest.php        |   12 +-
 tests/phpunit/includes/parser/NewParserTest.php    |  414 +++---
 tests/phpunit/includes/parser/ParserHelpers.php    |  136 --
 .../phpunit/includes/parser/ParserPreloadTest.php  |   67 +
 tests/phpunit/includes/parser/PreprocessorTest.php |  102 +-
 tests/phpunit/includes/parser/TagHooks.php         |   77 --
 tests/phpunit/includes/parser/TagHooksTest.php     |   77 ++
 tests/phpunit/includes/search/SearchEngineTest.php |   16 +-
 .../includes/specials/QueryAllSpecialPagesTest.php |   79 ++
 .../includes/specials/SpecialRecentchanges.php     |  134 --
 .../includes/specials/SpecialRecentchangesTest.php |  132 ++
 .../includes/specials/SpecialSearchTest.php        |  108 ++
 .../phpunit/includes/upload/UploadFromUrlTest.php  |   19 +-
 tests/phpunit/includes/upload/UploadStashTest.php  |   53 +-
 tests/phpunit/includes/upload/UploadTest.php       |   87 +-
 tests/phpunit/languages/LanguageAmTest.php         |   33 +
 tests/phpunit/languages/LanguageArTest.php         |   78 ++
 tests/phpunit/languages/LanguageBeTest.php         |   40 +
 tests/phpunit/languages/LanguageBe_taraskTest.php  |   35 +
 tests/phpunit/languages/LanguageBhTest.php         |   34 +
 tests/phpunit/languages/LanguageBsTest.php         |   41 +
 tests/phpunit/languages/LanguageCsTest.php         |   40 +
 tests/phpunit/languages/LanguageCuTest.php         |   41 +
 tests/phpunit/languages/LanguageCyTest.php         |   42 +
 tests/phpunit/languages/LanguageDsbTest.php        |   40 +
 tests/phpunit/languages/LanguageFrTest.php         |   34 +
 tests/phpunit/languages/LanguageGaTest.php         |   34 +
 tests/phpunit/languages/LanguageGdTest.php         |   38 +
 tests/phpunit/languages/LanguageGvTest.php         |   39 +
 tests/phpunit/languages/LanguageHeTest.php         |   48 +
 tests/phpunit/languages/LanguageHiTest.php         |   34 +
 tests/phpunit/languages/LanguageHrTest.php         |   41 +
 tests/phpunit/languages/LanguageHsbTest.php        |   40 +
 tests/phpunit/languages/LanguageHyTest.php         |   34 +
 tests/phpunit/languages/LanguageKshTest.php        |   34 +
 tests/phpunit/languages/LanguageLnTest.php         |   34 +
 tests/phpunit/languages/LanguageLtTest.php         |   53 +
 tests/phpunit/languages/LanguageLvTest.php         |   39 +
 tests/phpunit/languages/LanguageMgTest.php         |   35 +
 tests/phpunit/languages/LanguageMkTest.php         |   41 +
 tests/phpunit/languages/LanguageMlTest.php         |   43 +
 tests/phpunit/languages/LanguageMoTest.php         |   43 +
 tests/phpunit/languages/LanguageMtTest.php         |   72 ++
 tests/phpunit/languages/LanguageNlTest.php         |   28 +
 tests/phpunit/languages/LanguageNsoTest.php        |   32 +
 tests/phpunit/languages/LanguagePlTest.php         |   72 ++
 tests/phpunit/languages/LanguageRoTest.php         |   43 +
 tests/phpunit/languages/LanguageRuTest.php         |   54 +
 tests/phpunit/languages/LanguageSeTest.php         |   48 +
 tests/phpunit/languages/LanguageSgsTest.php        |   66 +
 tests/phpunit/languages/LanguageShTest.php         |   32 +
 tests/phpunit/languages/LanguageSkTest.php         |   40 +
 tests/phpunit/languages/LanguageSlTest.php         |   42 +
 tests/phpunit/languages/LanguageSmaTest.php        |   48 +
 tests/phpunit/languages/LanguageSrTest.php         |  199 +++
 tests/phpunit/languages/LanguageTest.php           |  591 +++++++--
 tests/phpunit/languages/LanguageTiTest.php         |   32 +
 tests/phpunit/languages/LanguageTlTest.php         |   32 +
 tests/phpunit/languages/LanguageTrTest.php         |    9 +-
 tests/phpunit/languages/LanguageUkTest.php         |   54 +
 tests/phpunit/languages/LanguageWaTest.php         |   32 +
 tests/phpunit/phpunit.php                          |    4 +-
 tests/phpunit/skins/SideBarTest.php                |    9 +-
 tests/phpunit/suite.xml                            |   19 +-
 tests/phpunit/suites/UploadFromUrlTestSuite.php    |   74 +-
 161 files changed, 10866 insertions(+), 4238 deletions(-)
 create mode 100644 tests/phpunit/StructureTest.php
 create mode 100644 tests/phpunit/data/db/mysql/functions.sql
 create mode 100644 tests/phpunit/data/db/postgres/functions.sql
 create mode 100644 tests/phpunit/data/db/sqlite/tables-1.13.sql
 create mode 100644 tests/phpunit/data/db/sqlite/tables-1.15.sql
 create mode 100644 tests/phpunit/data/db/sqlite/tables-1.16.sql
 create mode 100644 tests/phpunit/data/db/sqlite/tables-1.17.sql
 create mode 100644 tests/phpunit/data/db/sqlite/tables-1.18.sql
 create mode 100644 tests/phpunit/data/media/80x60-2layers.xcf
 create mode 100644 tests/phpunit/data/media/80x60-Greyscale.xcf
 create mode 100644 tests/phpunit/data/media/80x60-RGB.xcf
 create mode 100644 tests/phpunit/data/media/Toll_Texas_1.svg
 create mode 100644 tests/phpunit/data/media/iptc-invalid-psir.jpg
 create mode 100644 tests/phpunit/includes/EditPageTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
 delete mode 100644 tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
 create mode 100644 tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
 delete mode 100644 tests/phpunit/includes/ImageFunctionsTest.php
 create mode 100644 tests/phpunit/includes/PathRouterTest.php
 create mode 100644 tests/phpunit/includes/SanitizerValidateEmailTest.php
 create mode 100644 tests/phpunit/includes/TemplateCategoriesTest.php
 create mode 100644 tests/phpunit/includes/TitleMethodsTest.php
 delete mode 100644 tests/phpunit/includes/UserIsValidEmailAddrTest.php
 delete mode 100644 tests/phpunit/includes/db/sqlite/tables-1.13.sql
 delete mode 100644 tests/phpunit/includes/db/sqlite/tables-1.15.sql
 delete mode 100644 tests/phpunit/includes/db/sqlite/tables-1.16.sql
 delete mode 100644 tests/phpunit/includes/db/sqlite/tables-1.17.sql
 create mode 100644 tests/phpunit/includes/debug/MWDebugTest.php
 create mode 100644 tests/phpunit/includes/filerepo/FileBackendTest.php
 create mode 100644 tests/phpunit/includes/filerepo/FileRepoTest.php
 create mode 100644 tests/phpunit/includes/filerepo/StoreBatchTest.php
 create mode 100644 tests/phpunit/includes/json/ServicesJsonTest.php
 create mode 100644 tests/phpunit/includes/media/MediaHandlerTest.php
 create mode 100644 tests/phpunit/includes/media/XMPValidateTest.php
 delete mode 100644 tests/phpunit/includes/parser/ParserHelpers.php
 create mode 100644 tests/phpunit/includes/parser/ParserPreloadTest.php
 delete mode 100644 tests/phpunit/includes/parser/TagHooks.php
 create mode 100644 tests/phpunit/includes/parser/TagHooksTest.php
 create mode 100644 tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
 delete mode 100644 tests/phpunit/includes/specials/SpecialRecentchanges.php
 create mode 100644 tests/phpunit/includes/specials/SpecialRecentchangesTest.php
 create mode 100644 tests/phpunit/includes/specials/SpecialSearchTest.php
 create mode 100644 tests/phpunit/languages/LanguageAmTest.php
 create mode 100644 tests/phpunit/languages/LanguageArTest.php
 create mode 100644 tests/phpunit/languages/LanguageBeTest.php
 create mode 100644 tests/phpunit/languages/LanguageBhTest.php
 create mode 100644 tests/phpunit/languages/LanguageBsTest.php
 create mode 100644 tests/phpunit/languages/LanguageCsTest.php
 create mode 100644 tests/phpunit/languages/LanguageCuTest.php
 create mode 100644 tests/phpunit/languages/LanguageCyTest.php
 create mode 100644 tests/phpunit/languages/LanguageDsbTest.php
 create mode 100644 tests/phpunit/languages/LanguageFrTest.php
 create mode 100644 tests/phpunit/languages/LanguageGaTest.php
 create mode 100644 tests/phpunit/languages/LanguageGdTest.php
 create mode 100644 tests/phpunit/languages/LanguageGvTest.php
 create mode 100644 tests/phpunit/languages/LanguageHeTest.php
 create mode 100644 tests/phpunit/languages/LanguageHiTest.php
 create mode 100644 tests/phpunit/languages/LanguageHrTest.php
 create mode 100644 tests/phpunit/languages/LanguageHsbTest.php
 create mode 100644 tests/phpunit/languages/LanguageHyTest.php
 create mode 100644 tests/phpunit/languages/LanguageKshTest.php
 create mode 100644 tests/phpunit/languages/LanguageLnTest.php
 create mode 100644 tests/phpunit/languages/LanguageLtTest.php
 create mode 100644 tests/phpunit/languages/LanguageLvTest.php
 create mode 100644 tests/phpunit/languages/LanguageMgTest.php
 create mode 100644 tests/phpunit/languages/LanguageMkTest.php
 create mode 100644 tests/phpunit/languages/LanguageMlTest.php
 create mode 100644 tests/phpunit/languages/LanguageMoTest.php
 create mode 100644 tests/phpunit/languages/LanguageMtTest.php
 create mode 100644 tests/phpunit/languages/LanguageNlTest.php
 create mode 100644 tests/phpunit/languages/LanguageNsoTest.php
 create mode 100644 tests/phpunit/languages/LanguagePlTest.php
 create mode 100644 tests/phpunit/languages/LanguageRoTest.php
 create mode 100644 tests/phpunit/languages/LanguageRuTest.php
 create mode 100644 tests/phpunit/languages/LanguageSeTest.php
 create mode 100644 tests/phpunit/languages/LanguageSgsTest.php
 create mode 100644 tests/phpunit/languages/LanguageShTest.php
 create mode 100644 tests/phpunit/languages/LanguageSkTest.php
 create mode 100644 tests/phpunit/languages/LanguageSlTest.php
 create mode 100644 tests/phpunit/languages/LanguageSmaTest.php
 create mode 100644 tests/phpunit/languages/LanguageSrTest.php
 create mode 100644 tests/phpunit/languages/LanguageTiTest.php
 create mode 100644 tests/phpunit/languages/LanguageTlTest.php
 create mode 100644 tests/phpunit/languages/LanguageUkTest.php
 create mode 100644 tests/phpunit/languages/LanguageWaTest.php

(limited to 'tests/phpunit')

diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile
index 24536efc..8a55dae0 100644
--- a/tests/phpunit/Makefile
+++ b/tests/phpunit/Makefile
@@ -46,17 +46,26 @@ coverage:
 
 parser:
 	${PU} --group Parser
+parserfuzz:
+	@echo "******************************************************************"
+	@echo "* This WILL kill your computer by eating all memory AND all swap *"
+	@echo "*                                                                *"
+	@echo "* If you are on a production machine. ABORT NOW!!                *"
+	@echo "*  Press control+C to stop                                       *"
+	@echo "*                                                                *"
+	@echo "******************************************************************"
+	${PU} --group Parser,ParserFuzz
 noparser:
-	${PU} --exclude-group Parser,Broken,Stub
+	${PU} --exclude-group Parser,Broken,ParserFuzz,Stub
 
 safe:
-	${PU} --exclude-group Broken,Destructive,Stub
+	${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
 
 databaseless:
-	${PU} --exclude-group Broken,Destructive,Database,Stub
+	${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
 
 database:
-	${PU} --exclude-group Broken,Destructive,Stub --group Database
+	${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
 
 list-groups:
 	${PU} --list-groups
diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php
index 1cd6a3ba..783f0315 100644
--- a/tests/phpunit/MediaWikiLangTestCase.php
+++ b/tests/phpunit/MediaWikiLangTestCase.php
@@ -13,7 +13,11 @@ abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
 		self::$oldLang = $wgLang;
 		self::$oldContLang = $wgContLang;
 
-		if( $wgLanguageCode != $wgContLang->getCode() ) die("nooo!");
+		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'
 
diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php
index c0d9f363..ea385ad9 100644
--- a/tests/phpunit/MediaWikiPHPUnitCommand.php
+++ b/tests/phpunit/MediaWikiPHPUnitCommand.php
@@ -5,7 +5,10 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
 	static $additionalOptions = array(
 		'regex=' => false,
 		'file=' => false,
+		'use-filebackend=' => false,
 		'keep-uploads' => false,
+		'use-normal-tables' => false,
+		'reuse-db' => false,
 	);
 
 	public function __construct() {
@@ -17,6 +20,28 @@ class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
 
 	public static function main( $exit = true ) {
 		$command = new self;
+
+		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.
+			# The below code inject a parameter just like if the user called
+			# phpunit with a --no-color option (which does not exist). It
+			# overrides the suite.xml setting.
+			# Probably fix bug 29226
+			$command->arguments['colors'] = false;
+		}
+
+		# Makes MediaWiki PHPUnit directory includable so the PHPUnit will
+		# be able to resolve relative files inclusion such as suites/*
+		# PHPUnit uses stream_resolve_include_path() internally
+		# See bug 32022
+		set_include_path(
+			dirname( __FILE__ )
+			.PATH_SEPARATOR
+			. get_include_path()
+		);
+
 		$command->run($_SERVER['argv'], $exit);
 	}
 
@@ -40,6 +65,11 @@ ParserTest-specific options:
   --keep-uploads           Re-use the same upload directory for each test, don't delete it
 
 
+Database options:
+  --use-normal-tables      Use normal DB tables.
+  --reuse-db               Init DB only if tables are missing and keep after finish.
+
+
 EOT;
 	}
 
diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php
index 64cb486b..6ec8bdc7 100644
--- a/tests/phpunit/MediaWikiTestCase.php
+++ b/tests/phpunit/MediaWikiTestCase.php
@@ -11,6 +11,9 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 	protected $db;
 	protected $oldTablePrefix;
 	protected $useTemporaryTables = true;
+	protected $reuseDB = false;
+	protected $tablesUsed = array(); // tables with data
+
 	private static $dbSetup = false;
 
 	/**
@@ -22,6 +25,7 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 	protected $supportedDBs = array(
 		'mysql',
 		'sqlite',
+		'postgres',
 		'oracle'
 	);
 
@@ -40,8 +44,10 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 		ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
 
 		if( $this->needsDB() ) {
-
 			global $wgDBprefix;
+			
+			$this->useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
+			$this->reuseDB = $this->getCliArg('reuse-db');
 
 			$this->db = wfGetDB( DB_MASTER );
 
@@ -81,6 +87,34 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 	function addDBData() {}
 
 	private function addCoreDBData() {
+		# disabled for performance
+		#$this->tablesUsed[] = 'page';
+		#$this->tablesUsed[] = 'revision';
+
+		if ( $this->db->getType() == 'oracle' ) {
+
+			# Insert 0 user to prevent FK violations
+			# Anonymous user
+			$this->db->insert( 'user', array(
+				'user_id' 		=> 0,
+				'user_name'   	=> 'Anonymous' ), __METHOD__, array( 'IGNORE' ) );
+
+			# Insert 0 page to prevent FK violations
+			# Blank page
+			$this->db->insert( 'page', array(
+				'page_id' => 0,
+				'page_namespace' => 0,
+				'page_title' => ' ',
+				'page_restrictions' => NULL,
+				'page_counter' => 0,
+				'page_is_redirect' => 0,
+				'page_is_new' => 0,
+				'page_random' => 0,
+				'page_touched' => $this->db->timestamp(),
+				'page_latest' => 0,
+				'page_len' => 0 ), __METHOD__, array( 'IGNORE' ) );
+
+		}
 
 		User::resetIdByNameCache();
 
@@ -98,12 +132,14 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 
 
 		//Make 1 page with 1 revision
-		$article = new Article( Title::newFromText( 'UTPage' ) );
-		$article->doEdit( 'UTContent',
+		$page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+		if ( !$page->getId() == 0 ) {
+			$page->doEdit( 'UTContent',
 							'UTPageSummary',
 							EDIT_NEW,
 							false,
 							User::newFromName( 'UTSysop' ) );
+		}
 	}
 
 	private function initDB() {
@@ -112,18 +148,20 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 			throw new MWException( 'Cannot run unit tests, the database prefix is already "unittest_"' );
 		}
 
-		$dbClone = new CloneDatabase( $this->db, $this->listTables(), $this->dbPrefix() );
+		$tablesCloned = $this->listTables();
+		$dbClone = new CloneDatabase( $this->db, $tablesCloned, $this->dbPrefix() );
 		$dbClone->useTemporaryTables( $this->useTemporaryTables );
-		$dbClone->cloneTableStructure();
+
+		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;' );
-
-			# Insert 0 user to prevent FK violations
-			# Anonymous user
-			$this->db->insert( 'user', array(
-				'user_id' 		=> 0,
-				'user_name'   	=> 'Anonymous' ) );
 		}
 	}
 
@@ -132,35 +170,25 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 	 */
 	private function resetDB() {
 		if( $this->db ) {
-			foreach( $this->listTables() as $tbl ) {
-				if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
-				$this->db->delete( $tbl, '*', __METHOD__ );
+			if ( $this->db->getType() == 'oracle' )  {
+				if ( $this->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__ );
+					}
+				}
+			} else {
+				foreach( $this->tablesUsed as $tbl ) {
+					if( $tbl == 'interwiki' || $tbl == 'user' ) continue;
+					$this->db->delete( $tbl, '*', __METHOD__ );
+				}
 			}
 		}
 	}
 
-	protected function destroyDB() {
-		if ( $this->useTemporaryTables || is_null( $this->db ) ) {
-			# Don't need to do anything
-			return;
-		}
-
-		$tables = $this->db->listTables( $this->dbPrefix(), __METHOD__ );
-
-		foreach ( $tables as $table ) {
-			try {
-				$sql = $this->db->getType() == 'oracle' ? "DROP TABLE $table CASCADE CONSTRAINTS PURGE" : "DROP TABLE `$table`";
-				$this->db->query( $sql, __METHOD__ );
-			} catch( MWException $mwe ) {}
-		}
-
-		if ( $this->db->getType() == 'oracle' )
-			$this->db->query( 'BEGIN FILL_WIKI_INFO; END;', __METHOD__ );
-
-		CloneDatabase::changePrefix( $this->oldTablePrefix );
-	}
-
-
 	function __call( $func, $args ) {
 		static $compatibility = array(
 			'assertInternalType' => 'assertType',
@@ -235,5 +263,16 @@ abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
 	public static function disableInterwikis( $prefix, &$data ) {
 		return false;
 	}
-}
 
+	/**
+	 * Don't throw a warning if $function is deprecated and called later
+	 *
+	 * @param $function String
+	 * @return null
+	 */
+	function hideDeprecated( $function ) {
+		wfSuppressWarnings();
+		wfDeprecated( $function );
+		wfRestoreWarnings();
+	}
+}
diff --git a/tests/phpunit/StructureTest.php b/tests/phpunit/StructureTest.php
new file mode 100644
index 00000000..f967c18d
--- /dev/null
+++ b/tests/phpunit/StructureTest.php
@@ -0,0 +1,56 @@
+<?php
+/**
+ * The tests here verify the structure of the code.  This is for outright bugs,
+ * not just style issues.
+ */
+
+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.
+	 */
+	public function testUnitTestFileNamesEndWithTest() {
+		if ( wfIsWindows() ) {
+			$this->markTestSkipped( 'This test does not work on Windows' );
+		}
+		$rootPath = escapeshellarg( __DIR__ );
+		$testClassRegex = implode( '|', array(
+			'ApiFormatTestBase',
+			'ApiTestCase',
+			'MediaWikiLangTestCase',
+			'MediaWikiTestCase',
+			'PHPUnit_Framework_TestCase',
+		) );
+		$testClassRegex = "^class .* extends ($testClassRegex)";
+		$finder = "find $rootPath -name '*.php' '!' -name '*Test.php'" .
+			" | xargs grep -El '$testClassRegex|function suite\('";
+
+		$results = null;
+		$exitCode = null;
+		exec($finder, $results, $exitCode);
+
+		$this->assertEquals(
+			0,
+			$exitCode,
+			'Verify find/grep command succeeds.'
+		);
+
+		$results = array_filter(
+			$results,
+			array( $this, 'filterSuites' )
+		);
+
+		$this->assertEquals(
+			array(),
+			$results,
+			'Unit test file names must end with Test.'
+		);
+	}
+
+	/**
+	 * Filter to remove testUnitTestFileNamesEndWithTest false positives.
+	 */
+	public function filterSuites( $filename ) {
+		return strpos( $filename, __DIR__ . '/suites/' ) !== 0;
+	}
+}
diff --git a/tests/phpunit/data/db/mysql/functions.sql b/tests/phpunit/data/db/mysql/functions.sql
new file mode 100644
index 00000000..9e5e470f
--- /dev/null
+++ b/tests/phpunit/data/db/mysql/functions.sql
@@ -0,0 +1,12 @@
+-- MySQL test file for DatabaseTest::testStoredFunctions()
+
+DELIMITER //
+
+CREATE FUNCTION mw_test_function()
+RETURNS int DETERMINISTIC
+BEGIN
+	SET @foo = 21;
+	RETURN @foo * 2;
+END//
+
+DELIMITER //
diff --git a/tests/phpunit/data/db/postgres/functions.sql b/tests/phpunit/data/db/postgres/functions.sql
new file mode 100644
index 00000000..3086d4d5
--- /dev/null
+++ b/tests/phpunit/data/db/postgres/functions.sql
@@ -0,0 +1,12 @@
+-- Postgres test file for DatabaseTest::testStoredFunctions()
+
+CREATE FUNCTION mw_test_function()
+RETURNS INTEGER
+LANGUAGE plpgsql AS
+$mw$
+DECLARE foo INTEGER;
+BEGIN
+	foo := 21;
+	RETURN foo * 2;
+END
+$mw$;
diff --git a/tests/phpunit/data/db/sqlite/tables-1.13.sql b/tests/phpunit/data/db/sqlite/tables-1.13.sql
new file mode 100644
index 00000000..66847ab1
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.13.sql
@@ -0,0 +1,342 @@
+-- This is a copy of SQLite schema from MediaWiki 1.13 used for updater testing
+
+CREATE TABLE /*$wgDBprefix*/user (
+  user_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  user_name varchar(255)   default '',
+  user_real_name varchar(255)   default '',
+  user_password tinyblob ,
+  user_newpassword tinyblob ,
+  user_newpass_time BLOB,
+  user_email tinytext ,
+  user_options blob ,
+  user_touched BLOB  default '',
+  user_token BLOB  default '',
+  user_email_authenticated BLOB,
+  user_email_token BLOB,
+  user_email_token_expires BLOB,
+  user_registration BLOB,
+  user_editcount int) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/user_groups (
+  ug_user INTEGER  default '0',
+  ug_group varBLOB  default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/user_newtalk (
+  user_id INTEGER  default '0',
+  user_ip varBLOB  default '',
+  user_last_timestamp BLOB  default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/page (
+  page_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  page_namespace INTEGER ,
+  page_title varchar(255)  ,
+  page_restrictions tinyblob ,
+  page_counter bigint  default '0',
+  page_is_redirect tinyint  default '0',
+  page_is_new tinyint  default '0',
+  page_random real ,
+  page_touched BLOB  default '',
+  page_latest INTEGER ,
+  page_len INTEGER ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/revision (
+  rev_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  rev_page INTEGER ,
+  rev_text_id INTEGER ,
+  rev_comment tinyblob ,
+  rev_user INTEGER  default '0',
+  rev_user_text varchar(255)   default '',
+  rev_timestamp BLOB  default '',
+  rev_minor_edit tinyint  default '0',
+  rev_deleted tinyint  default '0',
+  rev_len int,
+  rev_parent_id INTEGER default NULL) /*$wgDBTableOptions*/  ;
+
+CREATE TABLE /*$wgDBprefix*/text (
+  old_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  old_text mediumblob ,
+  old_flags tinyblob ) /*$wgDBTableOptions*/  ;
+
+CREATE TABLE /*$wgDBprefix*/archive (
+  ar_namespace INTEGER  default '0',
+  ar_title varchar(255)   default '',
+  ar_text mediumblob ,
+  ar_comment tinyblob ,
+  ar_user INTEGER  default '0',
+  ar_user_text varchar(255)  ,
+  ar_timestamp BLOB  default '',
+  ar_minor_edit tinyint  default '0',
+  ar_flags tinyblob ,
+  ar_rev_id int,
+  ar_text_id int,
+  ar_deleted tinyint  default '0',
+  ar_len int,
+  ar_page_id int,
+  ar_parent_id INTEGER default NULL) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/pagelinks (
+  pl_from INTEGER  default '0',
+  pl_namespace INTEGER  default '0',
+  pl_title varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/templatelinks (
+  tl_from INTEGER  default '0',
+  tl_namespace INTEGER  default '0',
+  tl_title varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/imagelinks (
+  il_from INTEGER  default '0',
+  il_to varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/categorylinks (
+  cl_from INTEGER  default '0',
+  cl_to varchar(255)   default '',
+  cl_sortkey varchar(70)   default '',
+  cl_timestamp timestamp ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/category (
+  cat_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  cat_title varchar(255)  ,
+  cat_pages INTEGER signed  default 0,
+  cat_subcats INTEGER signed  default 0,
+  cat_files INTEGER signed  default 0,
+  cat_hidden tinyint  default 0) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/externallinks (
+  el_from INTEGER  default '0',
+  el_to blob ,
+  el_index blob ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/langlinks (
+  ll_from INTEGER  default '0',
+  ll_lang varBLOB  default '',
+  ll_title varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/site_stats (
+  ss_row_id INTEGER ,
+  ss_total_views bigint default '0',
+  ss_total_edits bigint default '0',
+  ss_good_articles bigint default '0',
+  ss_total_pages bigint default '-1',
+  ss_users bigint default '-1',
+  ss_admins INTEGER default '-1',
+  ss_images INTEGER default '0') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/hitcounter (
+  hc_id INTEGER
+)  ;
+
+CREATE TABLE /*$wgDBprefix*/ipblocks (
+  ipb_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  ipb_address tinyblob ,
+  ipb_user INTEGER  default '0',
+  ipb_by INTEGER  default '0',
+  ipb_by_text varchar(255)   default '',
+  ipb_reason tinyblob ,
+  ipb_timestamp BLOB  default '',
+  ipb_auto bool  default 0,
+  ipb_anon_only bool  default 0,
+  ipb_create_account bool  default 1,
+  ipb_enable_autoblock bool  default '1',
+  ipb_expiry varBLOB  default '',
+  ipb_range_start tinyblob ,
+  ipb_range_end tinyblob ,
+  ipb_deleted bool  default 0,
+  ipb_block_email bool  default 0) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/image (
+  img_name varchar(255)   default '',
+  img_size INTEGER  default '0',
+  img_width INTEGER  default '0',
+  img_height INTEGER  default '0',
+  img_metadata mediumblob ,
+  img_bits INTEGER  default '0',
+  img_media_type TEXT default NULL,
+  img_major_mime TEXT  default "unknown",
+  img_minor_mime varBLOB  default "unknown",
+  img_description tinyblob ,
+  img_user INTEGER  default '0',
+  img_user_text varchar(255)  ,
+  img_timestamp varBLOB  default '',
+  img_sha1 varBLOB  default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/oldimage (
+  oi_name varchar(255)   default '',
+  oi_archive_name varchar(255)   default '',
+  oi_size INTEGER  default 0,
+  oi_width INTEGER  default 0,
+  oi_height INTEGER  default 0,
+  oi_bits INTEGER  default 0,
+  oi_description tinyblob ,
+  oi_user INTEGER  default '0',
+  oi_user_text varchar(255)  ,
+  oi_timestamp BLOB  default '',
+  oi_metadata mediumblob ,
+  oi_media_type TEXT default NULL,
+  oi_major_mime TEXT  default "unknown",
+  oi_minor_mime varBLOB  default "unknown",
+  oi_deleted tinyint  default '0',
+  oi_sha1 varBLOB  default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/filearchive (
+  fa_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  fa_name varchar(255)   default '',
+  fa_archive_name varchar(255)  default '',
+  fa_storage_group varBLOB,
+  fa_storage_key varBLOB default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp BLOB default '',
+  fa_deleted_reason text,
+  fa_size INTEGER default '0',
+  fa_width INTEGER default '0',
+  fa_height INTEGER default '0',
+  fa_metadata mediumblob,
+  fa_bits INTEGER default '0',
+  fa_media_type TEXT default NULL,
+  fa_major_mime TEXT default "unknown",
+  fa_minor_mime varBLOB default "unknown",
+  fa_description tinyblob,
+  fa_user INTEGER default '0',
+  fa_user_text varchar(255) ,
+  fa_timestamp BLOB default '',
+  fa_deleted tinyint  default '0') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/recentchanges (
+  rc_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  rc_timestamp varBLOB  default '',
+  rc_cur_time varBLOB  default '',
+  rc_user INTEGER  default '0',
+  rc_user_text varchar(255)  ,
+  rc_namespace INTEGER  default '0',
+  rc_title varchar(255)   default '',
+  rc_comment varchar(255)   default '',
+  rc_minor tinyint  default '0',
+  rc_bot tinyint  default '0',
+  rc_new tinyint  default '0',
+  rc_cur_id INTEGER  default '0',
+  rc_this_oldid INTEGER  default '0',
+  rc_last_oldid INTEGER  default '0',
+  rc_type tinyint  default '0',
+  rc_moved_to_ns tinyint  default '0',
+  rc_moved_to_title varchar(255)   default '',
+  rc_patrolled tinyint  default '0',
+  rc_ip varBLOB  default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint  default '0',
+  rc_logid INTEGER  default '0',
+  rc_log_type varBLOB NULL default NULL,
+  rc_log_action varBLOB NULL default NULL,
+  rc_params blob NULL) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/watchlist (
+  wl_user INTEGER ,
+  wl_namespace INTEGER  default '0',
+  wl_title varchar(255)   default '',
+  wl_notificationtimestamp varBLOB) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/math (
+  math_inputhash varBLOB ,
+  math_outputhash varBLOB ,
+  math_html_conservativeness tinyint ,
+  math_html text,
+  math_mathml text) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/searchindex (
+  si_page INTEGER ,
+  si_title varchar(255)  default '',
+  si_text mediumtext ) ;
+
+CREATE TABLE /*$wgDBprefix*/interwiki (
+  iw_prefix varchar(32) ,
+  iw_url blob ,
+  iw_local bool ,
+  iw_trans tinyint  default 0) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/querycache (
+  qc_type varBLOB ,
+  qc_value INTEGER  default '0',
+  qc_namespace INTEGER  default '0',
+  qc_title varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/objectcache (
+  keyname varBLOB  default '',
+  value mediumblob,
+  exptime datetime) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/transcache (
+  tc_url varBLOB ,
+  tc_contents text,
+  tc_time INTEGER ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/logging (
+  log_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  log_type varBLOB  default '',
+  log_action varBLOB  default '',
+  log_timestamp BLOB  default '19700101000000',
+  log_user INTEGER  default 0,
+  log_namespace INTEGER  default 0,
+  log_title varchar(255)   default '',
+  log_comment varchar(255)  default '',
+  log_params blob ,
+  log_deleted tinyint  default '0') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/trackbacks (
+  tb_id INTEGER PRIMARY KEY AUTOINCREMENT,
+  tb_page INTEGER REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE,
+  tb_title varchar(255) ,
+  tb_url blob ,
+  tb_ex text,
+  tb_name varchar(255)) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/job (
+  job_id INTEGER  PRIMARY KEY AUTOINCREMENT,
+  job_cmd varBLOB  default '',
+  job_namespace INTEGER ,
+  job_title varchar(255)  ,
+  job_params blob ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/querycache_info (
+  qci_type varBLOB  default '',
+  qci_timestamp BLOB  default '19700101000000') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/redirect (
+  rd_from INTEGER  default '0',
+  rd_namespace INTEGER  default '0',
+  rd_title varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/querycachetwo (
+  qcc_type varBLOB ,
+  qcc_value INTEGER  default '0',
+  qcc_namespace INTEGER  default '0',
+  qcc_title varchar(255)   default '',
+  qcc_namespacetwo INTEGER  default '0',
+  qcc_titletwo varchar(255)   default '') /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/page_restrictions (
+  pr_page INTEGER ,
+  pr_type varBLOB ,
+  pr_level varBLOB ,
+  pr_cascade tinyint ,
+  pr_user INTEGER NULL,
+  pr_expiry varBLOB NULL,
+  pr_id INTEGER  PRIMARY KEY AUTOINCREMENT) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/protected_titles (
+  pt_namespace INTEGER ,
+  pt_title varchar(255)  ,
+  pt_user INTEGER ,
+  pt_reason tinyblob,
+  pt_timestamp BLOB ,
+  pt_expiry varBLOB  default '',
+  pt_create_perm varBLOB ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/page_props (
+  pp_page INTEGER ,
+  pp_propname varBLOB ,
+  pp_value blob ) /*$wgDBTableOptions*/;
+
+CREATE TABLE /*$wgDBprefix*/updatelog (
+  ul_key varchar(255) ) /*$wgDBTableOptions*/;
+
+
diff --git a/tests/phpunit/data/db/sqlite/tables-1.15.sql b/tests/phpunit/data/db/sqlite/tables-1.15.sql
new file mode 100644
index 00000000..6b3a628e
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.15.sql
@@ -0,0 +1,454 @@
+-- This is a copy of MediaWiki 1.15 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  user_name varchar(255) binary NOT NULL default '',
+  user_real_name varchar(255) binary NOT NULL default '',
+  user_password tinyblob NOT NULL,
+  user_newpassword tinyblob NOT NULL,
+  user_newpass_time binary(14),
+  user_email tinytext NOT NULL,
+  user_options blob NOT NULL,
+  user_touched binary(14) NOT NULL default '',
+  user_token binary(32) NOT NULL default '',
+  user_email_authenticated binary(14),
+  user_email_token binary(32),
+  user_email_token_expires binary(14),
+  user_registration binary(14),
+  user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE TABLE /*_*/user_groups (
+  ug_user int unsigned NOT NULL default 0,
+  ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_newtalk (
+  user_id int NOT NULL default 0,
+  user_ip varbinary(40) NOT NULL default '',
+  user_last_timestamp binary(14) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/page (
+  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  page_namespace int NOT NULL,
+  page_title varchar(255) binary NOT NULL,
+  page_restrictions tinyblob NOT NULL,
+  page_counter bigint unsigned NOT NULL default 0,
+  page_is_redirect tinyint unsigned NOT NULL default 0,
+  page_is_new tinyint unsigned NOT NULL default 0,
+  page_random real unsigned NOT NULL,
+  page_touched binary(14) NOT NULL default '',
+  page_latest int unsigned NOT NULL,
+  page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rev_page int unsigned NOT NULL,
+  rev_text_id int unsigned NOT NULL,
+  rev_comment tinyblob NOT NULL,
+  rev_user int unsigned NOT NULL default 0,
+  rev_user_text varchar(255) binary NOT NULL default '',
+  rev_timestamp binary(14) NOT NULL default '',
+  rev_minor_edit tinyint unsigned NOT NULL default 0,
+  rev_deleted tinyint unsigned NOT NULL default 0,
+  rev_len int unsigned,
+  rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  old_text mediumblob NOT NULL,
+  old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment tinyblob NOT NULL,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE TABLE /*_*/pagelinks (
+  pl_from int unsigned NOT NULL default 0,
+  pl_namespace int NOT NULL default 0,
+  pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+  tl_from int unsigned NOT NULL default 0,
+  tl_namespace int NOT NULL default 0,
+  tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+  il_from int unsigned NOT NULL default 0,
+  il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+  cl_from int unsigned NOT NULL default 0,
+  cl_to varchar(255) binary NOT NULL default '',
+  cl_sortkey varchar(70) binary NOT NULL default '',
+  cl_timestamp timestamp NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE TABLE /*_*/category (
+  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  cat_title varchar(255) binary NOT NULL,
+  cat_pages int signed NOT NULL default 0,
+  cat_subcats int signed NOT NULL default 0,
+  cat_files int signed NOT NULL default 0,
+  cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+  el_from int unsigned NOT NULL default 0,
+  el_to blob NOT NULL,
+  el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+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 /*_*/langlinks (
+  ll_from int unsigned NOT NULL default 0,
+
+  ll_lang varbinary(20) NOT NULL default '',
+  ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/site_stats (
+  ss_row_id int unsigned NOT NULL,
+  ss_total_views bigint unsigned default 0,
+  ss_total_edits bigint unsigned default 0,
+  ss_good_articles bigint unsigned default 0,
+  ss_total_pages bigint default '-1',
+  ss_users bigint default '-1',
+  ss_active_users bigint default '-1',
+  ss_admins int default '-1',
+  ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+  hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_reason tinyblob NOT NULL,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  img_minor_mime varbinary(32) NOT NULL default "unknown",
+  img_description tinyblob NOT NULL,
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description tinyblob NOT NULL,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  oi_minor_mime varbinary(32) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason text,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+  fa_minor_mime varbinary(32) default "unknown",
+  fa_description tinyblob,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/recentchanges (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_cur_time varbinary(14) NOT NULL default '',
+  rc_user int unsigned NOT NULL default 0,
+  rc_user_text varchar(255) binary NOT NULL,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment varchar(255) binary NOT NULL default '',
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+  rc_moved_to_title varchar(255) binary NOT NULL default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+  wl_user int unsigned NOT NULL,
+  wl_namespace int NOT NULL default 0,
+  wl_title varchar(255) binary NOT NULL default '',
+  wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/math (
+  math_inputhash varbinary(16) NOT NULL,
+  math_outputhash varbinary(16) NOT NULL,
+  math_html_conservativeness tinyint NOT NULL,
+  math_html text,
+  math_mathml text
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
+CREATE TABLE /*_*/searchindex (
+  si_page int unsigned NOT NULL,
+  si_title varchar(255) NOT NULL default '',
+  si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+  iw_prefix varchar(32) NOT NULL,
+  iw_url blob NOT NULL,
+  iw_local bool NOT NULL,
+  iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+  qc_type varbinary(32) NOT NULL,
+  qc_value int unsigned NOT NULL default 0,
+  qc_namespace int NOT NULL default 0,
+  qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+  value mediumblob,
+  exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+  tc_url varbinary(255) NOT NULL,
+  tc_contents text,
+  tc_time int NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(10) NOT NULL default '',
+  log_action varbinary(10) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_user int unsigned NOT NULL default 0,
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_comment varchar(255) NOT NULL default '',
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE TABLE /*_*/trackbacks (
+  tb_id int PRIMARY KEY AUTO_INCREMENT,
+  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  tb_title varchar(255) NOT NULL,
+  tb_url blob NOT NULL,
+  tb_ex text,
+  tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  job_cmd varbinary(60) NOT NULL default '',
+  job_namespace int NOT NULL,
+  job_title varchar(255) binary NOT NULL,
+  job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title);
+CREATE TABLE /*_*/querycache_info (
+  qci_type varbinary(32) NOT NULL default '',
+  qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+  rd_namespace int NOT NULL default 0,
+  rd_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+  qcc_type varbinary(32) NOT NULL,
+  qcc_value int unsigned NOT NULL default 0,
+  qcc_namespace int NOT NULL default 0,
+  qcc_title varchar(255) binary NOT NULL default '',
+  qcc_namespacetwo int NOT NULL default 0,
+  qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+  pr_page int NOT NULL,
+  pr_type varbinary(60) NOT NULL,
+  pr_level varbinary(60) NOT NULL,
+  pr_cascade tinyint NOT NULL,
+  pr_user int NULL,
+  pr_expiry varbinary(14) NULL,
+  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+  pt_namespace int NOT NULL,
+  pt_title varchar(255) binary NOT NULL,
+  pt_user int unsigned NOT NULL,
+  pt_reason tinyblob,
+  pt_timestamp binary(14) NOT NULL,
+  pt_expiry varbinary(14) NOT NULL default '',
+  pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+  pp_page int NOT NULL,
+  pp_propname varbinary(60) NOT NULL,
+  pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+  ul_key varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+  ct_rc_id int NULL,
+  ct_log_id int NULL,
+  ct_rev_id int NULL,
+  ct_tag varchar(255) NOT NULL,
+  ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+  ts_rc_id int NULL,
+  ts_log_id int NULL,
+  ts_rev_id int NULL,
+  ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+  vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
diff --git a/tests/phpunit/data/db/sqlite/tables-1.16.sql b/tests/phpunit/data/db/sqlite/tables-1.16.sql
new file mode 100644
index 00000000..6e56add2
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql
@@ -0,0 +1,483 @@
+-- This is a copy of MediaWiki 1.16 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  user_name varchar(255) binary NOT NULL default '',
+  user_real_name varchar(255) binary NOT NULL default '',
+  user_password tinyblob NOT NULL,
+  user_newpassword tinyblob NOT NULL,
+  user_newpass_time binary(14),
+  user_email tinytext NOT NULL,
+  user_options blob NOT NULL,
+  user_touched binary(14) NOT NULL default '',
+  user_token binary(32) NOT NULL default '',
+  user_email_authenticated binary(14),
+  user_email_token binary(32),
+  user_email_token_expires binary(14),
+  user_registration binary(14),
+  user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE TABLE /*_*/user_groups (
+  ug_user int unsigned NOT NULL default 0,
+  ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_newtalk (
+  user_id int NOT NULL default 0,
+  user_ip varbinary(40) NOT NULL default '',
+  user_last_timestamp binary(14) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+  up_user int NOT NULL,
+  up_property varbinary(32) NOT NULL,
+  up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  page_namespace int NOT NULL,
+  page_title varchar(255) binary NOT NULL,
+  page_restrictions tinyblob NOT NULL,
+  page_counter bigint unsigned NOT NULL default 0,
+  page_is_redirect tinyint unsigned NOT NULL default 0,
+  page_is_new tinyint unsigned NOT NULL default 0,
+  page_random real unsigned NOT NULL,
+  page_touched binary(14) NOT NULL default '',
+  page_latest int unsigned NOT NULL,
+  page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rev_page int unsigned NOT NULL,
+  rev_text_id int unsigned NOT NULL,
+  rev_comment tinyblob NOT NULL,
+  rev_user int unsigned NOT NULL default 0,
+  rev_user_text varchar(255) binary NOT NULL default '',
+  rev_timestamp binary(14) NOT NULL default '',
+  rev_minor_edit tinyint unsigned NOT NULL default 0,
+  rev_deleted tinyint unsigned NOT NULL default 0,
+  rev_len int unsigned,
+  rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  old_text mediumblob NOT NULL,
+  old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment tinyblob NOT NULL,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE TABLE /*_*/pagelinks (
+  pl_from int unsigned NOT NULL default 0,
+  pl_namespace int NOT NULL default 0,
+  pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+  tl_from int unsigned NOT NULL default 0,
+  tl_namespace int NOT NULL default 0,
+  tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+  il_from int unsigned NOT NULL default 0,
+  il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+  cl_from int unsigned NOT NULL default 0,
+  cl_to varchar(255) binary NOT NULL default '',
+  cl_sortkey varchar(70) binary NOT NULL default '',
+  cl_timestamp timestamp NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE TABLE /*_*/category (
+  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  cat_title varchar(255) binary NOT NULL,
+  cat_pages int signed NOT NULL default 0,
+  cat_subcats int signed NOT NULL default 0,
+  cat_files int signed NOT NULL default 0,
+  cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+  el_from int unsigned NOT NULL default 0,
+  el_to blob NOT NULL,
+  el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+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 '',
+  ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/site_stats (
+  ss_row_id int unsigned NOT NULL,
+  ss_total_views bigint unsigned default 0,
+  ss_total_edits bigint unsigned default 0,
+  ss_good_articles bigint unsigned default 0,
+  ss_total_pages bigint default '-1',
+  ss_users bigint default '-1',
+  ss_active_users bigint default '-1',
+  ss_admins int default '-1',
+  ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+  hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_reason tinyblob NOT NULL,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description tinyblob NOT NULL,
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description tinyblob NOT NULL,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason text,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description tinyblob,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/recentchanges (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_cur_time varbinary(14) NOT NULL default '',
+  rc_user int unsigned NOT NULL default 0,
+  rc_user_text varchar(255) binary NOT NULL,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment varchar(255) binary NOT NULL default '',
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+  rc_moved_to_title varchar(255) binary NOT NULL default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+  wl_user int unsigned NOT NULL,
+  wl_namespace int NOT NULL default 0,
+  wl_title varchar(255) binary NOT NULL default '',
+  wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/math (
+  math_inputhash varbinary(16) NOT NULL,
+  math_outputhash varbinary(16) NOT NULL,
+  math_html_conservativeness tinyint NOT NULL,
+  math_html text,
+  math_mathml text
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
+CREATE TABLE /*_*/searchindex (
+  si_page int unsigned NOT NULL,
+  si_title varchar(255) NOT NULL default '',
+  si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+  iw_prefix varchar(32) NOT NULL,
+  iw_url blob NOT NULL,
+  iw_local bool NOT NULL,
+  iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+  qc_type varbinary(32) NOT NULL,
+  qc_value int unsigned NOT NULL default 0,
+  qc_namespace int NOT NULL default 0,
+  qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+  value mediumblob,
+  exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+  tc_url varbinary(255) NOT NULL,
+  tc_contents text,
+  tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(32) NOT NULL default '',
+  log_action varbinary(32) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_user int unsigned NOT NULL default 0,
+  log_user_text varchar(255) binary NOT NULL default '',
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_page int unsigned NULL,
+  log_comment varchar(255) NOT NULL default '',
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE TABLE /*_*/log_search (
+  ls_field varbinary(32) NOT NULL,
+  ls_value varchar(255) NOT NULL,
+  ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/trackbacks (
+  tb_id int PRIMARY KEY AUTO_INCREMENT,
+  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  tb_title varchar(255) NOT NULL,
+  tb_url blob NOT NULL,
+  tb_ex text,
+  tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  job_cmd varbinary(60) NOT NULL default '',
+  job_namespace int NOT NULL,
+  job_title varchar(255) binary NOT NULL,
+  job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE TABLE /*_*/querycache_info (
+  qci_type varbinary(32) NOT NULL default '',
+  qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+  rd_namespace int NOT NULL default 0,
+  rd_title varchar(255) binary NOT NULL default '',
+  rd_interwiki varchar(32) default NULL,
+  rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+  qcc_type varbinary(32) NOT NULL,
+  qcc_value int unsigned NOT NULL default 0,
+  qcc_namespace int NOT NULL default 0,
+  qcc_title varchar(255) binary NOT NULL default '',
+  qcc_namespacetwo int NOT NULL default 0,
+  qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+  pr_page int NOT NULL,
+  pr_type varbinary(60) NOT NULL,
+  pr_level varbinary(60) NOT NULL,
+  pr_cascade tinyint NOT NULL,
+  pr_user int NULL,
+  pr_expiry varbinary(14) NULL,
+  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+  pt_namespace int NOT NULL,
+  pt_title varchar(255) binary NOT NULL,
+  pt_user int unsigned NOT NULL,
+  pt_reason tinyblob,
+  pt_timestamp binary(14) NOT NULL,
+  pt_expiry varbinary(14) NOT NULL default '',
+  pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+  pp_page int NOT NULL,
+  pp_propname varbinary(60) NOT NULL,
+  pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+  ul_key varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+  ct_rc_id int NULL,
+  ct_log_id int NULL,
+  ct_rev_id int NULL,
+  ct_tag varchar(255) NOT NULL,
+  ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+  ts_rc_id int NULL,
+  ts_log_id int NULL,
+  ts_rev_id int NULL,
+  ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+  vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+  lc_lang varbinary(32) NOT NULL,
+  lc_key varchar(255) NOT NULL,
+  lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
diff --git a/tests/phpunit/data/db/sqlite/tables-1.17.sql b/tests/phpunit/data/db/sqlite/tables-1.17.sql
new file mode 100644
index 00000000..69ae3764
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql
@@ -0,0 +1,516 @@
+-- This is a copy of MediaWiki 1.17 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  user_name varchar(255) binary NOT NULL default '',
+  user_real_name varchar(255) binary NOT NULL default '',
+  user_password tinyblob NOT NULL,
+  user_newpassword tinyblob NOT NULL,
+  user_newpass_time binary(14),
+  user_email tinytext NOT NULL,
+  user_options blob NOT NULL,
+  user_touched binary(14) NOT NULL default '',
+  user_token binary(32) NOT NULL default '',
+  user_email_authenticated binary(14),
+  user_email_token binary(32),
+  user_email_token_expires binary(14),
+  user_registration binary(14),
+  user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE TABLE /*_*/user_groups (
+  ug_user int unsigned NOT NULL default 0,
+  ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_newtalk (
+  user_id int NOT NULL default 0,
+  user_ip varbinary(40) NOT NULL default '',
+  user_last_timestamp binary(14) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+  up_user int NOT NULL,
+  up_property varbinary(32) NOT NULL,
+  up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  page_namespace int NOT NULL,
+  page_title varchar(255) binary NOT NULL,
+  page_restrictions tinyblob NOT NULL,
+  page_counter bigint unsigned NOT NULL default 0,
+  page_is_redirect tinyint unsigned NOT NULL default 0,
+  page_is_new tinyint unsigned NOT NULL default 0,
+  page_random real unsigned NOT NULL,
+  page_touched binary(14) NOT NULL default '',
+  page_latest int unsigned NOT NULL,
+  page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rev_page int unsigned NOT NULL,
+  rev_text_id int unsigned NOT NULL,
+  rev_comment tinyblob NOT NULL,
+  rev_user int unsigned NOT NULL default 0,
+  rev_user_text varchar(255) binary NOT NULL default '',
+  rev_timestamp binary(14) NOT NULL default '',
+  rev_minor_edit tinyint unsigned NOT NULL default 0,
+  rev_deleted tinyint unsigned NOT NULL default 0,
+  rev_len int unsigned,
+  rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  old_text mediumblob NOT NULL,
+  old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment tinyblob NOT NULL,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+  pl_from int unsigned NOT NULL default 0,
+  pl_namespace int NOT NULL default 0,
+  pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+  tl_from int unsigned NOT NULL default 0,
+  tl_namespace int NOT NULL default 0,
+  tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+  il_from int unsigned NOT NULL default 0,
+  il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+  cl_from int unsigned NOT NULL default 0,
+  cl_to varchar(255) binary NOT NULL default '',
+  cl_sortkey varbinary(230) NOT NULL default '',
+  cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+  cl_timestamp timestamp NOT NULL,
+  cl_collation varbinary(32) NOT NULL default '',
+  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  cat_title varchar(255) binary NOT NULL,
+  cat_pages int signed NOT NULL default 0,
+  cat_subcats int signed NOT NULL default 0,
+  cat_files int signed NOT NULL default 0,
+  cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+  el_from int unsigned NOT NULL default 0,
+  el_to blob NOT NULL,
+  el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+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 '',
+  ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+  iwl_from int unsigned NOT NULL default 0,
+  iwl_prefix varbinary(20) NOT NULL default '',
+  iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+  ss_row_id int unsigned NOT NULL,
+  ss_total_views bigint unsigned default 0,
+  ss_total_edits bigint unsigned default 0,
+  ss_good_articles bigint unsigned default 0,
+  ss_total_pages bigint default '-1',
+  ss_users bigint default '-1',
+  ss_active_users bigint default '-1',
+  ss_admins int default '-1',
+  ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+  hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_reason tinyblob NOT NULL,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description tinyblob NOT NULL,
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description tinyblob NOT NULL,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason text,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description tinyblob,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/recentchanges (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_cur_time varbinary(14) NOT NULL default '',
+  rc_user int unsigned NOT NULL default 0,
+  rc_user_text varchar(255) binary NOT NULL,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment varchar(255) binary NOT NULL default '',
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+  rc_moved_to_title varchar(255) binary NOT NULL default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+  wl_user int unsigned NOT NULL,
+  wl_namespace int NOT NULL default 0,
+  wl_title varchar(255) binary NOT NULL default '',
+  wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/math (
+  math_inputhash varbinary(16) NOT NULL,
+  math_outputhash varbinary(16) NOT NULL,
+  math_html_conservativeness tinyint NOT NULL,
+  math_html text,
+  math_mathml text
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
+CREATE TABLE /*_*/searchindex (
+  si_page int unsigned NOT NULL,
+  si_title varchar(255) NOT NULL default '',
+  si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+  iw_prefix varchar(32) NOT NULL,
+  iw_url blob NOT NULL,
+  iw_api blob NOT NULL,
+  iw_wikiid varchar(64) NOT NULL,
+  iw_local bool NOT NULL,
+  iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+  qc_type varbinary(32) NOT NULL,
+  qc_value int unsigned NOT NULL default 0,
+  qc_namespace int NOT NULL default 0,
+  qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+  value mediumblob,
+  exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+  tc_url varbinary(255) NOT NULL,
+  tc_contents text,
+  tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(32) NOT NULL default '',
+  log_action varbinary(32) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_user int unsigned NOT NULL default 0,
+  log_user_text varchar(255) binary NOT NULL default '',
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_page int unsigned NULL,
+  log_comment varchar(255) NOT NULL default '',
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE TABLE /*_*/log_search (
+  ls_field varbinary(32) NOT NULL,
+  ls_value varchar(255) NOT NULL,
+  ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/trackbacks (
+  tb_id int PRIMARY KEY AUTO_INCREMENT,
+  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  tb_title varchar(255) NOT NULL,
+  tb_url blob NOT NULL,
+  tb_ex text,
+  tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  job_cmd varbinary(60) NOT NULL default '',
+  job_namespace int NOT NULL,
+  job_title varchar(255) binary NOT NULL,
+  job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE TABLE /*_*/querycache_info (
+  qci_type varbinary(32) NOT NULL default '',
+  qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+  rd_namespace int NOT NULL default 0,
+  rd_title varchar(255) binary NOT NULL default '',
+  rd_interwiki varchar(32) default NULL,
+  rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+  qcc_type varbinary(32) NOT NULL,
+  qcc_value int unsigned NOT NULL default 0,
+  qcc_namespace int NOT NULL default 0,
+  qcc_title varchar(255) binary NOT NULL default '',
+  qcc_namespacetwo int NOT NULL default 0,
+  qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+  pr_page int NOT NULL,
+  pr_type varbinary(60) NOT NULL,
+  pr_level varbinary(60) NOT NULL,
+  pr_cascade tinyint NOT NULL,
+  pr_user int NULL,
+  pr_expiry varbinary(14) NULL,
+  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+  pt_namespace int NOT NULL,
+  pt_title varchar(255) binary NOT NULL,
+  pt_user int unsigned NOT NULL,
+  pt_reason tinyblob,
+  pt_timestamp binary(14) NOT NULL,
+  pt_expiry varbinary(14) NOT NULL default '',
+  pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+  pp_page int NOT NULL,
+  pp_propname varbinary(60) NOT NULL,
+  pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+  ul_key varchar(255) NOT NULL PRIMARY KEY,
+  ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+  ct_rc_id int NULL,
+  ct_log_id int NULL,
+  ct_rev_id int NULL,
+  ct_tag varchar(255) NOT NULL,
+  ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+  ts_rc_id int NULL,
+  ts_log_id int NULL,
+  ts_rev_id int NULL,
+  ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+  vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+  lc_lang varbinary(32) NOT NULL,
+  lc_key varchar(255) NOT NULL,
+  lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+  mr_resource varbinary(255) NOT NULL,
+  mr_lang varbinary(32) NOT NULL,
+  mr_blob mediumblob NOT NULL,
+  mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+  mrl_resource varbinary(255) NOT NULL,
+  mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+  md_module varbinary(255) NOT NULL,
+  md_skin varbinary(32) NOT NULL,
+  md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
diff --git a/tests/phpunit/data/db/sqlite/tables-1.18.sql b/tests/phpunit/data/db/sqlite/tables-1.18.sql
new file mode 100644
index 00000000..bedf6c33
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql
@@ -0,0 +1,535 @@
+-- This is a copy of MediaWiki 1.18 schema shared by MySQL and SQLite.
+-- It is used for updater testing. Comments are stripped to decrease
+-- file size, as we don't need to maintain it.
+
+CREATE TABLE /*_*/user (
+  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  user_name varchar(255) binary NOT NULL default '',
+  user_real_name varchar(255) binary NOT NULL default '',
+  user_password tinyblob NOT NULL,
+  user_newpassword tinyblob NOT NULL,
+  user_newpass_time binary(14),
+  user_email tinytext NOT NULL,
+  user_options blob NOT NULL,
+  user_touched binary(14) NOT NULL default '',
+  user_token binary(32) NOT NULL default '',
+  user_email_authenticated binary(14),
+  user_email_token binary(32),
+  user_email_token_expires binary(14),
+  user_registration binary(14),
+  user_editcount int
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
+CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
+CREATE INDEX /*i*/user_email ON /*_*/user (user_email(50));
+CREATE TABLE /*_*/user_groups (
+  ug_user int unsigned NOT NULL default 0,
+  ug_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
+CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
+CREATE TABLE /*_*/user_former_groups (
+  ufg_user int unsigned NOT NULL default 0,
+  ufg_group varbinary(16) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ufg_user_group ON /*_*/user_former_groups (ufg_user,ufg_group);
+CREATE TABLE /*_*/user_newtalk (
+  user_id int NOT NULL default 0,
+  user_ip varbinary(40) NOT NULL default '',
+  user_last_timestamp varbinary(14) NULL default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
+CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
+CREATE TABLE /*_*/user_properties (
+  up_user int NOT NULL,
+  up_property varbinary(255) NOT NULL,
+  up_value blob
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
+CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
+CREATE TABLE /*_*/page (
+  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  page_namespace int NOT NULL,
+  page_title varchar(255) binary NOT NULL,
+  page_restrictions tinyblob NOT NULL,
+  page_counter bigint unsigned NOT NULL default 0,
+  page_is_redirect tinyint unsigned NOT NULL default 0,
+  page_is_new tinyint unsigned NOT NULL default 0,
+  page_random real unsigned NOT NULL,
+  page_touched binary(14) NOT NULL default '',
+  page_latest int unsigned NOT NULL,
+  page_len int unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
+CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
+CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
+CREATE TABLE /*_*/revision (
+  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rev_page int unsigned NOT NULL,
+  rev_text_id int unsigned NOT NULL,
+  rev_comment tinyblob NOT NULL,
+  rev_user int unsigned NOT NULL default 0,
+  rev_user_text varchar(255) binary NOT NULL default '',
+  rev_timestamp binary(14) NOT NULL default '',
+  rev_minor_edit tinyint unsigned NOT NULL default 0,
+  rev_deleted tinyint unsigned NOT NULL default 0,
+  rev_len int unsigned,
+  rev_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
+CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
+CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
+CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
+CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
+CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
+CREATE TABLE /*_*/text (
+  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  old_text mediumblob NOT NULL,
+  old_flags tinyblob NOT NULL
+) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
+CREATE TABLE /*_*/archive (
+  ar_namespace int NOT NULL default 0,
+  ar_title varchar(255) binary NOT NULL default '',
+  ar_text mediumblob NOT NULL,
+  ar_comment tinyblob NOT NULL,
+  ar_user int unsigned NOT NULL default 0,
+  ar_user_text varchar(255) binary NOT NULL,
+  ar_timestamp binary(14) NOT NULL default '',
+  ar_minor_edit tinyint NOT NULL default 0,
+  ar_flags tinyblob NOT NULL,
+  ar_rev_id int unsigned,
+  ar_text_id int unsigned,
+  ar_deleted tinyint unsigned NOT NULL default 0,
+  ar_len int unsigned,
+  ar_page_id int unsigned,
+  ar_parent_id int unsigned default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
+CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
+CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
+CREATE TABLE /*_*/pagelinks (
+  pl_from int unsigned NOT NULL default 0,
+  pl_namespace int NOT NULL default 0,
+  pl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
+CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
+CREATE TABLE /*_*/templatelinks (
+  tl_from int unsigned NOT NULL default 0,
+  tl_namespace int NOT NULL default 0,
+  tl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
+CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
+CREATE TABLE /*_*/imagelinks (
+  il_from int unsigned NOT NULL default 0,
+  il_to varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
+CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
+CREATE TABLE /*_*/categorylinks (
+  cl_from int unsigned NOT NULL default 0,
+  cl_to varchar(255) binary NOT NULL default '',
+  cl_sortkey varbinary(230) NOT NULL default '',
+  cl_sortkey_prefix varchar(255) binary NOT NULL default '',
+  cl_timestamp timestamp NOT NULL,
+  cl_collation varbinary(32) NOT NULL default '',
+  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
+CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
+CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
+CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
+CREATE TABLE /*_*/category (
+  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  cat_title varchar(255) binary NOT NULL,
+  cat_pages int signed NOT NULL default 0,
+  cat_subcats int signed NOT NULL default 0,
+  cat_files int signed NOT NULL default 0,
+  cat_hidden tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
+CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
+CREATE TABLE /*_*/externallinks (
+  el_from int unsigned NOT NULL default 0,
+  el_to blob NOT NULL,
+  el_index blob NOT NULL
+) /*$wgDBTableOptions*/;
+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 '',
+  ll_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
+CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
+CREATE TABLE /*_*/iwlinks (
+  iwl_from int unsigned NOT NULL default 0,
+  iwl_prefix varbinary(20) NOT NULL default '',
+  iwl_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
+CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
+CREATE TABLE /*_*/site_stats (
+  ss_row_id int unsigned NOT NULL,
+  ss_total_views bigint unsigned default 0,
+  ss_total_edits bigint unsigned default 0,
+  ss_good_articles bigint unsigned default 0,
+  ss_total_pages bigint default '-1',
+  ss_users bigint default '-1',
+  ss_active_users bigint default '-1',
+  ss_admins int default '-1',
+  ss_images int default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
+CREATE TABLE /*_*/hitcounter (
+  hc_id int unsigned NOT NULL
+) ENGINE=HEAP MAX_ROWS=25000;
+CREATE TABLE /*_*/ipblocks (
+  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  ipb_address tinyblob NOT NULL,
+  ipb_user int unsigned NOT NULL default 0,
+  ipb_by int unsigned NOT NULL default 0,
+  ipb_by_text varchar(255) binary NOT NULL default '',
+  ipb_reason tinyblob NOT NULL,
+  ipb_timestamp binary(14) NOT NULL default '',
+  ipb_auto bool NOT NULL default 0,
+  ipb_anon_only bool NOT NULL default 0,
+  ipb_create_account bool NOT NULL default 1,
+  ipb_enable_autoblock bool NOT NULL default '1',
+  ipb_expiry varbinary(14) NOT NULL default '',
+  ipb_range_start tinyblob NOT NULL,
+  ipb_range_end tinyblob NOT NULL,
+  ipb_deleted bool NOT NULL default 0,
+  ipb_block_email bool NOT NULL default 0,
+  ipb_allow_usertalk bool NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
+CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
+CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
+CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
+CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
+CREATE TABLE /*_*/image (
+  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
+  img_size int unsigned NOT NULL default 0,
+  img_width int NOT NULL default 0,
+  img_height int NOT NULL default 0,
+  img_metadata mediumblob NOT NULL,
+  img_bits int NOT NULL default 0,
+  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  img_minor_mime varbinary(100) NOT NULL default "unknown",
+  img_description tinyblob NOT NULL,
+  img_user int unsigned NOT NULL default 0,
+  img_user_text varchar(255) binary NOT NULL,
+  img_timestamp varbinary(14) NOT NULL default '',
+  img_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
+CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
+CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
+CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
+CREATE TABLE /*_*/oldimage (
+  oi_name varchar(255) binary NOT NULL default '',
+  oi_archive_name varchar(255) binary NOT NULL default '',
+  oi_size int unsigned NOT NULL default 0,
+  oi_width int NOT NULL default 0,
+  oi_height int NOT NULL default 0,
+  oi_bits int NOT NULL default 0,
+  oi_description tinyblob NOT NULL,
+  oi_user int unsigned NOT NULL default 0,
+  oi_user_text varchar(255) binary NOT NULL,
+  oi_timestamp binary(14) NOT NULL default '',
+  oi_metadata mediumblob NOT NULL,
+  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
+  oi_minor_mime varbinary(100) NOT NULL default "unknown",
+  oi_deleted tinyint unsigned NOT NULL default 0,
+  oi_sha1 varbinary(32) NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
+CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
+CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
+CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
+CREATE TABLE /*_*/filearchive (
+  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  fa_name varchar(255) binary NOT NULL default '',
+  fa_archive_name varchar(255) binary default '',
+  fa_storage_group varbinary(16),
+  fa_storage_key varbinary(64) default '',
+  fa_deleted_user int,
+  fa_deleted_timestamp binary(14) default '',
+  fa_deleted_reason text,
+  fa_size int unsigned default 0,
+  fa_width int default 0,
+  fa_height int default 0,
+  fa_metadata mediumblob,
+  fa_bits int default 0,
+  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
+  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
+  fa_minor_mime varbinary(100) default "unknown",
+  fa_description tinyblob,
+  fa_user int unsigned default 0,
+  fa_user_text varchar(255) binary,
+  fa_timestamp binary(14) default '',
+  fa_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
+CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
+CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
+CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
+CREATE TABLE /*_*/uploadstash (
+	us_id int unsigned NOT NULL PRIMARY KEY auto_increment,
+	us_user int unsigned NOT NULL,
+	us_key varchar(255) NOT NULL,
+	us_orig_path varchar(255) NOT NULL,
+	us_path varchar(255) NOT NULL,
+	us_source_type varchar(50),
+	us_timestamp varbinary(14) not null,
+	us_status varchar(50) not null,
+	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_image_width int unsigned,
+	us_image_height int unsigned,
+	us_image_bits smallint unsigned
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/us_user ON /*_*/uploadstash (us_user);
+CREATE UNIQUE INDEX /*i*/us_key ON /*_*/uploadstash (us_key);
+CREATE INDEX /*i*/us_timestamp ON /*_*/uploadstash (us_timestamp);
+CREATE TABLE /*_*/recentchanges (
+  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  rc_timestamp varbinary(14) NOT NULL default '',
+  rc_cur_time varbinary(14) NOT NULL default '',
+  rc_user int unsigned NOT NULL default 0,
+  rc_user_text varchar(255) binary NOT NULL,
+  rc_namespace int NOT NULL default 0,
+  rc_title varchar(255) binary NOT NULL default '',
+  rc_comment varchar(255) binary NOT NULL default '',
+  rc_minor tinyint unsigned NOT NULL default 0,
+  rc_bot tinyint unsigned NOT NULL default 0,
+  rc_new tinyint unsigned NOT NULL default 0,
+  rc_cur_id int unsigned NOT NULL default 0,
+  rc_this_oldid int unsigned NOT NULL default 0,
+  rc_last_oldid int unsigned NOT NULL default 0,
+  rc_type tinyint unsigned NOT NULL default 0,
+  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
+  rc_moved_to_title varchar(255) binary NOT NULL default '',
+  rc_patrolled tinyint unsigned NOT NULL default 0,
+  rc_ip varbinary(40) NOT NULL default '',
+  rc_old_len int,
+  rc_new_len int,
+  rc_deleted tinyint unsigned NOT NULL default 0,
+  rc_logid int unsigned NOT NULL default 0,
+  rc_log_type varbinary(255) NULL default NULL,
+  rc_log_action varbinary(255) NULL default NULL,
+  rc_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
+CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
+CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
+CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
+CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
+CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
+CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
+CREATE TABLE /*_*/watchlist (
+  wl_user int unsigned NOT NULL,
+  wl_namespace int NOT NULL default 0,
+  wl_title varchar(255) binary NOT NULL default '',
+  wl_notificationtimestamp varbinary(14)
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
+CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
+CREATE TABLE /*_*/searchindex (
+  si_page int unsigned NOT NULL,
+  si_title varchar(255) NOT NULL default '',
+  si_text mediumtext NOT NULL
+) ENGINE=MyISAM;
+CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
+CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
+CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
+CREATE TABLE /*_*/interwiki (
+  iw_prefix varchar(32) NOT NULL,
+  iw_url blob NOT NULL,
+  iw_api blob NOT NULL,
+  iw_wikiid varchar(64) NOT NULL,
+  iw_local bool NOT NULL,
+  iw_trans tinyint NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
+CREATE TABLE /*_*/querycache (
+  qc_type varbinary(32) NOT NULL,
+  qc_value int unsigned NOT NULL default 0,
+  qc_namespace int NOT NULL default 0,
+  qc_title varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
+CREATE TABLE /*_*/objectcache (
+  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
+  value mediumblob,
+  exptime datetime
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
+CREATE TABLE /*_*/transcache (
+  tc_url varbinary(255) NOT NULL,
+  tc_contents text,
+  tc_time binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
+CREATE TABLE /*_*/logging (
+  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  log_type varbinary(32) NOT NULL default '',
+  log_action varbinary(32) NOT NULL default '',
+  log_timestamp binary(14) NOT NULL default '19700101000000',
+  log_user int unsigned NOT NULL default 0,
+  log_user_text varchar(255) binary NOT NULL default '',
+  log_namespace int NOT NULL default 0,
+  log_title varchar(255) binary NOT NULL default '',
+  log_page int unsigned NULL,
+  log_comment varchar(255) NOT NULL default '',
+  log_params blob NOT NULL,
+  log_deleted tinyint unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
+CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
+CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
+CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
+CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
+CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
+CREATE TABLE /*_*/log_search (
+  ls_field varbinary(32) NOT NULL,
+  ls_value varchar(255) NOT NULL,
+  ls_log_id int unsigned NOT NULL default 0
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
+CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
+CREATE TABLE /*_*/trackbacks (
+  tb_id int PRIMARY KEY AUTO_INCREMENT,
+  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
+  tb_title varchar(255) NOT NULL,
+  tb_url blob NOT NULL,
+  tb_ex text,
+  tb_name varchar(255)
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
+CREATE TABLE /*_*/job (
+  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+  job_cmd varbinary(60) NOT NULL default '',
+  job_namespace int NOT NULL,
+  job_title varchar(255) binary NOT NULL,
+  job_params blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
+CREATE TABLE /*_*/querycache_info (
+  qci_type varbinary(32) NOT NULL default '',
+  qci_timestamp binary(14) NOT NULL default '19700101000000'
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
+CREATE TABLE /*_*/redirect (
+  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
+  rd_namespace int NOT NULL default 0,
+  rd_title varchar(255) binary NOT NULL default '',
+  rd_interwiki varchar(32) default NULL,
+  rd_fragment varchar(255) binary default NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
+CREATE TABLE /*_*/querycachetwo (
+  qcc_type varbinary(32) NOT NULL,
+  qcc_value int unsigned NOT NULL default 0,
+  qcc_namespace int NOT NULL default 0,
+  qcc_title varchar(255) binary NOT NULL default '',
+  qcc_namespacetwo int NOT NULL default 0,
+  qcc_titletwo varchar(255) binary NOT NULL default ''
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
+CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
+CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
+CREATE TABLE /*_*/page_restrictions (
+  pr_page int NOT NULL,
+  pr_type varbinary(60) NOT NULL,
+  pr_level varbinary(60) NOT NULL,
+  pr_cascade tinyint NOT NULL,
+  pr_user int NULL,
+  pr_expiry varbinary(14) NULL,
+  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
+CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
+CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
+CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
+CREATE TABLE /*_*/protected_titles (
+  pt_namespace int NOT NULL,
+  pt_title varchar(255) binary NOT NULL,
+  pt_user int unsigned NOT NULL,
+  pt_reason tinyblob,
+  pt_timestamp binary(14) NOT NULL,
+  pt_expiry varbinary(14) NOT NULL default '',
+  pt_create_perm varbinary(60) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
+CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
+CREATE TABLE /*_*/page_props (
+  pp_page int NOT NULL,
+  pp_propname varbinary(60) NOT NULL,
+  pp_value blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
+CREATE TABLE /*_*/updatelog (
+  ul_key varchar(255) NOT NULL PRIMARY KEY,
+  ul_value blob
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/change_tag (
+  ct_rc_id int NULL,
+  ct_log_id int NULL,
+  ct_rev_id int NULL,
+  ct_tag varchar(255) NOT NULL,
+  ct_params blob NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
+CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
+CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
+CREATE TABLE /*_*/tag_summary (
+  ts_rc_id int NULL,
+  ts_log_id int NULL,
+  ts_rev_id int NULL,
+  ts_tags blob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
+CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
+CREATE TABLE /*_*/valid_tag (
+  vt_tag varchar(255) NOT NULL PRIMARY KEY
+) /*$wgDBTableOptions*/;
+CREATE TABLE /*_*/l10n_cache (
+  lc_lang varbinary(32) NOT NULL,
+  lc_key varchar(255) NOT NULL,
+  lc_value mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
+CREATE TABLE /*_*/msg_resource (
+  mr_resource varbinary(255) NOT NULL,
+  mr_lang varbinary(32) NOT NULL,
+  mr_blob mediumblob NOT NULL,
+  mr_timestamp binary(14) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
+CREATE TABLE /*_*/msg_resource_links (
+  mrl_resource varbinary(255) NOT NULL,
+  mrl_message varbinary(255) NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
+CREATE TABLE /*_*/module_deps (
+  md_module varbinary(255) NOT NULL,
+  md_skin varbinary(32) NOT NULL,
+  md_deps mediumblob NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
+
diff --git a/tests/phpunit/data/media/80x60-2layers.xcf b/tests/phpunit/data/media/80x60-2layers.xcf
new file mode 100644
index 00000000..c51e980c
Binary files /dev/null and b/tests/phpunit/data/media/80x60-2layers.xcf differ
diff --git a/tests/phpunit/data/media/80x60-Greyscale.xcf b/tests/phpunit/data/media/80x60-Greyscale.xcf
new file mode 100644
index 00000000..84bf3e67
Binary files /dev/null and b/tests/phpunit/data/media/80x60-Greyscale.xcf differ
diff --git a/tests/phpunit/data/media/80x60-RGB.xcf b/tests/phpunit/data/media/80x60-RGB.xcf
new file mode 100644
index 00000000..1d58f16d
Binary files /dev/null and b/tests/phpunit/data/media/80x60-RGB.xcf differ
diff --git a/tests/phpunit/data/media/Toll_Texas_1.svg b/tests/phpunit/data/media/Toll_Texas_1.svg
new file mode 100644
index 00000000..73004e3e
--- /dev/null
+++ b/tests/phpunit/data/media/Toll_Texas_1.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 12.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 51448)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+	<!ENTITY ns_svg "http://www.w3.org/2000/svg">
+	<!ENTITY ns_xlink "http://www.w3.org/1999/xlink">
+]>
+<svg  version="1.1" id="Layer_1" xmlns="&ns_svg;" xmlns:xlink="&ns_xlink;" width="385" height="385.0004883"
+	 viewBox="0 0 385 385.0004883" overflow="visible" enable-background="new 0 0 385 385.0004883" xml:space="preserve">
+<g>
+	<g>
+		<g>
+			<path fill="#FFFFFF" d="M0.5,24.5c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883
+				c0,13.2548828-10.7451172,24-24,24h-336c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+			<path fill="#FFFFFF" d="M192.5,192.5004883"/>
+		</g>
+		<g>
+			<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="M0.5,24.5
+				c0-13.2548828,10.7451172-24,24-24h336c13.2548828,0,24,10.7451172,24,24v336.0004883c0,13.2548828-10.7451172,24-24,24h-336
+				c-13.2548828,0-24-10.7451172-24-24V24.5L0.5,24.5z"/>
+			<path fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" d="
+				M192.5,192.5004883"/>
+		</g>
+	</g>
+	<g>
+		<path fill="#003882" d="M24.5,0.5h336c13.2548828,0,24,10.7451172,24,24v232.0004883H0.5V24.5
+			C0.5,11.2451172,11.2451172,0.5,24.5,0.5z"/>
+	</g>
+	<g>
+		<path fill="#FFFFFF" d="M10.5,24.5c0-7.7319336,6.2680664-14,14-14h336c7.7324219,0,14,6.2680664,14,14v222.0004883h-364V24.5z"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="93.809082,348.2397461 91.6787109,347.8666992 
+			89.5478516,347.7368164 85.2929688,347.7368164 83.1640625,347.8666992 78.9042969,348.6157227 76.7763672,349.1166992 
+			72.7666016,350.3706055 70.7631836,351.246582 68.8837891,352.121582 67.0053711,353.1254883 65.1254883,354.253418 
+			63.3740234,355.5063477 60.1210938,358.2602539 58.4926758,359.762207 57.1132813,361.2641602 55.7338867,362.8959961 
+			54.3603516,364.6459961 21.2949219,301.3999023 21.168457,301.3999023 22.5478516,299.6469727 23.9248047,298.0200195 
+			25.3022461,296.5180664 26.9296875,295.0141602 30.1875,292.2592773 31.9404297,291.0073242 33.8188477,289.8793945 
+			35.6972656,288.8774414 37.5761719,288.0004883 39.5791016,287.1245117 43.5878906,285.8706055 45.7148438,285.3706055 
+			49.9755859,284.6176758 52.1020508,284.4946289 56.3632813,284.4946289 58.4926758,284.6176758 60.6201172,284.9946289 
+			60.6201172,284.8696289 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="32.8154297,319.559082 45.0898438,312.4233398 
+			42.2080078,298.5209961 52.7299805,308.0375977 65.1254883,300.9008789 59.2421875,313.9243164 69.8867188,323.4428711 
+			55.7338867,321.9389648 49.8476563,334.965332 46.9677734,321.0629883 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#B01C2E" points="132.0053711,306.6606445 148.5385742,338.2211914 
+			147.4101563,339.7241211 146.1577148,341.2270508 144.9052734,342.6020508 143.5302734,343.9848633 142.1494141,345.2358398 
+			140.6484375,346.4868164 139.2705078,347.6157227 137.765625,348.6157227 136.1381836,349.6176758 134.6357422,350.621582 
+			133.0083008,351.3696289 131.3798828,352.246582 129.625,352.8745117 128,353.5024414 126.2441406,354.0004883 
+			124.4921875,354.5043945 122.737793,354.8774414 120.9853516,355.1293945 117.4785156,355.3793945 113.9707031,355.3793945 
+			112.09375,355.2543945 110.3408203,355.003418 108.5854492,354.6274414 106.9589844,354.253418 103.4501953,353.2485352 
+			101.8232422,352.6254883 100.0688477,351.871582 98.4428711,351.1235352 96.9389648,350.1176758 95.3095703,349.2426758 
+			93.809082,348.2397461 93.809082,348.1147461 93.809082,348.2397461 77.1523438,316.4282227 77.2753906,316.4282227 
+			77.2753906,316.5551758 78.78125,317.5581055 80.4057617,318.4350586 81.9116211,319.4360352 83.5395508,320.1860352 
+			85.2929688,320.9399414 86.9208984,321.5649414 90.4257813,322.5668945 92.0551758,322.9418945 93.809082,323.3188477 
+			95.5620117,323.5688477 97.4423828,323.6948242 100.9462891,323.6948242 102.6992188,323.5688477 104.4521484,323.4428711 
+			106.2060547,323.1928711 107.9609375,322.8168945 111.4682617,321.8168945 113.0947266,321.1889648 114.8481445,320.5639648 
+			116.4765625,319.6879883 118.1035156,318.9350586 119.6083984,317.934082 121.2363281,316.9301758 122.737793,315.9282227 
+			124.1152344,314.8012695 125.6196289,313.5483398 126.9960938,312.2983398 128.3759766,310.9194336 129.625,309.5424805 
+			130.8789063,308.0375977 132.0053711,306.5366211 132.1328125,306.5366211 		"/>
+	</g>
+	<g>
+		
+			<polyline fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" points="
+			60.7441406,285.1196289 63,286.4956055 65.2529297,287.7485352 67.5068359,288.8774414 69.8867188,289.8793945 
+			72.3916016,290.6313477 74.8955078,291.3813477 77.4008789,291.7583008 80.0302734,292.1333008 82.5366211,292.2592773 
+			85.1655273,292.2592773 87.6708984,292.0083008 90.3022461,291.6313477 92.8066406,291.1313477 95.3095703,290.3793945 
+			97.6904297,289.5024414 100.0688477,288.5004883 102.3256836,287.2504883 104.578125,285.8706055 106.7070313,284.4946289 
+			108.7124023,282.8637695 110.5888672,281.1108398 112.3427734,279.2329102 114.0986328,277.2290039 115.5976563,275.1010742 		"/>
+	</g>
+	<g>
+		
+			<line fill="none" stroke="#003882" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="3.863693" x1="115.4746094" y1="275.1010742" x2="131.8793945" y2="306.2836914"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="215.1650391,349.1166992 213.1601563,348.2397461 
+			211.2832031,347.237793 207.7773438,344.7329102 206.1503906,343.2319336 204.6435547,341.7270508 203.2685547,340.0991211 
+			202.0166016,338.347168 200.8867188,336.5942383 199.8857422,334.590332 199.0097656,332.7114258 198.2578125,330.7075195 
+			197.6328125,328.5776367 197.1289063,326.5756836 196.7548828,324.4458008 196.6318359,322.3168945 196.6318359,320.0629883 
+			196.7548828,317.934082 197.0058594,315.8041992 197.3818359,313.6743164 198.6357422,309.6665039 199.5107422,307.6635742 
+			200.5126953,305.7827148 201.6386719,303.9067383 202.890625,302.152832 204.2685547,300.3989258 205.6464844,298.8969727 
+			207.2744141,297.3920898 208.9023438,296.0161133 210.6572266,294.887207 212.5361328,293.7602539 214.4130859,292.7602539 
+			216.4179688,291.8833008 218.5449219,291.0073242 220.6767578,290.2543945 222.9306641,289.6274414 227.4384766,288.8774414 
+			229.6933594,288.6254883 231.9482422,288.5004883 234.2011719,288.5004883 236.4541016,288.6254883 238.7119141,288.8774414 
+			240.9638672,289.253418 243.21875,289.7543945 245.3476563,290.3793945 247.6025391,291.1313477 249.6054688,292.0083008 
+			251.7363281,293.0083008 251.7363281,292.8852539 253.6132813,294.137207 255.4931641,295.5151367 257.2460938,297.0161133 
+			258.8740234,298.6459961 260.5019531,300.3989258 261.8808594,302.277832 263.1328125,304.1577148 264.2578125,306.2836914 
+			266.0117188,310.543457 266.6386719,312.7983398 267.390625,317.3051758 267.5136719,319.6879883 267.390625,321.9418945 
+			267.265625,324.3188477 266.2626953,328.8286133 265.5117188,330.9575195 264.6347656,333.2114258 263.6318359,335.2163086 
+			262.5058594,337.2192383 261.1298828,339.0981445 259.625,340.9770508 258.1240234,342.6020508 256.3701172,344.1079102 
+			254.4902344,345.6098633 252.6142578,346.8618164 250.609375,347.9926758 248.4785156,348.9926758 246.3496094,349.8696289 
+			244.2216797,350.621582 242.0927734,351.246582 239.8359375,351.7485352 237.5830078,352.121582 235.3291016,352.3754883 
+			232.9501953,352.4985352 230.6933594,352.4985352 228.4414063,352.3754883 226.1865234,352.121582 223.9296875,351.7485352 
+			221.6777344,351.246582 219.421875,350.746582 217.2949219,349.9946289 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" points="219.9238281,303.6547852 221.9287109,301.5258789 
+			224.4335938,299.8999023 227.1904297,298.8969727 230.1943359,298.2700195 233.0742188,298.3959961 235.9550781,299.0219727 
+			238.7119141,300.2739258 241.0898438,301.9018555 243.0957031,304.1577148 244.5957031,306.5366211 245.9746094,308.9145508 
+			246.9765625,311.4204102 247.8535156,314.0512695 248.4785156,316.8051758 248.8535156,319.559082 248.9804688,322.3168945 
+			248.9804688,325.0727539 248.6035156,327.8256836 248.1035156,330.5805664 247.3496094,333.2114258 246.3496094,335.7172852 
+			244.9726563,337.8442383 243.34375,339.6000977 241.3408203,341.1020508 239.2109375,342.355957 236.8330078,343.2319336 
+			234.4511719,343.6049805 231.9482422,343.7319336 229.4414063,343.3579102 227.1904297,342.6020508 224.9326172,341.4770508 
+			222.9306641,340.0991211 221.1757813,338.347168 219.6748047,336.3422852 218.5449219,334.2114258 217.7949219,331.8334961 
+			217.0449219,329.7036133 216.0419922,325.4477539 215.7910156,323.1928711 215.6660156,320.9399414 215.6660156,318.684082 
+			215.9169922,316.4282227 216.1669922,314.300293 216.6679688,312.0454102 217.2949219,309.918457 218.0458984,307.7895508 
+			218.9208984,305.7827148 219.9238281,303.7817383 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="150.4169922,290.0063477 196.3789063,289.7543945 
+			192.7470703,304.4067383 192.6220703,304.5307617 192.1210938,303.7817383 191.4960938,303.1567383 190.8681641,302.5288086 
+			190.1162109,302.027832 189.2421875,301.652832 188.4887695,301.2768555 187.6113281,301.0258789 186.7353516,300.7758789 
+			179.9741211,300.7758789 179.8481445,345.1098633 179.8481445,345.2358398 180.0966797,346.1118164 180.3486328,347.1157227 
+			180.7246094,347.9926758 181.1005859,348.7407227 181.6015625,349.6176758 182.8549805,351.1235352 183.6054688,351.7485352 
+			158.5556641,351.7485352 158.5556641,351.871582 159.3095703,351.246582 160.0595703,350.4956055 160.6845703,349.7426758 
+			161.1875,348.9926758 161.6879883,347.9926758 161.9384766,347.1157227 162.1894531,346.1118164 162.3144531,345.1098633 
+			162.4375,300.9008789 156.5527344,300.9008789 155.1767578,301.0258789 153.9248047,301.2768555 152.671875,301.5258789 
+			151.4204102,301.9018555 150.1660156,302.4008789 148.9135742,303.0297852 146.6601563,304.2797852 146.6601563,304.4067383 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="275.7822266,344.3598633 276.03125,298.1450195 
+			275.90625,297.769043 275.90625,297.894043 275.7822266,296.7661133 275.5302734,295.7670898 275.15625,294.6391602 
+			274.7792969,293.637207 272.8984375,291.0073242 272.0234375,290.2543945 272.1484375,290.2543945 297.4492188,290.1313477 
+			296.4453125,290.8803711 295.5683594,291.6313477 294.6904297,292.5083008 294.0673828,293.5102539 293.5644531,294.6391602 
+			293.1904297,295.7670898 293.0644531,297.0161133 293.0644531,298.2700195 293.0644531,298.1450195 293.1904297,298.1450195 
+			293.0644531,340.7260742 292.9394531,340.7260742 294.8183594,341.3540039 296.8222656,341.8540039 298.7011719,342.1040039 
+			300.7050781,342.355957 302.7070313,342.4799805 304.5878906,342.355957 306.5917969,342.2299805 308.46875,341.8540039 
+			311.4746094,340.7260742 312.4765625,340.2231445 313.3535156,339.7241211 314.3535156,339.2241211 316.109375,337.972168 
+			311.8505859,351.621582 271.3984375,351.7485352 272.3994141,351.1235352 273.2753906,350.3706055 274.0292969,349.6176758 
+			275.2802734,347.6157227 275.53125,346.4868164 275.7822266,345.4858398 275.90625,344.2348633 		"/>
+	</g>
+	<g>
+		<polygon fill-rule="evenodd" clip-rule="evenodd" fill="#003882" points="327.5058594,344.3598633 327.7539063,298.1450195 
+			327.6308594,297.769043 327.6308594,297.894043 327.5058594,296.7661133 327.2539063,295.7670898 326.8769531,294.6391602 
+			326.5029297,293.637207 324.6259766,291.0073242 323.7480469,290.2543945 323.8740234,290.2543945 349.171875,290.1313477 
+			348.1708984,290.8803711 347.2929688,291.6313477 346.4179688,292.5083008 345.7890625,293.5102539 345.2871094,294.6391602 
+			344.9121094,295.7670898 344.7890625,297.0161133 344.7890625,298.2700195 344.7890625,298.1450195 344.9121094,298.1450195 
+			344.7890625,340.7260742 344.6640625,340.7260742 346.5410156,341.3540039 348.5458984,341.8540039 350.4238281,342.1040039 
+			352.4277344,342.355957 354.4316406,342.4799805 356.3105469,342.355957 358.3144531,342.2299805 360.1933594,341.8540039 
+			361.1933594,341.4770508 363.1992188,340.7260742 364.2011719,340.2231445 365.078125,339.7241211 366.0820313,339.2241211 
+			367.8320313,337.972168 363.5751953,351.621582 323.1220703,351.7485352 324.125,351.1235352 325.0019531,350.3706055 
+			325.7519531,349.6176758 327.0058594,347.6157227 327.2539063,346.4868164 327.5058594,345.4858398 327.6308594,344.2348633 		"/>
+	</g>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" fill="#003882" d="M188.4228516,211.0395508V89.7011719h-23.2304688V66.4711914
+	c7.4140625-0.3291016,13.8393555-2.3886719,19.2763672-6.1782227c5.4365234-3.7890625,9.3085938-8.7314453,11.6152344-14.8276367
+	h23.7236328v165.5742188H188.4228516z"/>
+</svg>
diff --git a/tests/phpunit/data/media/iptc-invalid-psir.jpg b/tests/phpunit/data/media/iptc-invalid-psir.jpg
new file mode 100644
index 00000000..01b9acf3
Binary files /dev/null and b/tests/phpunit/data/media/iptc-invalid-psir.jpg differ
diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php
index 01776c95..02571b55 100644
--- a/tests/phpunit/includes/ArticleTablesTest.php
+++ b/tests/phpunit/includes/ArticleTablesTest.php
@@ -6,29 +6,28 @@
 class ArticleTablesTest extends MediaWikiLangTestCase {
 
 	function testbug14404() {
-		global $wgUser, $wgContLang, $wgLanguageCode, $wgLang;
-		
-		$title = Title::newFromText("Bug 14404");
-		$article = new Article( $title );
-		$wgUser = new User();
-		$wgUser->mRights = array( 'createpage', 'edit', 'purge' );
+		global $wgContLang, $wgLanguageCode, $wgLang;
+
+		$title = Title::newFromText( 'Bug 14404' );
+		$page = WikiPage::factory( $title );
+		$user = new User();
+		$user->mRights = array( 'createpage', 'edit', 'purge' );
 		$wgLanguageCode = 'es';
 		$wgContLang = Language::factory( 'es' );
-		
+
 		$wgLang = Language::factory( 'fr' );
-		$status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0 );
-		$templates1 = $article->getUsedTemplates();
+		$status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', 0, false, $user );
+		$templates1 = $page->getUsedTemplates();
 
 		$wgLang = Language::factory( 'de' );
-		$article->mParserOptions = null; // Let it pick the new user language
-		$article->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
-		
+		$page->mPreparedEdit = false; // In order to force the rerendering of the same wikitext
+
 		// We need an edit, a purge is not enough to regenerate the tables
-		$status = $article->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE );
-		$templates2 = $article->getUsedTemplates();
-		
+		$status = $page->doEdit( '{{:{{int:history}}}}', 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+		$templates2 = $page->getUsedTemplates();
+
 		$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 285efee9..846d2b86 100644
--- a/tests/phpunit/includes/ArticleTest.php
+++ b/tests/phpunit/includes/ArticleTest.php
@@ -19,25 +19,25 @@ class ArticleTest extends MediaWikiTestCase {
 
 	}
 
-	function testImplementsGetMagic() {		
-		$this->assertEquals( -1, $this->article->mCounter, "Article __get magic" );
+	function testImplementsGetMagic() {
+		$this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
 	}
 
 	/**
 	 * @depends testImplementsGetMagic
 	 */
 	function testImplementsSetMagic() {
-
-		$this->article->mCounter = 2;
-		$this->assertEquals( 2, $this->article->mCounter, "Article __set magic" );
+		$this->article->mLatest = 2;
+		$this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
 	}
 
 	/**
 	 * @depends testImplementsSetMagic
 	 */
 	function testImplementsCallMagic() {
-		$this->article->mCounter = 33;
-		$this->assertEquals( 33, $this->article->getCount(), "Article __call magic" );
+		$this->article->mLatest = 33;
+		$this->article->mDataLoaded = true;
+		$this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" );
 	}
 
 	function testGetOrSetOnNewProperty() {
diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php
index 2f224ba8..749f40b4 100644
--- a/tests/phpunit/includes/BlockTest.php
+++ b/tests/phpunit/includes/BlockTest.php
@@ -2,11 +2,10 @@
 
 /**
  * @group Database
+ * @group Blocking
  */
 class BlockTest extends MediaWikiLangTestCase {
 
-	const REASON = "Some reason";
-
 	private $block, $madeAt;
 
 	/* variable used to save up the blockID we insert in this test suite */
@@ -36,8 +35,8 @@ class BlockTest extends MediaWikiLangTestCase {
 			$oldBlock->delete();
 		}
 
-		$this->block = new Block( 'UTBlockee', 1, 0,
-			self::REASON
+		$this->block = new Block( 'UTBlockee', $user->getID(), 0,
+			'Parce que', 0, false, time() + 100500
 		);
 		$this->madeAt = wfTimestamp( TS_MW );
 
@@ -68,7 +67,7 @@ class BlockTest extends MediaWikiLangTestCase {
 		// $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");
 
 	}
@@ -77,8 +76,9 @@ class BlockTest extends MediaWikiLangTestCase {
 	 * per bug 26425
 	 */
 	function testBug26425BlockTimestampDefaultsToTime() {
-
-		$this->assertEquals( $this->madeAt, $this->block->mTimestamp, "If no timestamp is specified, the block is recorded as time()");
+		// 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()");
 
 	}
 
@@ -91,6 +91,8 @@ class BlockTest extends MediaWikiLangTestCase {
 	 * @dataProvider dataBug29116
 	 */
 	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' );
 
@@ -121,4 +123,3 @@ class BlockTest extends MediaWikiLangTestCase {
 		);
 	}
 }
-
diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php
new file mode 100644
index 00000000..e98e9707
--- /dev/null
+++ b/tests/phpunit/includes/EditPageTest.php
@@ -0,0 +1,33 @@
+<?php
+
+class EditPageTest extends MediaWikiTestCase {
+
+	/**
+	 * @dataProvider dataExtractSectionTitle
+	 */
+	function testExtractSectionTitle( $section, $title ) {
+		$extracted = EditPage::extractSectionTitle( $section );
+		$this->assertEquals( $title, $extracted );
+	}
+
+	function dataExtractSectionTitle() {
+		return array(
+			array(
+				"== Test ==\n\nJust a test section.",
+				"Test"
+			),
+			array(
+				"An initial section, no header.",
+				false
+			),
+			array(
+				"An initial section with a fake heder (bug 32617)\n\n== Test == ??\nwtf",
+				false
+			),
+			array(
+				"== Section ==\nfollowed by a fake == Non-section == ??\nnoooo",
+				"Section"
+			)
+		);
+	}
+}
diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php
index 5b0aa98b..a9088cb2 100644
--- a/tests/phpunit/includes/ExtraParserTest.php
+++ b/tests/phpunit/includes/ExtraParserTest.php
@@ -10,11 +10,13 @@ class ExtraParserTest extends MediaWikiTestCase {
 		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;
 		$this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
@@ -61,20 +63,48 @@ class ExtraParserTest extends MediaWikiTestCase {
 	 * cleanSig() makes all templates substs and removes tildes
 	 */
 	function testCleanSig() {
+		global $wgCleanSignatures;
+		$oldCleanSignature = $wgCleanSignatures;
+		$wgCleanSignatures = true;
+
 		$title = Title::newFromText( __FUNCTION__ );
 		$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+		$wgCleanSignatures = $oldCleanSignature;
 		
 		$this->assertEquals( "{{SUBST:Foo}} ", $outputText );
 	}
-	
+
 	/**
-	 * cleanSigInSig() just removes tildes
+	 * cleanSig() should do nothing if disabled
 	 */
-	function testCleanSigInSig() {
+	function testCleanSigDisabled() {
+		global $wgCleanSignatures;
+		$oldCleanSignature = $wgCleanSignatures;
+		$wgCleanSignatures = false;
+
 		$title = Title::newFromText( __FUNCTION__ );
-		$outputText = $this->parser->cleanSigInSig( "{{Foo}} ~~~~" );
+		$outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+		$wgCleanSignatures = $oldCleanSignature;
 		
-		$this->assertEquals( "{{Foo}} ", $outputText );
+		$this->assertEquals( "{{Foo}} ~~~~", $outputText );
+	}
+	
+	/**
+	 * cleanSigInSig() just removes tildes
+	 * @dataProvider provideStringsForCleanSigInSig
+	 */
+	function testCleanSigInSig( $in, $out ) {
+		$this->assertEquals( Parser::cleanSigInSig( $in), $out );
+	}
+	
+	function provideStringsForCleanSigInSig() {
+		return array(
+			array( "{{Foo}} ~~~~", "{{Foo}} " ),
+			array( "~~~", "" ),
+			array( "~~~~~", "" ),
+		);
 	}
 	
 	function testGetSection() {
@@ -110,4 +140,28 @@ class ExtraParserTest extends MediaWikiTestCase {
 			'finalTitle' => $title,
 			'deps' => $deps );
 	}
+
+	/**
+	 * @group Database
+	 */
+	function testTrackingCategory() {
+		$title = Title::newFromText( __FUNCTION__ );
+		$catName =  wfMsgForContent( 'broken-file-category' );
+		$cat = Title::makeTitleSafe( NS_CATEGORY, $catName );
+		$expected = array( $cat->getDBkey() );
+		$parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+		$result = $parserOutput->getCategoryLinks();
+		$this->assertEquals( $expected, $result );
+	}
+
+	/**
+	 * @group Database
+	 */
+	function testTrackingCategorySpecial() {
+		// Special pages shouldn't have tracking cats.
+		$title = SpecialPage::getTitleFor( 'Contributions' );
+		$parserOutput = $this->parser->parse( "[[file:nonexistent]]" , $title, $this->options );
+		$result = $parserOutput->getCategoryLinks();
+		$this->assertEmpty( $result );
+	}
  }
diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php
index 85d76271..d86c95d7 100644
--- a/tests/phpunit/includes/FormOptionsInitializationTest.php
+++ b/tests/phpunit/includes/FormOptionsInitializationTest.php
@@ -25,9 +25,9 @@ class FormOptionsExposed extends FormOptions {
  *
  * Generated by PHPUnit on 2011-02-28 at 20:46:27.
  *
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
  *
- * @author Ashar Voultoiz
+ * @author Antoine Musso
  */
 class FormOptionsInitializationTest extends MediaWikiTestCase {
 	/**
diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php
index 86618d93..749343ec 100644
--- a/tests/phpunit/includes/FormOptionsTest.php
+++ b/tests/phpunit/includes/FormOptionsTest.php
@@ -12,9 +12,9 @@
  * Test class for FormOptions methods.
  * Generated by PHPUnit on 2011-02-28 at 20:46:27.
  *
- * Copyright © 2011, Ashar Voultoiz
+ * Copyright © 2011, Antoine Musso
  *
- * @author Ashar Voultoiz
+ * @author Antoine Musso
  */
 class FormOptionsTest extends MediaWikiTestCase {
 	/**
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
index 3d157d0a..3cb42f12 100644
--- a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
@@ -94,24 +94,80 @@ class GlobalTest extends MediaWikiTestCase {
 		$this->assertTrue( $end > $start, "Time is running backwards!" );
 	}
 
-	function testArrayToCGI() {
+	function dataArrayToCGI() {
+		return array(
+			array( array(), '' ), // empty
+			array( array( 'foo' => 'bar' ), 'foo=bar' ), // string test
+			array( array( 'foo' => '' ), 'foo=' ), // empty string test
+			array( array( 'foo' => 1 ), 'foo=1' ), // number test
+			array( array( 'foo' => true ), 'foo=1' ), // true test
+			array( array( 'foo' => false ), '' ), // false test
+			array( array( 'foo' => null ), '' ), // null test
+			array( array( 'foo' => 'A&B=5+6@!"\'' ), 'foo=A%26B%3D5%2B6%40%21%22%27' ), // urlencoding test
+			array( array( 'foo' => 'bar', 'baz' => 'is', 'asdf' => 'qwerty' ), 'foo=bar&baz=is&asdf=qwerty' ), // multi-item test
+			array( array( 'foo' => array( 'bar' => 'baz' ) ), 'foo%5Bbar%5D=baz' ),
+			array( array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ), 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf' ),
+			array( array( 'foo' => array( 'bar', 'baz' ) ), 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+			array( array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ), 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataArrayToCGI
+	 */
+	function testArrayToCGI( $array, $result ) {
+		$this->assertEquals( $result, wfArrayToCGI( $array ) );
+	}
+
+
+	function testArrayToCGI2() {
 		$this->assertEquals(
-			"baz=AT%26T&foo=bar",
+			"baz=bar&foo=bar",
 			wfArrayToCGI(
-				array( 'baz' => 'AT&T', 'ignore' => '' ),
+				array( 'baz' => 'bar' ),
 				array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
-		$this->assertEquals(
-			"path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost",
-			wfArrayToCGI( array(
-				'path' => array( 'wiki', 'test' ),
-				'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ) ) );
 	}
 
-	function testCgiToArray() {
-		$this->assertEquals(
-			array( 'path' => array( 'wiki', 'test' ),
-			'cfg' => array( 'servers' => array( 'http' => 'localhost' ) ) ),
-			wfCgiToArray( 'path%5B0%5D=wiki&path%5B1%5D=test&cfg%5Bservers%5D%5Bhttp%5D=localhost' ) );
+	function dataCgiToArray() {
+		return array(
+			array( '', array() ), // empty
+			array( 'foo=bar', array( 'foo' => 'bar' ) ), // string
+			array( 'foo=', array( 'foo' => '' ) ), // empty string
+			array( 'foo', array( 'foo' => '' ) ), // missing =
+			array( 'foo=bar&qwerty=asdf', array( 'foo' => 'bar', 'qwerty' => 'asdf' ) ), // multiple value
+			array( 'foo=A%26B%3D5%2B6%40%21%22%27', array( 'foo' => 'A&B=5+6@!"\'' ) ), // urldecoding test
+			array( 'foo%5Bbar%5D=baz', array( 'foo' => array( 'bar' => 'baz' ) ) ),
+			array( 'foo%5Bbar%5D=baz&foo%5Bqwerty%5D=asdf', array( 'foo' => array( 'bar' => 'baz', 'qwerty' => 'asdf' ) ) ),
+			array( 'foo%5B0%5D=bar&foo%5B1%5D=baz', array( 'foo' => array( 0 => 'bar', 1 => 'baz' ) ) ),
+			array( 'foo%5Bbar%5D%5Bbar%5D=baz', array( 'foo' => array( 'bar' => array( 'bar' => 'baz' ) ) ) ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataCgiToArray
+	 */
+	function testCgiToArray( $cgi, $result ) {
+		$this->assertEquals( $result, wfCgiToArray( $cgi ) );
+	}
+
+	function dataCgiRoundTrip() {
+		return array(
+			array( '' ),
+			array( 'foo=bar' ),
+			array( 'foo=' ),
+			array( 'foo=bar&baz=biz' ),
+			array( 'foo=A%26B%3D5%2B6%40%21%22%27' ),
+			array( 'foo%5Bbar%5D=baz' ),
+			array( 'foo%5B0%5D=bar&foo%5B1%5D=baz' ),
+			array( 'foo%5Bbar%5D%5Bbar%5D=baz' ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataCgiRoundTrip
+	 */
+	function testCgiRoundTrip( $cgi ) {
+		$this->assertEquals( $cgi, wfArrayToCGI( wfCgiToArray( $cgi ) ) );
 	}
 
 	function testMimeTypeMatch() {
@@ -176,263 +232,6 @@ class GlobalTest extends MediaWikiTestCase {
 				array( 'text/*'                => 1.0 ),
 				array( 'application/xhtml+xml' => 1.0 ) ) );
 	}
-
-	function testTimestamp() {
-		$t = gmmktime( 12, 34, 56, 1, 15, 2001 );
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, $t ),
-			'TS_UNIX to TS_MW' );
-		$this->assertEquals(
-			'19690115123456',
-			wfTimestamp( TS_MW, -30281104 ),
-			'Negative TS_UNIX to TS_MW' );
-		$this->assertEquals(
-			979562096,
-			wfTimestamp( TS_UNIX, $t ),
-			'TS_UNIX to TS_UNIX' );
-		$this->assertEquals(
-			'2001-01-15 12:34:56',
-			wfTimestamp( TS_DB, $t ),
-			'TS_UNIX to TS_DB' );
-		$this->assertEquals(
-			'20010115T123456Z',
-			wfTimestamp( TS_ISO_8601_BASIC, $t ),
-			'TS_ISO_8601_BASIC to TS_DB' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, '20010115123456' ),
-			'TS_MW to TS_MW' );
-		$this->assertEquals(
-			979562096,
-			wfTimestamp( TS_UNIX, '20010115123456' ),
-			'TS_MW to TS_UNIX' );
-		$this->assertEquals(
-			'2001-01-15 12:34:56',
-			wfTimestamp( TS_DB, '20010115123456' ),
-			'TS_MW to TS_DB' );
-		$this->assertEquals(
-			'20010115T123456Z',
-			wfTimestamp( TS_ISO_8601_BASIC, '20010115123456' ),
-			'TS_MW to TS_ISO_8601_BASIC' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, '2001-01-15 12:34:56' ),
-			'TS_DB to TS_MW' );
-		$this->assertEquals(
-			979562096,
-			wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ),
-			'TS_DB to TS_UNIX' );
-		$this->assertEquals(
-			'2001-01-15 12:34:56',
-			wfTimestamp( TS_DB, '2001-01-15 12:34:56' ),
-			'TS_DB to TS_DB' );
-		$this->assertEquals(
-			'20010115T123456Z',
-			wfTimestamp( TS_ISO_8601_BASIC, '2001-01-15 12:34:56' ),
-			'TS_DB to TS_ISO_8601_BASIC' );
-
-		# rfc2822 section 3.3
-
-		$this->assertEquals(
-			'Mon, 15 Jan 2001 12:34:56 GMT',
-			wfTimestamp( TS_RFC2822, '20010115123456' ),
-			'TS_MW to TS_RFC2822' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, 'Mon, 15 Jan 2001 12:34:56 GMT' ),
-			'TS_RFC2822 to TS_MW' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, ' Mon, 15 Jan 2001 12:34:56 GMT' ),
-			'TS_RFC2822 with leading space to TS_MW' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, '15 Jan 2001 12:34:56 GMT' ),
-			'TS_RFC2822 without optional day-of-week to TS_MW' );
-
-		# FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
-		# obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, 'Mon, 15         Jan 2001 12:34:56 GMT' ),
-			'TS_RFC2822 to TS_MW' );
-
-		# WSP = SP / HTAB ; rfc2234
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, "Mon, 15 Jan\x092001 12:34:56 GMT" ),
-			'TS_RFC2822 with HTAB to TS_MW' );
-
-		$this->assertEquals(
-			'20010115123456',
-			wfTimestamp( TS_MW, "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT" ),
-			'TS_RFC2822 with HTAB and SP to TS_MW' );
-
-		$this->assertEquals(
-			'19941106084937',
-			wfTimestamp( TS_MW, "Sun, 6 Nov 94 08:49:37 GMT" ),
-			'TS_RFC2822 with obsolete year to TS_MW' );
-	}
-
-	/**
-	 * This test checks wfTimestamp() with values outside.
-	 * It needs PHP 64 bits or PHP > 5.1.
-	 * See r74778 and bug 25451
-	 */
-	function testOldTimestamps() {
-		$this->assertEquals( 'Fri, 13 Dec 1901 20:45:54 GMT',
-			wfTimestamp( TS_RFC2822, '19011213204554' ),
-			'Earliest time according to php documentation' );
-
-		$this->assertEquals( 'Tue, 19 Jan 2038 03:14:07 GMT',
-			wfTimestamp( TS_RFC2822, '20380119031407' ),
-			'Latest 32 bit time' );
-
-		$this->assertEquals( '-2147483648',
-			wfTimestamp( TS_UNIX, '19011213204552' ),
-			'Earliest 32 bit unix time' );
-
-		$this->assertEquals( '2147483647',
-			wfTimestamp( TS_UNIX, '20380119031407' ),
-			'Latest 32 bit unix time' );
-
-		$this->assertEquals( 'Fri, 13 Dec 1901 20:45:52 GMT',
-			wfTimestamp( TS_RFC2822, '19011213204552' ),
-			'Earliest 32 bit time' );
-
-		$this->assertEquals( 'Fri, 13 Dec 1901 20:45:51 GMT',
-			wfTimestamp( TS_RFC2822, '19011213204551' ),
-			'Earliest 32 bit time - 1' );
-
-		$this->assertEquals( 'Tue, 19 Jan 2038 03:14:08 GMT',
-			wfTimestamp( TS_RFC2822, '20380119031408' ),
-			'Latest 32 bit time + 1' );
-
-		$this->assertEquals( '19011212000000',
-			wfTimestamp(TS_MW, '19011212000000'),
-			'Convert to itself r74778#c10645' );
-
-		$this->assertEquals( '-2147483649',
-			wfTimestamp( TS_UNIX, '19011213204551' ),
-			'Earliest 32 bit unix time - 1' );
-
-		$this->assertEquals( '2147483648',
-			wfTimestamp( TS_UNIX, '20380119031408' ),
-			'Latest 32 bit unix time + 1' );
-
-		$this->assertEquals( '19011213204551',
-			wfTimestamp( TS_MW, '-2147483649' ),
-			'1901 negative unix time to MediaWiki' );
-
-		$this->assertEquals( '18010115123456',
-			wfTimestamp( TS_MW, '-5331871504' ),
-			'1801 negative unix time to MediaWiki' );
-
-		$this->assertEquals( 'Tue, 09 Aug 0117 12:34:56 GMT',
-			wfTimestamp( TS_RFC2822, '0117-08-09 12:34:56'),
-			'Death of Roman Emperor [[Trajan]]');
-
-		/* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
-
-		$this->assertEquals( 'Sun, 01 Jan 0101 00:00:00 GMT',
-			wfTimestamp( TS_RFC2822, '-58979923200'),
-			'1/1/101');
-
-		$this->assertEquals( 'Mon, 01 Jan 0001 00:00:00 GMT',
-			wfTimestamp( TS_RFC2822, '-62135596800'),
-			'Year 1');
-
-		/* It is not clear if we should generate a year 0 or not
-		 * We are completely off RFC2822 requirement of year being
-		 * 1900 or later.
-		 */
-		$this->assertEquals( 'Wed, 18 Oct 0000 00:00:00 GMT',
-			wfTimestamp( TS_RFC2822, '-62142076800'),
-			'ISO 8601:2004 [[year 0]], also called [[1 BC]]');
-	}
-
-	function testHttpDate() {
-		# The Resource Loader uses wfTimestamp() to convert timestamps
-		# from If-Modified-Since header.
-		# Thus it must be able to parse all rfc2616 date formats
-		# http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
-
-		$this->assertEquals(
-			'19941106084937',
-			wfTimestamp( TS_MW, 'Sun, 06 Nov 1994 08:49:37 GMT' ),
-			'RFC 822 date' );
-
-		$this->assertEquals(
-			'19941106084937',
-			wfTimestamp( TS_MW, 'Sunday, 06-Nov-94 08:49:37 GMT' ),
-			'RFC 850 date' );
-
-		$this->assertEquals(
-			'19941106084937',
-			wfTimestamp( TS_MW, 'Sun Nov  6 08:49:37 1994' ),
-			"ANSI C's asctime() format" );
-
-		// See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
-		$this->assertEquals(
-			'20101122141242',
-			wfTimestamp( TS_MW, 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626' ),
-			"Netscape extension to HTTP/1.0" );
-
-	}
-
-	function testTimestampParameter() {
-		// 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
-
-		$now = wfTimestamp( TS_UNIX );
-		// 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 );
-		$this->assertNotEquals( false, $zero );
-		$this->assertLessThan( 5, $zero - $now );
-
-		$empty = wfTimestamp( TS_UNIX, '' );
-		$this->assertNotEquals( false, $empty );
-		$this->assertLessThan( 5, $empty - $now );
-
-		$null = wfTimestamp( TS_UNIX, null );
-		$this->assertNotEquals( false, $null );
-		$this->assertLessThan( 5, $null - $now );
-	}
-
-	function testBasename() {
-		$sets = array(
-			'' => '',
-			'/' => '',
-			'\\' => '',
-			'//' => '',
-			'\\\\' => '',
-			'a' => 'a',
-			'aaaa' => 'aaaa',
-			'/a' => 'a',
-			'\\a' => 'a',
-			'/aaaa' => 'aaaa',
-			'\\aaaa' => 'aaaa',
-			'/aaaa/' => 'aaaa',
-			'\\aaaa\\' => 'aaaa',
-			'\\aaaa\\' => 'aaaa',
-			'/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg',
-			'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE',
-			'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png',
-			);
-		foreach ( $sets as $from => $to ) {
-			$this->assertEquals( $to, wfBaseName( $from ),
-				"wfBaseName('$from') => '$to'" );
-		}
-	}
-	
 	
 	function testFallbackMbstringFunctions() {
 		
@@ -701,135 +500,6 @@ class GlobalTest 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
-	 * all upper cases language code.
-	 *
-	 * @see http://tools.ietf.org/html/bcp47
-	 * @dataProvider provideLanguageCodes()
-	 */
-	function testBCP47( $code, $expected ) {
-		$code = strtolower( $code );
-		$this->assertEquals( $expected, wfBCP47($code),
-			"Applying BCP47 standard to lower case '$code'"
-		);
-
-		$code = strtoupper( $code );
-		$this->assertEquals( $expected, wfBCP47($code),
-			"Applying BCP47 standard to upper case '$code'"
-		);
-	}
-
-	/**
-	 * Array format is ($code, $expected)
-	 */
-	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( '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( 'de-ch-1996', 'de-CH-1996' ),
-
-			# 2.2.6
-			array(
-				'en-latn-gb-boont-r-extended-sequence-x-private',
-				'en-Latn-GB-boont-r-extended-sequence-x-private'
-			),
-
-			// Examples from BCP47 Appendix A
-			# Simple language subtag:
-			array( 'DE', 'de' ),
-			array( 'fR', 'fr' ),
-			array( 'ja', 'ja' ),
-
-			# Language subtag plus script subtag:
-			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'         ),
-
-			# 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-biske', 'sl-rozaj-biske' ),
-			array( 'sl-nedis'      , 'sl-nedis'       ),
-
-			# Language-Region-Variant:
-			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'),
-
-			# Private use subtags:
-			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:
-			 *  az-Arab-x-AZE-derbend
-			 * AZE being private, it should be lower case, hence the test above
-			 * should probably be:
-			#array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
-			 */
-
-			# 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( 'sr-latn-qm', 'sr-Latn-QM' ),
-			array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
-
-			# Tags that use extensions
-			array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
-			array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
-			array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
-
-			# Invalid:
-			// 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)
-		 */);
-	}
-
 	/**
 	 * @dataProvider provideMakeUrlIndexes()
 	 */
@@ -886,7 +556,63 @@ class GlobalTest extends MediaWikiTestCase {
 			),
 		);
 	}
+	
+	/**
+	 * @dataProvider provideWfMatchesDomainList
+	 */
+	function testWfMatchesDomainList( $url, $domains, $expected, $description ) {
+		$actual = wfMatchesDomainList( $url, $domains );
+		$this->assertEquals( $expected, $actual, $description );
+	}
+	
+	function provideWfMatchesDomainList() {
+		$a = array();
+		$protocols = array( 'HTTP' => 'http:', 'HTTPS' => 'https:', 'protocol-relative' => '' );
+		foreach ( $protocols as $pDesc => $p ) {
+			$a = array_merge( $a, array(
+				array( "$p//www.example.com", array(), false, "No matches for empty domains array, $pDesc URL" ),
+				array( "$p//www.example.com", array( 'www.example.com' ), true, "Exact match in domains array, $pDesc URL" ),
+				array( "$p//www.example.com", array( 'example.com' ), true, "Match without subdomain in domains array, $pDesc URL" ),
+				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" ),
+			) );
+		}
+		return $a;
+	}
 
+	/**
+	 * @dataProvider provideWfShellMaintenanceCmdList
+	 */
+	function testWfShellMaintenanceCmd( $script, $parameters, $options, $expected, $description ) {
+		if( wfIsWindows() ) {
+			// Approximation that's good enough for our purposes just now
+			$expected = str_replace( "'", '"', $expected );
+		}
+		$actual = wfShellMaintenanceCmd( $script, $parameters, $options );
+		$this->assertEquals( $expected, $actual, $description );
+	}
+
+	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'),
+				"'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'),
+				"'$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'),
+				"'php5' 'MWScript.php' 'eval.php' '--help' '--test' 'y'",
+				"Called eval.php --help --test with wrapper and php option" ),
+		);
+	}
 	/* TODO: many more! */
 }
 
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
new file mode 100644
index 00000000..4879a38d
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @group Database
+ */
+class GlobalWithDBTest extends MediaWikiTestCase {
+	/**
+	 * @dataProvider provideWfIsBadImageList
+	 */
+	function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) {
+		$this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc );
+	}
+
+	function provideWfIsBadImageList() {
+		$blacklist = '* [[File:Bad.jpg]] except [[Nasty page]]';
+		return array(
+			array( 'Bad.jpg', false, $blacklist, true,
+				'Called on a bad image' ),
+			array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'A page' ), $blacklist, true,
+				'Called on a bad image' ),
+			array( 'NotBad.jpg', false, $blacklist, false,
+				'Called on a non-bad image' ),
+			array( 'Bad.jpg', Title::makeTitle( NS_MAIN, 'Nasty page' ), $blacklist, false,
+				'Called on a bad image but is on a whitelisted page' ),
+			array( 'File:Bad.jpg', false, $blacklist, false,
+				'Called on a bad image with File:' ),
+		);
+	}
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644
index 00000000..be6c99e7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * Unit tests for wfAssembleUrl()
+ */
+
+class wfAssembleUrl extends MediaWikiTestCase {
+	/** @dataProvider provideURLParts */
+	public function testWfAssembleUrl( $parts, $output ) {
+		$partsDump = print_r( $parts, true );
+		$this->assertEquals(
+			$output,
+			wfAssembleUrl( $parts ),
+			"Testing $partsDump assembles to $output"
+		);
+	}
+
+	/**
+	 * Provider of URL parts for testing wfAssembleUrl()
+	 *
+	 * @return array
+	 */
+	public function provideURLParts() {
+		$schemes = array(
+			'' => array(),
+			'//' => array(
+				'delimiter' => '//',
+			),
+			'http://' => array(
+				'scheme' => 'http',
+				'delimiter' => '://',
+			),
+		);
+
+		$hosts = array(
+			'' => array(),
+			'example.com' => array(
+				'host' => 'example.com',
+			),
+			'example.com:123' => array(
+				'host' => 'example.com',
+				'port' => 123,
+			),
+			'id@example.com' => array(
+				'user' => 'id',
+				'host' => 'example.com',
+			),
+			'id@example.com:123' => array(
+				'user' => 'id',
+				'host' => 'example.com',
+				'port' => 123,
+			),
+			'id:key@example.com' => array(
+				'user' => 'id',
+				'pass' => 'key',
+				'host' => 'example.com',
+			),
+			'id:key@example.com:123' => array(
+				'user' => 'id',
+				'pass' => 'key',
+				'host' => 'example.com',
+				'port' => 123,
+			),
+		);
+
+		$cases = array();
+		foreach ( $schemes as $scheme => $schemeParts ) {
+			foreach ( $hosts as $host => $hostParts ) {
+				foreach ( array( '', '/path' ) as $path ) {
+					foreach ( array( '', 'query' ) as $query ) {
+						foreach ( array( '', 'fragment' ) as $fragment ) {
+							$parts = array_merge(
+								$schemeParts,
+								$hostParts
+							);
+							$url = $scheme .
+								$host .
+								$path;
+
+							if ( $path ) {
+								$parts['path'] = $path;
+							}
+							if ( $query ) {
+								$parts['query'] = $query;
+								$url .= '?' . $query;
+							}
+							if( $fragment ) {
+								$parts['fragment'] = $fragment;
+								$url .= '#' . $fragment;
+							}
+
+
+							$cases[] = array(
+								$parts,
+								$url,
+							);
+						}
+					}
+				}
+			}
+		}
+
+		$complexURL = 'http://id:key@example.org:321' .
+			'/over/there?name=ferret&foo=bar#nose';
+		$cases[] = array(
+			wfParseUrl( $complexURL ),
+			$complexURL,
+		);
+
+		return $cases;
+	}
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
new file mode 100644
index 00000000..f4ec7a5f
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
@@ -0,0 +1,133 @@
+<?php
+/**
+ * Unit tests for wfBCP47()
+ */
+class wfBCP47 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
+	 * all upper cases language code.
+	 *
+	 * @see http://tools.ietf.org/html/bcp47
+	 * @dataProvider provideLanguageCodes()
+	 */
+	function testBCP47( $code, $expected ) {
+		$code = strtolower( $code );
+		$this->assertEquals( $expected, wfBCP47($code),
+			"Applying BCP47 standard to lower case '$code'"
+		);
+
+		$code = strtoupper( $code );
+		$this->assertEquals( $expected, wfBCP47($code),
+			"Applying BCP47 standard to upper case '$code'"
+		);
+	}
+
+	/**
+	 * Array format is ($code, $expected)
+	 */
+	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( '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( 'de-ch-1996', 'de-CH-1996' ),
+
+			# 2.2.6
+			array(
+				'en-latn-gb-boont-r-extended-sequence-x-private',
+				'en-Latn-GB-boont-r-extended-sequence-x-private'
+			),
+
+			// Examples from BCP47 Appendix A
+			# Simple language subtag:
+			array( 'DE', 'de' ),
+			array( 'fR', 'fr' ),
+			array( 'ja', 'ja' ),
+
+			# Language subtag plus script subtag:
+			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'         ),
+
+			# 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-biske', 'sl-rozaj-biske' ),
+			array( 'sl-nedis'      , 'sl-nedis'       ),
+
+			# Language-Region-Variant:
+			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'),
+
+			# Private use subtags:
+			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:
+			 *  az-Arab-x-AZE-derbend
+			 * AZE being private, it should be lower case, hence the test above
+			 * should probably be:
+			#array( 'az-arab-x-aze-derbend', 'az-Arab-x-AZE-derbend' ),
+			 */
+
+			# 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( 'sr-latn-qm', 'sr-Latn-QM' ),
+			array( 'sr-qaaa-rs', 'sr-Qaaa-RS' ),
+
+			# Tags that use extensions
+			array( 'en-us-u-islamcal', 'en-US-u-islamcal' ),
+			array( 'zh-cn-a-myext-x-private', 'zh-CN-a-myext-x-private' ),
+			array( 'en-a-myext-b-another', 'en-a-myext-b-another' ),
+
+			# Invalid:
+			// 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/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644
index 00000000..59954b23
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Tests for wfBaseName()
+ */
+class wfBaseName extends MediaWikiTestCase {
+	/**
+	 * @dataProvider providePaths
+	 */
+	function testBaseName( $fullpath, $basename ) {
+		$this->assertEquals( $basename, wfBaseName( $fullpath ),
+				"wfBaseName('$fullpath') => '$basename'" );
+	}
+	
+	function providePaths() {
+		return array(
+			array( '', '' ),
+			array( '/', '' ),
+			array( '\\', '' ),
+			array( '//', '' ),
+			array( '\\\\', '' ),
+			array( 'a', 'a' ),
+			array( 'aaaa', 'aaaa' ),
+			array( '/a', 'a' ),
+			array( '\\a', 'a' ),
+			array( '/aaaa', 'aaaa' ),
+			array( '\\aaaa', 'aaaa' ),
+			array( '/aaaa/', 'aaaa' ),
+			array( '\\aaaa\\', 'aaaa' ),
+			array( '\\aaaa\\', 'aaaa' ),
+			array( '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg',
+				'93px-Zork_Grand_Inquisitor_box_cover.jpg' ),
+			array( 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE', 'VIEWER.EXE' ),
+			array( 'Östergötland_coat_of_arms.png', 'Östergötland_coat_of_arms.png' ),
+		);
+	}
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php
deleted file mode 100644
index b388b266..00000000
--- a/tests/phpunit/includes/GlobalFunctions/wfExpandUrl.php
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-/* 
- * Unit tests for wfExpandUrl()
- */
-
-class wfExpandUrl 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;
-		
-		// Fake $_SERVER['HTTPS'] if needed
-		if ( $httpsMode ) {
-			$_SERVER['HTTPS'] = 'on';
-		} else {
-			unset( $_SERVER['HTTPS'] );
-		}
-		
-		$this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message );
-		
-		// Restore $wgServer and $wgCanonicalServer
-		$wgServer = $oldServer;
-		$wgCanonicalServer = $oldCanServer;
-	}
-
-	/**
-	 * Provider of URL examples for testing wfExpandUrl()
-	 */
-	public 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 );
-		
-		$retval = array();
-		foreach ( $modes as $mode ) {
-			$httpsMode = $mode == 'https';
-			foreach ( $servers as $serverDesc => $server ) {
-				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 )" );
-						# 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 )" );
-						
-						// Determine expected protocol
-						$p = $protoDesc . ':'; // default case
-						if ( $protoDesc == 'protocol-relative' ) {
-							$p = '';
-						} else if ( $protoDesc == 'current' ) {
-							$p = "$mode:";
-						} else if ( $protoDesc == 'canonical' ) {
-							$p = "$canServerMode:";
-						} else {
-							$p = $protoDesc . ':';
-						}
-						// Determine expected server name
-						if ( $protoDesc == 'canonical' ) {
-							$srv = $canServer;
-						} else if ( $serverDesc == 'protocol-relative' ) {
-							$srv = $p . $server;
-						} else {
-							$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 )" );
-					}
-				}
-			}
-		}
-		return $retval;
-	}
-}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
new file mode 100644
index 00000000..192689f8
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * Unit tests for wfExpandUrl()
+ */
+
+class wfExpandUrl 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;
+
+		// Fake $_SERVER['HTTPS'] if needed
+		if ( $httpsMode ) {
+			$_SERVER['HTTPS'] = 'on';
+		} else {
+			unset( $_SERVER['HTTPS'] );
+		}
+
+		$this->assertEquals( $fullUrl, wfExpandUrl( $shortUrl, $defaultProto ), $message );
+
+		// Restore $wgServer and $wgCanonicalServer
+		$wgServer = $oldServer;
+		$wgCanonicalServer = $oldCanServer;
+	}
+
+	/**
+	 * Provider of URL examples for testing wfExpandUrl()
+	 *
+	 * @return array
+	 */
+	public 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 );
+
+		$retval = array();
+		foreach ( $modes as $mode ) {
+			$httpsMode = $mode == 'https';
+			foreach ( $servers as $serverDesc => $server ) {
+				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 )" );
+						# 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 )" );
+
+						// Determine expected protocol
+						$p = $protoDesc . ':'; // default case
+						if ( $protoDesc == 'protocol-relative' ) {
+							$p = '';
+						} elseif ( $protoDesc == 'current' ) {
+							$p = "$mode:";
+						} elseif ( $protoDesc == 'canonical' ) {
+							$p = "$canServerMode:";
+						} else {
+							$p = $protoDesc . ':';
+						}
+						// Determine expected server name
+						if ( $protoDesc == 'canonical' ) {
+							$srv = $canServer;
+						} elseif ( $serverDesc == 'protocol-relative' ) {
+							$srv = $p . $server;
+						} else {
+							$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 )" );
+					}
+				}
+			}
+		}
+		return $retval;
+	}
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644
index 00000000..1cf0e0fb
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
@@ -0,0 +1,90 @@
+<?php
+/**
+ * Unit tests for wfRemoveDotSegments()
+ */
+
+class wfRemoveDotSegments extends MediaWikiTestCase {
+	/** @dataProvider providePaths */
+	public function testWfRemoveDotSegments( $inputPath, $outputPath ) {
+		$this->assertEquals(
+			$outputPath,
+			wfRemoveDotSegments( $inputPath ),
+			"Testing $inputPath expands to $outputPath"
+		);
+	}
+
+	/**
+	 * Provider of URL paths for testing wfRemoveDotSegments()
+	 *
+	 * @return array
+	 */
+	public function providePaths() {
+		return array(
+			array( '/a/b/c/./../../g', '/a/g' ),
+			array( 'mid/content=5/../6', 'mid/6' ),
+			array( '/a//../b', '/a/b' ),
+			array( '/.../a', '/.../a' ),
+			array( '.../a', '.../a' ),
+			array( '', '' ),
+			array( '/', '/' ),
+			array( '//', '//' ),
+			array( '.', '' ),
+			array( '..', '' ),
+			array( '...', '...' ),
+			array( '/.', '/' ),
+			array( '/..', '/' ),
+			array( './', '' ),
+			array( '../', '' ),
+			array( './a', 'a' ),
+			array( '../a', 'a' ),
+			array( '../../a', 'a' ),
+			array( '.././a', 'a' ),
+			array( './../a', 'a' ),
+			array( '././a', 'a' ),
+			array( '../../', '' ),
+			array( '.././', '' ),
+			array( './../', '' ),
+			array( '././', '' ),
+			array( '../..', '' ),
+			array( '../.', '' ),
+			array( './..', '' ),
+			array( './.', '' ),
+			array( '/../../a', '/a' ),
+			array( '/.././a', '/a' ),
+			array( '/./../a', '/a' ),
+			array( '/././a', '/a' ),
+			array( '/../../', '/' ),
+			array( '/.././', '/' ),
+			array( '/./../', '/' ),
+			array( '/././', '/' ),
+			array( '/../..', '/' ),
+			array( '/../.', '/' ),
+			array( '/./..', '/' ),
+			array( '/./.', '/' ),
+			array( 'b/../../a', '/a' ),
+			array( 'b/.././a', '/a' ),
+			array( 'b/./../a', '/a' ),
+			array( 'b/././a', 'b/a' ),
+			array( 'b/../../', '/' ),
+			array( 'b/.././', '/' ),
+			array( 'b/./../', '/' ),
+			array( 'b/././', 'b/' ),
+			array( 'b/../..', '/' ),
+			array( 'b/../.', '/' ),
+			array( 'b/./..', '/' ),
+			array( 'b/./.', 'b/' ),
+			array( '/b/../../a', '/a' ),
+			array( '/b/.././a', '/a' ),
+			array( '/b/./../a', '/a' ),
+			array( '/b/././a', '/b/a' ),
+			array( '/b/../../', '/' ),
+			array( '/b/.././', '/' ),
+			array( '/b/./../', '/' ),
+			array( '/b/././', '/b/' ),
+			array( '/b/../..', '/' ),
+			array( '/b/../.', '/' ),
+			array( '/b/./..', '/' ),
+			array( '/b/./.', '/b/' ),
+		);
+	}
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
new file mode 100644
index 00000000..1df26d2c
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
@@ -0,0 +1,28 @@
+<?php
+
+class wfShorthandToIntegerTest extends MediaWikiTestCase {
+	/**
+	 * @dataProvider provideABunchOfShorthands
+	 */
+	function testWfShorthandToInteger( $input, $output, $description ) {
+		$this->assertEquals(
+			wfShorthandToInteger( $input ),
+			$output,
+			$description
+		);
+	}
+
+	function provideABunchOfShorthands() {
+		return array(
+			array( '', -1, 'Empty string' ),
+			array( '     ', -1, 'String of spaces' ),
+			array( '1G', 1024 * 1024 * 1024, 'One gig uppercased' ),
+			array( '1g', 1024 * 1024 * 1024, 'One gig lowercased' ),
+			array( '1M', 1024 * 1024, 'One meg uppercased' ),
+			array( '1m', 1024 * 1024, 'One meg lowercased' ),
+			array( '1K', 1024, 'One kb uppercased' ),
+			array( '1k', 1024, 'One kb lowercased' ),
+		);
+	}
+	
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
new file mode 100644
index 00000000..505c28c7
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
@@ -0,0 +1,134 @@
+<?php
+
+/*
+ * Tests for wfTimestamp()
+ */
+class wfTimestamp extends MediaWikiTestCase {
+	/**
+	 * @dataProvider provideNormalTimestamps
+	 */
+	function testNormalTimestamps( $input, $format, $output, $desc ) {
+		$this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+	}
+
+	function provideNormalTimestamps() {
+		$t = gmmktime( 12, 34, 56, 1, 15, 2001 );
+		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' ),
+			array( $t, TS_UNIX, 979562096, 'TS_UNIX to TS_UNIX' ),
+			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' ),
+			array( '2001-01-15 12:34:56', TS_DB, '2001-01-15 12:34:56', 'TS_DB to TS_DB' ),
+			array( '2001-01-15 12:34:56', TS_ISO_8601_BASIC, '20010115T123456Z', 'TS_DB to TS_ISO_8601_BASIC' ),
+
+			# rfc2822 section 3.3
+			array( '20010115123456', TS_RFC2822, 'Mon, 15 Jan 2001 12:34:56 GMT', 'TS_MW to TS_RFC2822' ),
+			array( 'Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+			array( ' Mon, 15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 with leading space to TS_MW' ),
+			array( '15 Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 without optional day-of-week to TS_MW' ),
+
+			# FWS = ([*WSP CRLF] 1*WSP) / obs-FWS ; Folding white space
+			# obs-FWS = 1*WSP *(CRLF 1*WSP) ; Section 4.2
+			array( 'Mon, 15         Jan 2001 12:34:56 GMT', TS_MW, '20010115123456', 'TS_RFC2822 to TS_MW' ),
+
+			# WSP = SP / HTAB ; rfc2234
+			array( "Mon, 15 Jan\x092001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB to TS_MW' ),
+			array( "Mon, 15 Jan\x09 \x09  2001 12:34:56 GMT", TS_MW, '20010115123456', 'TS_RFC2822 with HTAB and SP to TS_MW' ),
+			array( 'Sun, 6 Nov 94 08:49:37 GMT', TS_MW, '19941106084937', 'TS_RFC2822 with obsolete year to TS_MW' ),
+		);
+	}
+
+	/**
+	 * This test checks wfTimestamp() with values outside.
+	 * It needs PHP 64 bits or PHP > 5.1.
+	 * See r74778 and bug 25451
+	 * @dataProvider provideOldTimestamps
+	 */
+	function testOldTimestamps( $input, $format, $output, $desc ) {
+		$this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+	}
+
+	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' ),
+			array( '20380119031407', TS_UNIX, '2147483647', 'Latest 32 bit unix time' ),
+			array( '19011213204552', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:52 GMT', 'Earliest 32 bit time' ),
+			array( '19011213204551', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:51 GMT', 'Earliest 32 bit time - 1' ),
+			array( '20380119031408', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:08 GMT', 'Latest 32 bit time + 1' ),
+			array( '19011212000000', TS_MW, '19011212000000', 'Convert to itself r74778#c10645' ),
+			array( '19011213204551', TS_UNIX, '-2147483649', 'Earliest 32 bit unix time - 1' ),
+			array( '20380119031408', TS_UNIX, '2147483648', 'Latest 32 bit unix time + 1' ),
+			array( '-2147483649', TS_MW, '19011213204551', '1901 negative unix time to MediaWiki' ),
+			array( '-5331871504', TS_MW, '18010115123456', '1801 negative unix time to MediaWiki' ),
+			array( '0117-08-09 12:34:56', TS_RFC2822, 'Tue, 09 Aug 0117 12:34:56 GMT', 'Death of Roman Emperor [[Trajan]]' ),
+
+			/* @todo FIXME: 00 to 101 years are taken as being in [1970-2069] */
+			array( '-58979923200', TS_RFC2822, 'Sun, 01 Jan 0101 00:00:00 GMT', '1/1/101' ),
+			array( '-62135596800', TS_RFC2822, 'Mon, 01 Jan 0001 00:00:00 GMT', 'Year 1' ),
+
+			/* It is not clear if we should generate a year 0 or not
+			 * We are completely off RFC2822 requirement of year being
+			 * 1900 or later.
+			 */
+			array( '-62142076800', TS_RFC2822, 'Wed, 18 Oct 0000 00:00:00 GMT', 'ISO 8601:2004 [[year 0]], also called [[1 BC]]' ),
+		);
+	}
+
+	/**
+	 * The Resource Loader uses wfTimestamp() to convert timestamps
+	 * from If-Modified-Since header. Thus it must be able to parse all
+	 * rfc2616 date formats
+	 * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1
+	 * @dataProvider provideHttpDates
+	 */
+	function testHttpDate( $input, $output, $desc ) {
+		$this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+	}
+
+	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' ),
+			array( 'Sun Nov  6 08:49:37 1994', '19941106084937', "ANSI C's asctime() format" ),
+			// See http://www.squid-cache.org/mail-archive/squid-users/200307/0122.html and r77171
+			array( 'Mon, 22 Nov 2010 14:12:42 GMT; length=52626', '20101122141242', 'Netscape extension to HTTP/1.0' ),
+		);
+	}
+
+	/**
+	 * 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() {
+		$now = wfTimestamp( TS_UNIX );
+		// 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 );
+		$this->assertNotEquals( false, $zero );
+		$this->assertLessThan( 5, $zero - $now );
+
+		$empty = wfTimestamp( TS_UNIX, '' );
+		$this->assertNotEquals( false, $empty );
+		$this->assertLessThan( 5, $empty - $now );
+
+		$null = wfTimestamp( TS_UNIX, null );
+		$this->assertNotEquals( false, $null );
+		$this->assertLessThan( 5, $null - $now );
+	}
+}
diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php
index 96bb1803..67b60d32 100644
--- a/tests/phpunit/includes/HtmlTest.php
+++ b/tests/phpunit/includes/HtmlTest.php
@@ -3,58 +3,90 @@
 
 class HtmlTest extends MediaWikiTestCase {
 	private static $oldLang;
+	private static $oldContLang;
+	private static $oldLanguageCode;
+	private static $oldNamespaces;
 
 	public function setUp() {
-		global $wgLang, $wgLanguageCode;
+		global $wgLang, $wgContLang, $wgLanguageCode;
 		
 		self::$oldLang = $wgLang;
+		self::$oldContLang = $wgContLang;
+		self::$oldNamespaces = $wgContLang->getNamespaces();
+		self::$oldLanguageCode = $wgLanguageCode;
+		
 		$wgLanguageCode = 'en';
-		$wgLang = Language::factory( $wgLanguageCode );
+		$wgContLang = $wgLang = Language::factory( $wgLanguageCode );
+
+		// Hardcode namespaces during test runs,
+		// so that html output based on existing namespaces
+		// can be properly evaluated.
+		$wgContLang->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',
+		) );
 	}
 	
 	public function tearDown() {
-		global $wgLang, $wgLanguageCode;
+		global $wgLang, $wgContLang, $wgLanguageCode;
+
+		$wgContLang->setNamespaces( self::$oldNamespaces );
 		$wgLang = self::$oldLang;
-		$wgLanguageCode = $wgLang->getCode();
+		$wgContLang = self::$oldContLang;
+		$wgLanguageCode = self::$oldLanguageCode;
 	}
 
 	public function testExpandAttributesSkipsNullAndFalse() {
 		
 		### EMPTY ########
 		$this->AssertEmpty(
-			Html::expandAttributes( array( 'foo'=>null) ),
+			Html::expandAttributes( array( 'foo' => null ) ),
 			'skip keys with null value'
 		);
 		$this->AssertEmpty(
-			Html::expandAttributes( array( 'foo'=>false) ),
+			Html::expandAttributes( array( 'foo' => false ) ),
 			'skip keys with false value'
 		);
 		$this->AssertNotEmpty(
-			Html::expandAttributes( array( 'foo'=>'') ),
+			Html::expandAttributes( array( 'foo' => '' ) ),
 			'keep keys with an empty string'
 		);
 	}
 
 	public function testExpandAttributesForBooleans() {
+		global $wgHtml5;
 		$this->AssertEquals(
 			'',
-			Html::expandAttributes( array( 'selected'=>false) ),
+			Html::expandAttributes( array( 'selected' => false ) ),
 			'Boolean attributes do not generates output when value is false'
 		);
 		$this->AssertEquals(
 			'',
-			Html::expandAttributes( array( 'selected'=>null) ),
+			Html::expandAttributes( array( 'selected' => null ) ),
 			'Boolean attributes do not generates output when value is null'
 		);
 
-		### FIXME: maybe they should just output 'selected'
 		$this->AssertEquals(
-			' selected=""',
-			Html::expandAttributes( array( 'selected'=>true ) ),
+			$wgHtml5 ? ' selected=""' : ' selected="selected"',
+			Html::expandAttributes( array( 'selected' => true ) ),
 			'Boolean attributes skip value output'
 		);
 		$this->AssertEquals(
-			' selected=""',
+			$wgHtml5 ? ' selected=""' : ' selected="selected"',
 			Html::expandAttributes( array( 'selected' ) ),
 			'Boolean attributes (ex: selected) do not need a value'
 		);
@@ -68,23 +100,234 @@ class HtmlTest extends MediaWikiTestCase {
 		### NOT EMPTY ####
 		$this->AssertEquals(
 			' empty_string=""',
-			Html::expandAttributes( array( 'empty_string'=>'') ),
+			Html::expandAttributes( array( 'empty_string' => '' ) ),
 			'Value with an empty string'
 		);
 		$this->AssertEquals(
 			' key="value"',
-			Html::expandAttributes( array( 'key'=>'value') ),
+			Html::expandAttributes( array( 'key' => 'value' ) ),
 			'Value is a string'
 		);
 		$this->AssertEquals(
 			' one="1"',
-			Html::expandAttributes( array( 'one'=>1) ),
+			Html::expandAttributes( array( 'one' => 1 ) ),
 			'Value is a numeric one'
 		);
 		$this->AssertEquals(
 			' zero="0"',
-			Html::expandAttributes( array( 'zero'=>0) ),
+			Html::expandAttributes( array( 'zero' => 0 ) ),
 			'Value is a numeric zero'
 		);
 	}
+
+	/**
+	 * Html::expandAttributes has special features for HTML
+	 * attributes that use space separated lists and also
+	 * allows arrays to be used as values.
+	 */
+	public function testExpandAttributesListValueAttributes() {
+		### STRING VALUES
+		$this->AssertEquals(
+			' class="redundant spaces here"',
+			Html::expandAttributes( array( 'class' => ' redundant  spaces  here  ' ) ),
+			'Normalization should strip redundant spaces'
+		);
+		$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(
+			' class=""',
+			Html::expandAttributes( array( 'class' => array() ) ),
+			'Value with an empty array'
+		);
+		$this->AssertEquals(
+			' class=""',
+			Html::expandAttributes( array( 'class' => array( null, '', ' ', '  ' ) ) ),
+			'Array with null, empty string and spaces'
+		);
+		### NON-EMPTY ARRAY VALUES
+		$this->AssertEquals(
+			' class="foo bar"',
+			Html::expandAttributes( array( 'class' => array(
+				'foo',
+				'bar',
+				'foo',
+				'bar',
+				'bar',
+			) ) ),
+			'Normalization should remove duplicates in the array'
+		);
+		$this->AssertEquals(
+			' class="foo bar"',
+			Html::expandAttributes( array( 'class' => array(
+				'foo bar',
+				'bar foo',
+				'foo',
+				'bar bar',
+			) ) ),
+			'Normalization should remove duplicates in string-lists in the array'
+		);
+	}
+
+	/**
+	 * Test feature added by r96188, let pass attributes values as
+	 * a PHP array. Restricted to class,rel, accesskey.
+	 */
+	function testExpandAttributesSpaceSeparatedAttributesWithBoolean() {
+		$this->assertEquals(
+			' class="booltrue one"',
+			Html::expandAttributes( array( 'class' => array(
+				'booltrue' => true,
+				'one' => 1,
+
+				# Method use isset() internally, make sure we do discard
+			    # attributes values which have been assigned well known values
+				'emptystring' => '',
+				'boolfalse' => false,
+				'zero' => 0,
+				'null' => null,
+			)))
+		);
+	}
+
+	/**
+	 * How do we handle duplicate keys in HTML attributes expansion?
+	 * We could pass a "class" the values: 'GREEN' and array( 'GREEN' => false )
+	 * The later will take precedence.
+	 *
+	 * Feature added by r96188
+	 */
+	function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
+		$this->assertEquals(
+			' class=""',
+			Html::expandAttributes( array( 'class' => array(
+				'GREEN',
+				'GREEN' => false,
+				'GREEN',
+			)))
+		);
+	}
+
+	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="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>&#160;' .
+'<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="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' )
+			),
+			'Basic namespace selector with custom values'
+		);
+	}
+
+	function testNamespaceSelectorIdAndNameDefaultsAttributes() {
+
+		$this->assertNsSelectorIdAndName(
+			'namespace', 'namespace',
+			Html::namespaceSelector( array(), array(
+				# neither 'id' nor 'name' key given
+			)),
+			"Neither 'id' nor 'name' key given"
+		);
+
+		$this->assertNsSelectorIdAndName(
+			'namespace', 'select_name',
+			Html::namespaceSelector( array(), array(
+				'name' => 'select_name',
+				# no 'id' key given
+			)),
+			"No 'id' key given, 'name' given"
+		);
+
+		$this->assertNsSelectorIdAndName(
+			'select_id', 'namespace',
+			Html::namespaceSelector( array(), array(
+				'id' => 'select_id',
+				# no 'name' key given
+			)),
+			"'id' given, no 'name' key given"
+		);
+
+		$this->assertNsSelectorIdAndName(
+			'select_id', 'select_name',
+			Html::namespaceSelector( array(), array(
+				'id'   => 'select_id',
+				'name' => 'select_name',
+			)),
+			"Both 'id' and 'name' given"
+		);
+	}
+
+	/**
+	 * Helper to verify <select> attributes generated by Html::namespaceSelector()
+	 * This helper expect the Html method to use 'namespace' as a default value for
+	 * both 'id' and 'name' attributes.
+	 *
+	 * @param String $expectedId <select> id attribute value
+	 * @param String $expectedName <select> name attribute value
+	 * @param String $html Output of a call to Html::namespaceSelector()
+	 * @param String $msg Optional message (default: '')
+	*/
+	function assertNsSelectorIdAndName( $expectedId, $expectedName, $html, $msg = '' ) {
+		$actualId = 'namespace';
+		if( 1 === preg_match( '/id="(.+?)"/', $html, $m ) ) {
+			$actualId = $m[1];
+		}
+
+		$actualName = 'namespace';
+		if( 1 === preg_match( '/name="(.+?)"/', $html, $m ) ) {
+			$actualName = $m[1];
+		}
+		$this->assertEquals(
+			array( #expected
+				'id'   => $expectedId,
+				'name' => $expectedName,
+			),
+			array( #actual
+				'id'   => $actualId,
+				'name' => $actualName,
+			),
+			'Html::namespaceSelector() got wrong id and/or name attribute(s). ' . $msg
+		);
+	}
+
 }
diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php
index 1a99af7d..263383f1 100644
--- a/tests/phpunit/includes/HttpTest.php
+++ b/tests/phpunit/includes/HttpTest.php
@@ -1,325 +1,12 @@
 <?php
-
-class MockCookie extends Cookie {
-	public function canServeDomain( $arg ) { return parent::canServeDomain( $arg ); }
-	public function canServePath( $arg ) { return parent::canServePath( $arg ); }
-	public function isUnExpired() { return parent::isUnExpired(); }
-}
-
 /**
  * @group Broken
  */
 class HttpTest extends MediaWikiTestCase {
-	static $content;
-	static $headers;
-	static $has_curl;
-	static $has_fopen;
-	static $has_proxy = false;
-	static $proxy = "http://hulk:8080/";
-	var $test_geturl = array(
-		"http://en.wikipedia.org/robots.txt",
-		"https://secure.wikimedia.org/",
-		"http://pecl.php.net/feeds/pkg_apc.rss",
-		"http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw",
-		"http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php",
-	);
-	var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" );
-
-	var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" );
-
-	function setUp() {
-		putenv( "http_proxy" ); /* Remove any proxy env var, so curl doesn't get confused */
-		if ( is_array( self::$content ) ) {
-			return;
-		}
-		self::$has_curl = function_exists( 'curl_init' );
-		self::$has_fopen = wfIniGetBool( 'allow_url_fopen' );
-
-		if ( !file_exists( "/usr/bin/curl" ) ) {
-			$this->markTestIncomplete( "This test requires the curl binary at /usr/bin/curl.	 If you have curl, please file a bug on this test, or, better yet, provide a patch." );
-		}
-
-		$content = tempnam( wfTempDir(), "" );
-		$headers = tempnam( wfTempDir(), "" );
-		if ( !$content && !$headers ) {
-			die( "Couldn't create temp file!" );
-		}
-
-		// This probably isn't the best test for a proxy, but it works on my system!
-		system( "curl -0 -o $content -s " . self::$proxy );
-		$out = file_get_contents( $content );
-		if ( $out ) {
-			self::$has_proxy = true;
-		}
-
-		/* Maybe use wget instead of curl here ... just to use a different codebase? */
-		foreach ( $this->test_geturl as $u ) {
-			system( "curl -0 -s -D $headers '$u' -o $content" );
-			self::$content["GET $u"] = file_get_contents( $content );
-			self::$headers["GET $u"] = file_get_contents( $headers );
-		}
-		foreach ( $this->test_requesturl as $u ) {
-			system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" );
-			self::$content["POST $u"] = file_get_contents( $content );
-			self::$headers["POST $u"] = file_get_contents( $headers );
-		}
-		foreach ( $this->test_posturl as $u => $postData ) {
-			system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" );
-			self::$content["POST $u => $postData"] = file_get_contents( $content );
-			self::$headers["POST $u => $postData"] = file_get_contents( $headers );
-		}
-		unlink( $content );
-		unlink( $headers );
-	}
-
-
-	function testInstantiation() {
-		Http::$httpEngine = false;
-
-		$r = MWHttpRequest::factory( "http://www.example.com/" );
-		if ( self::$has_curl ) {
-			$this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
-		} else {
-			$this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
-		}
-		unset( $r );
-
-		if ( !self::$has_fopen ) {
-			$this->setExpectedException( 'MWException' );
-		}
-		Http::$httpEngine = 'php';
-		$r = MWHttpRequest::factory( "http://www.example.com/" );
-		$this->assertThat( $r, $this->isInstanceOf( 'PhpHttpRequest' ) );
-		unset( $r );
-
-		if ( !self::$has_curl ) {
-			$this->setExpectedException( 'MWException' );
-		}
-		Http::$httpEngine = 'curl';
-		$r = MWHttpRequest::factory( "http://www.example.com/" );
-		if ( self::$has_curl ) {
-			$this->assertThat( $r, $this->isInstanceOf( 'CurlHttpRequest' ) );
-		}
-	}
-
-	function runHTTPFailureChecks() {
-		// Each of the following requests should result in a failure.
-
-		$timeout = 1;
-		$start_time = time();
-		$r = Http::get( "http://www.example.com:1/", $timeout );
-		$end_time = time();
-		$this->assertLessThan( $timeout + 2, $end_time - $start_time,
-							  "Request took less than {$timeout}s via " . Http::$httpEngine );
-		$this->assertEquals( $r, false, "false -- what we get on error from Http::get()" );
-
-		$r = Http::get( "http://www.mediawiki.org/xml/made-up-url", $timeout );
-		$this->assertFalse( $r, "False on 404s" );
-
-
-		$r = MWHttpRequest::factory( "http://www.mediawiki.org/xml/made-up-url" );
-		$er = $r->execute();
-		if ( $r instanceof PhpHttpRequest && version_compare( '5.2.10', phpversion(), '>' ) ) {
-			$this->assertRegexp( "/HTTP request failed/", $er->getWikiText() );
-		} else {
-			$this->assertRegexp( "/404 Not Found/", $er->getWikiText() );
-		}
-	}
-
-	function testFailureDefault() {
-		Http::$httpEngine = false;
-		$this->runHTTPFailureChecks();
-	}
-
-	function testFailurePhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = "php";
-		$this->runHTTPFailureChecks();
-	}
-
-	function testFailureCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = "curl";
-		$this->runHTTPFailureChecks();
-	}
-
-	/* ./phase3/includes/Import.php:1108:		$data = Http::request( $method, $url ); */
-	/* ./includes/Import.php:1124:			$link = Title::newFromText( "$interwiki:Special:Export/$page" ); */
-	/* ./includes/Import.php:1134:			return ImportStreamSource::newFromURL( $url, "POST" ); */
-	function runHTTPRequests( $proxy = null ) {
-		$opt = array();
-
-		if ( $proxy ) {
-			$opt['proxy'] = $proxy;
-		} elseif ( $proxy === false ) {
-			$opt['noProxy'] = true;
-		}
-
-		/* no postData here because the only request I could find in code so far didn't have any */
-		foreach ( $this->test_requesturl as $u ) {
-			$r = Http::request( "POST", $u, $opt );
-			$this->assertEquals( self::$content["POST $u"], "$r", "POST $u with " . Http::$httpEngine );
-		}
-	}
-
-	function testRequestDefault() {
-		Http::$httpEngine = false;
-		$this->runHTTPRequests();
-	}
-
-	function testRequestPhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = "php";
-		$this->runHTTPRequests();
-	}
-
-	function testRequestCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = "curl";
-		$this->runHTTPRequests();
-	}
-
-	function runHTTPGets( $proxy = null ) {
-		$opt = array();
-
-		if ( $proxy ) {
-			$opt['proxy'] = $proxy;
-		} elseif ( $proxy === false ) {
-			$opt['noProxy'] = true;
-		}
-
-		foreach ( $this->test_geturl as $u ) {
-			$r = Http::get( $u, 30, $opt ); /* timeout of 30s */
-			$this->assertEquals( self::$content["GET $u"], "$r", "Get $u with " . Http::$httpEngine );
-		}
-	}
-
-	function testGetDefault() {
-		Http::$httpEngine = false;
-		$this->runHTTPGets();
-	}
-
-	function testGetPhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = "php";
-		$this->runHTTPGets();
-	}
-
-	function testGetCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = "curl";
-		$this->runHTTPGets();
-	}
-
-	function runHTTPPosts( $proxy = null ) {
-		$opt = array();
-
-		if ( $proxy ) {
-			$opt['proxy'] = $proxy;
-		} elseif ( $proxy === false ) {
-			$opt['noProxy'] = true;
-		}
-
-		foreach ( $this->test_posturl as $u => $postData ) {
-			$opt['postData'] = $postData;
-			$r = Http::post( $u, $opt );
-			$this->assertEquals( self::$content["POST $u => $postData"], "$r",
-								 "POST $u (postData=$postData) with " . Http::$httpEngine );
-		}
-	}
-
-	function testPostDefault() {
-		Http::$httpEngine = false;
-		$this->runHTTPPosts();
-	}
-
-	function testPostPhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = "php";
-		$this->runHTTPPosts();
-	}
-
-	function testPostCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = "curl";
-		$this->runHTTPPosts();
-	}
-
-	function runProxyRequests() {
-		if ( !self::$has_proxy ) {
-			$this->markTestIncomplete( "This test requires a proxy." );
-		}
-		$this->runHTTPGets( self::$proxy );
-		$this->runHTTPPosts( self::$proxy );
-		$this->runHTTPRequests( self::$proxy );
-
-		// Set false here to do noProxy
-		$this->runHTTPGets( false );
-		$this->runHTTPPosts( false );
-		$this->runHTTPRequests( false );
-	}
-
-	function testProxyDefault() {
-		Http::$httpEngine = false;
-		$this->runProxyRequests();
-	}
-
-	function testProxyPhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = 'php';
-		$this->runProxyRequests();
-	}
-
-	function testProxyCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = 'curl';
-		$this->runProxyRequests();
-	}
-
-	function testIsLocalUrl() {
-	}
-
-	/* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559:		$user_agent = Http::userAgent(); */
-	function testUserAgent() {
-	}
-
-	function testIsValidUrl() {
-	}
-
 	/**
 	 * @dataProvider cookieDomains
 	 */
-	function testValidateCookieDomain( $expected, $domain, $origin=null ) {
+	function testValidateCookieDomain( $expected, $domain, $origin = null ) {
 		if ( $origin ) {
 			$ok = Cookie::validateCookieDomain( $domain, $origin );
 			$msg = "$domain against origin $origin";
@@ -329,7 +16,7 @@ class HttpTest extends MediaWikiTestCase {
 		}
 		$this->assertEquals( $expected, $ok, $msg );
 	}
-	
+
 	function cookieDomains() {
 		return array(
 			array( false, "org"),
@@ -359,189 +46,11 @@ class HttpTest extends MediaWikiTestCase {
 		);
 	}
 
-	function testSetCooke() {
-		$c = new MockCookie( "name", "value",
-							 array(
-								 "domain" => "ac.th",
-								 "path" => "/path/",
-							 ) );
-		$this->assertFalse( $c->canServeDomain( "ac.th" ) );
-
-		$c = new MockCookie( "name", "value",
-							 array(
-								 "domain" => "example.com",
-								 "path" => "/path/",
-							 ) );
-
-		$this->assertTrue( $c->canServeDomain( "example.com" ) );
-		$this->assertFalse( $c->canServeDomain( "www.example.com" ) );
-
-		$c = new MockCookie( "name", "value",
-							 array(
-								 "domain" => ".example.com",
-								 "path" => "/path/",
-							 ) );
-
-		$this->assertFalse( $c->canServeDomain( "www.example.net" ) );
-		$this->assertFalse( $c->canServeDomain( "example.com" ) );
-		$this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
-		$this->assertFalse( $c->canServePath( "/" ) );
-		$this->assertFalse( $c->canServePath( "/bogus/path/" ) );
-		$this->assertFalse( $c->canServePath( "/path" ) );
-		$this->assertTrue( $c->canServePath( "/path/" ) );
-
-		$this->assertTrue( $c->isUnExpired() );
-
-		$this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.net" ) );
-		$this->assertEquals( "", $c->serializeToHttpRequest( "/", "www.example.com" ) );
-		$this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
-		$c = new MockCookie( "name", "value",
-							 array(
-								 "domain" => "www.example.com",
-								 "path" => "/path/",
-							 ) );
-		$this->assertFalse( $c->canServeDomain( "example.com" ) );
-		$this->assertFalse( $c->canServeDomain( "www.example.net" ) );
-		$this->assertTrue( $c->canServeDomain( "www.example.com" ) );
-
-		$c = new MockCookie( "name", "value",
-						 array(
-							 "domain" => ".example.com",
-							 "path" => "/path/",
-							 "expires" => "-1 day",
-						 ) );
-		$this->assertFalse( $c->isUnExpired() );
-		$this->assertEquals( "", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
-		$c = new MockCookie( "name", "value",
-						 array(
-							 "domain" => ".example.com",
-							 "path" => "/path/",
-							 "expires" => "+1 day",
-						 ) );
-		$this->assertTrue( $c->isUnExpired() );
-		$this->assertEquals( "name=value", $c->serializeToHttpRequest( "/path/", "www.example.com" ) );
-	}
-
-	function testCookieJarSetCookie() {
-		$cj = new CookieJar;
-		$cj->setCookie( "name", "value",
-						 array(
-							 "domain" => ".example.com",
-							 "path" => "/path/",
-						 ) );
-		$cj->setCookie( "name2", "value",
-						 array(
-							 "domain" => ".example.com",
-							 "path" => "/path/sub",
-						 ) );
-		$cj->setCookie( "name3", "value",
-						 array(
-							 "domain" => ".example.com",
-							 "path" => "/",
-						 ) );
-		$cj->setCookie( "name4", "value",
-						 array(
-							 "domain" => ".example.net",
-							 "path" => "/path/",
-						 ) );
-		$cj->setCookie( "name5", "value",
-						 array(
-							 "domain" => ".example.net",
-							 "path" => "/path/",
-							 "expires" => "-1 day",
-						 ) );
-
-		$this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-		$this->assertEquals( "name3=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
-		$this->assertEquals( "name=value; name3=value", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
-		$cj->setCookie( "name5", "value",
-						 array(
-							 "domain" => ".example.net",
-							 "path" => "/path/",
-							 "expires" => "+1 day",
-						 ) );
-		$this->assertEquals( "name4=value; name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
-		$cj->setCookie( "name4", "value",
-						 array(
-							 "domain" => ".example.net",
-							 "path" => "/path/",
-							 "expires" => "-1 day",
-						 ) );
-		$this->assertEquals( "name5=value", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-	}
-
-	function testParseResponseHeader() {
-		$cj = new CookieJar;
-
-		$h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[0], "www.example.com" );
-		$this->assertEquals( "name4=value", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
-
-		$h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[1], "www.example.com" );
-		$this->assertEquals( "", $cj->serializeToHttpRequest( "/", "www.example.com" ) );
-		$this->assertEquals( "name4=value2", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
-		$h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[2], "www.example.com" );
-		$this->assertEquals( "name4=value2; name5=value3", $cj->serializeToHttpRequest( "/path/", "www.example.com" ) );
-
-		$h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[3], "www.example.com" );
-		$this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
-		$h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[4], "www.example.net" );
-		$this->assertEquals( "", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-
-		$h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT";
-		$cj->parseCookieResponseHeader( $h[5], "www.example.net" );
-		$this->assertEquals( "name6=value4", $cj->serializeToHttpRequest( "/path/", "www.example.net" ) );
-	}
-
-	function runCookieRequests() {
-		$r = MWHttpRequest::factory( "http://www.php.net/manual", array( 'followRedirects' => true ) );
-		$r->execute();
-
-		$jar = $r->getCookieJar();
-		$this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) );
-
-		$serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" );
-		$this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized );
-		$this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized );
-		$this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) );
-	}
-
-	function testCookieRequestDefault() {
-		Http::$httpEngine = false;
-		$this->runCookieRequests();
-	}
-	function testCookieRequestPhp() {
-		if ( !self::$has_fopen ) {
-			$this->markTestIncomplete( "This test requires allow_url_fopen=true." );
-		}
-
-		Http::$httpEngine = 'php';
-		$this->runCookieRequests();
-	}
-	function testCookieRequestCurl() {
-		if ( !self::$has_curl ) {
-			$this->markTestIncomplete( "This test requires curl." );
-		}
-
-		Http::$httpEngine = 'curl';
-		$this->runCookieRequests();
-	}
-
 	/**
 	 * Test Http::isValidURI()
-	 * @bug 27854 : Http::isValidURI is to lax
-	 *@dataProvider provideURI */
+	 * @bug 27854 : Http::isValidURI is too lax
+	 * @dataProvider provideURI
+	 */
 	function testIsValidUri( $expect, $URI, $message = '' ) {
 		$this->assertEquals(
 			$expect,
@@ -567,7 +76,7 @@ class HttpTest extends MediaWikiTestCase {
 			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' ),
@@ -615,4 +124,57 @@ 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' );
+		# Forge a Location header
+		$h->setRespHeaders( 'location', array(
+			'http://newsite/file.ext',
+			'/newfile.ext',
+			)
+		);
+		# Verify we correctly fix the Location
+		$this->assertEquals(
+			'http://newsite/newfile.ext',
+			$h->getFinalUrl(),
+			"Relative file path Location: interpreted as full URL"
+		);
+
+		$h->setRespHeaders( 'location', array(
+			'https://oldsite/file.ext'
+			)
+		);
+		$this->assertEquals(
+			'https://oldsite/file.ext',
+			$h->getFinalUrl(),
+			"Location to the HTTPS version of the site"
+		);
+
+		$h->setRespHeaders( 'location', array(
+			'/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!")
+		);
+	}
+}
+
+/**
+ * Class to let us overwrite MWHttpREquest respHeaders variable
+ */
+class MWHttpRequestTester extends MWHttpRequest {
+	function setRespHeaders( $name, $value ) {
+		$this->respHeaders[$name] = $value ;
+	}
 }
diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php
index c77dd852..4397b879 100644
--- a/tests/phpunit/includes/IPTest.php
+++ b/tests/phpunit/includes/IPTest.php
@@ -1,5 +1,5 @@
 <?php
-/*
+/**
  * Tests for IP validity functions. Ported from /t/inc/IP.t by avar.
  */
 
@@ -43,20 +43,20 @@ class IPTest extends MediaWikiTestCase {
 		$this->assertFalse( IP::isIPv6( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
 		$this->assertFalse( IP::isIPv6( 'fc:300' ), 'IPv6 with only 2 words' );
 		$this->assertFalse( IP::isIPv6( 'fc:100:300' ), 'IPv6 with only 3 words' );
-		
+
 		$this->assertTrue( IP::isIPv6( 'fc:100::' ) );
 		$this->assertTrue( IP::isIPv6( 'fc:100:a::' ) );
 		$this->assertTrue( IP::isIPv6( 'fc:100:a:d::' ) );
 		$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1::' ) );
 		$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e::' ) );
 		$this->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac::' ) );
-		
+
 		$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
 		$this->assertFalse( IP::isIPv6( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
 
 		$this->assertFalse( IP::isIPv6( ':::' ) );
 		$this->assertFalse( IP::isIPv6( '::0:' ), 'IPv6 ending in a lone ":"' );
-		
+
 		$this->assertTrue( IP::isIPv6( '::' ), 'IPv6 zero address' );
 		$this->assertTrue( IP::isIPv6( '::0' ) );
 		$this->assertTrue( IP::isIPv6( '::fc' ) );
@@ -66,14 +66,14 @@ class IPTest extends MediaWikiTestCase {
 		$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1' ) );
 		$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e' ) );
 		$this->assertTrue( IP::isIPv6( '::fc:100:a:d:1:e:ac' ) );
-		
+
 		$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' );
 
 		$this->assertFalse( IP::isIPv6( ':fc::100' ), 'IPv6 starting with lone ":"' );
 		$this->assertFalse( IP::isIPv6( 'fc::100:' ), 'IPv6 ending with lone ":"' );
 		$this->assertFalse( IP::isIPv6( 'fc:::100' ), 'IPv6 with ":::" in the middle' );
-		
+
 		$this->assertTrue( IP::isIPv6( 'fc::100' ), 'IPv6 with "::" and 2 words' );
 		$this->assertTrue( IP::isIPv6( 'fc::100:a' ), 'IPv6 with "::" and 3 words' );
 		$this->assertTrue( IP::isIPv6( 'fc::100:a:d', 'IPv6 with "::" and 4 words' ) );
@@ -83,7 +83,7 @@ class IPTest extends MediaWikiTestCase {
 		$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' );
 
@@ -135,11 +135,11 @@ class IPTest extends MediaWikiTestCase {
 		$this->assertFalse( IP::isValid( 'fc:100:::' ), 'IPv6 ending with a ":::"' );
 		$this->assertFalse( IP::isValid( 'fc:300' ), 'IPv6 with only 2 words' );
 		$this->assertFalse( IP::isValid( 'fc:100:300' ), 'IPv6 with only 3 words' );
-		
+
 		$this->assertTrue( IP::isValid( 'fc:100::' ) );
 		$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e::' ) );
 		$this->assertTrue( IP::isValid( 'fc:100:a:d:1:e:ac::' ) );
-		
+
 		$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' );
@@ -147,7 +147,7 @@ class IPTest extends MediaWikiTestCase {
 		$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' );
-		
+
 		$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0::' ), 'IPv6 with 8 words ending with "::"' );
 		$this->assertFalse( IP::isValid( 'fc:100:a:d:1:e:ac:0:1::' ), 'IPv6 with 9 words ending with "::"' );
 	}
@@ -276,7 +276,7 @@ class IPTest extends MediaWikiTestCase {
 		foreach ( $private as $p ) {
 			$this->assertFalse( IP::isPublic( $p ), "$p is not a public IP address" );
 		}
-		$public = array( '2001:5c0:1000:a::133', 'fc::3' );
+		$public = array( '2001:5c0:1000:a::133', 'fc::3', '00FC::' );
 		foreach ( $public as $p ) {
 			$this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" );
 		}
@@ -332,7 +332,7 @@ class IPTest extends MediaWikiTestCase {
 		$this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF', IP::hexToOctet( 'FCCFFAFF' ) );
 	}
 
-	/*
+	/**
 	 * IP::parseCIDR() returns an array containing a signed IP address
 	 * representing the network mask and the bit mask.
 	 * @covers IP::parseCIDR
@@ -391,7 +391,7 @@ class IPTest extends MediaWikiTestCase {
 	}
 
 	/**
-	 * Issues there are most probably from IP::toHex() or IP::parseRange()		
+	 * Issues there are most probably from IP::toHex() or IP::parseRange()
 	 * @covers IP::isInRange
 	 * @dataProvider provideIPsAndRanges
 	 */
@@ -464,9 +464,9 @@ class IPTest extends MediaWikiTestCase {
 	 */
 	function testCombineHostAndPort( $expected, $input, $description ) {
 		list( $host, $port, $defaultPort ) = $input;
-		$this->assertEquals( 
-			$expected, 
-			IP::combineHostAndPort( $host, $port, $defaultPort ), 
+		$this->assertEquals(
+			$expected,
+			IP::combineHostAndPort( $host, $port, $defaultPort ),
 			$description );
 	}
 
diff --git a/tests/phpunit/includes/ImageFunctionsTest.php b/tests/phpunit/includes/ImageFunctionsTest.php
deleted file mode 100644
index cb7e67f3..00000000
--- a/tests/phpunit/includes/ImageFunctionsTest.php
+++ /dev/null
@@ -1,48 +0,0 @@
-<?php
-
-class ImageFunctionsTest extends MediaWikiTestCase {
-	function testFitBoxWidth() {
-		$vals = array(
-			array(
-				'width' => 50,
-				'height' => 50,
-				'tests' => array(
-					50 => 50,
-					17 => 17,
-					18 => 18 ) ),
-			array(
-				'width' => 366,
-				'height' => 300,
-				'tests' => array(
-					50 => 61,
-					17 => 21,
-					18 => 22 ) ),
-			array(
-				'width' => 300,
-				'height' => 366,
-				'tests' => array(
-					50 => 41,
-					17 => 14,
-					18 => 15 ) ),
-			array(
-				'width' => 100,
-				'height' => 400,
-				'tests' => array(
-					50 => 12,
-					17 => 4,
-					18 => 4 ) ) );
-		foreach ( $vals as $row ) {
-			extract( $row );
-			foreach ( $tests as $max => $expected ) {
-				$y = round( $expected * $height / $width );
-				$result = wfFitBoxWidth( $width, $height, $max );
-				$y2 = round( $result * $height / $width );
-				$this->assertEquals( $expected,
-					$result,
-					"($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
-			}
-		}
-	}
-}
-
-
diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php
index e08d4d7e..5b26b89c 100644
--- a/tests/phpunit/includes/LocalFileTest.php
+++ b/tests/phpunit/includes/LocalFileTest.php
@@ -10,12 +10,21 @@ class LocalFileTest extends MediaWikiTestCase {
 		global $wgCapitalLinks;
 
 		$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',
+				'lockManager' => 'fsLockManager',
+				'containerPaths' => array(
+					'cont1' => "/testdir/local-backend/tempimages/cont1",
+					'cont2' => "/testdir/local-backend/tempimages/cont2"
+				)
+			) )
 		);
 		$this->repo_hl0 = new LocalRepo( array( 'hashLevels' => 0 ) + $info );
 		$this->repo_hl2 = new LocalRepo( array( 'hashLevels' => 2 ) + $info );
@@ -44,17 +53,17 @@ class LocalFileTest extends MediaWikiTestCase {
 	}
 
 	function testGetArchivePath() {
-		$this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() );
-		$this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() );
-		$this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) );
-		$this->assertEquals( '/testdir/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() );
+		$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() {
-		$this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() );
-		$this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() );
-		$this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) );
-		$this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) );
+		$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() {
diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php
index 462afc24..6b231fc5 100644
--- a/tests/phpunit/includes/MWNamespaceTest.php
+++ b/tests/phpunit/includes/MWNamespaceTest.php
@@ -1,7 +1,7 @@
 <?php
 /**
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
  * @file
  */
 
@@ -39,40 +39,55 @@ class MWNamespaceTest extends MediaWikiTestCase {
 	/**
 	 * Please make sure to change testIsTalk() if you change the assertions below
 	 */
-	public function testIsMain() {
+	public function testIsSubject() {
 		// Special namespaces
-		$this->assertTrue( MWNamespace::isMain( NS_MEDIA   ) );
-		$this->assertTrue( MWNamespace::isMain( NS_SPECIAL ) );
+		$this->assertIsSubject( NS_MEDIA   );
+		$this->assertIsSubject( NS_SPECIAL );
 
 		// Subject pages
-		$this->assertTrue( MWNamespace::isMain( NS_MAIN   ) );
-		$this->assertTrue( MWNamespace::isMain( NS_USER   ) );
-		$this->assertTrue( MWNamespace::isMain( 100 ) );  # user defined
+		$this->assertIsSubject( NS_MAIN );
+		$this->assertIsSubject( NS_USER );
+		$this->assertIsSubject( 100     );  # user defined
 
 		// Talk pages
-		$this->assertFalse( MWNamespace::isMain( NS_TALK      ) );
-		$this->assertFalse( MWNamespace::isMain( NS_USER_TALK ) );
-		$this->assertFalse( MWNamespace::isMain( 101          ) ); # user defined
+		$this->assertIsNotSubject( NS_TALK      );
+		$this->assertIsNotSubject( NS_USER_TALK );
+		$this->assertIsNotSubject( 101          ); # user defined
+
+		// Back compat
+		$this->assertTrue( MWNamespace::isMain( NS_MAIN ) == MWNamespace::isSubject( NS_MAIN ) );
+		$this->assertTrue( MWNamespace::isMain( NS_USER_TALK ) == MWNamespace::isSubject( NS_USER_TALK ) );
 	}
 
 	/**
-	 * Reverse of testIsMain().
-	 * Please update testIsMain() if you change assertions below
+	 * Reverse of testIsSubject().
+	 * Please update testIsSubject() if you change assertions below
 	 */
 	public function testIsTalk() {
 		// Special namespaces
-		$this->assertFalse( MWNamespace::isTalk( NS_MEDIA   ) );
-		$this->assertFalse( MWNamespace::isTalk( NS_SPECIAL ) );
+		$this->assertIsNotTalk( NS_MEDIA   );
+		$this->assertIsNotTalk( NS_SPECIAL );
 
 		// Subject pages
-		$this->assertFalse( MWNamespace::isTalk( NS_MAIN   ) );
-		$this->assertFalse( MWNamespace::isTalk( NS_USER   ) );
-		$this->assertFalse( MWNamespace::isTalk( 100 ) );  # user defined
+		$this->assertIsNotTalk( NS_MAIN   );
+		$this->assertIsNotTalk( NS_USER   );
+		$this->assertIsNotTalk( 100       );  # user defined
 
 		// Talk pages
-		$this->assertTrue( MWNamespace::isTalk( NS_TALK      ) );
-		$this->assertTrue( MWNamespace::isTalk( NS_USER_TALK ) );
-		$this->assertTrue( MWNamespace::isTalk( 101          ) ); # user defined
+		$this->assertIsTalk( NS_TALK      );
+		$this->assertIsTalk( NS_USER_TALK );
+		$this->assertIsTalk( 101          ); # user defined
+	}
+
+	/**
+	 */
+	public function testGetSubject() {
+		// Special namespaces are their own subjects
+		$this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
+		$this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
+
+		$this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
+		$this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
 	}
 
 	/**
@@ -82,6 +97,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
 	 */
 	public function testGetTalk() {
 		$this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_MAIN ) );
+		$this->assertEquals( NS_TALK, MWNamespace::getTalk( NS_TALK ) );
+		$this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER ) );
+		$this->assertEquals( NS_USER_TALK, MWNamespace::getTalk( NS_USER_TALK ) );
 	}
 
 	/**
@@ -93,7 +111,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$this->assertNull( MWNamespace::getTalk( NS_MEDIA ) );
 	}
 
-		/**
+	/**
 	 * Exceptions with getTalk()
 	 * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them.
 	 * @expectedException MWException
@@ -108,7 +126,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
 	 * the function testGetAssociatedExceptions()
 	 */
 	public function testGetAssociated() {
-		$this->assertEquals( NS_TALK,  MWNamespace::getAssociated( NS_MAIN ) );
+		$this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
 		$this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) );
 
 	}
@@ -130,81 +148,123 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$this->assertNull( MWNamespace::getAssociated( NS_SPECIAL ) );
 	}
 
-	/**
-	 */
-	public function testGetSubject() {
-		// Special namespaces are their own subjects
-		$this->assertEquals( NS_MEDIA, MWNamespace::getSubject( NS_MEDIA ) );
-		$this->assertEquals( NS_SPECIAL, MWNamespace::getSubject( NS_SPECIAL ) );
-
-		$this->assertEquals( NS_MAIN, MWNamespace::getSubject( NS_TALK ) );
-		$this->assertEquals( NS_USER, MWNamespace::getSubject( NS_USER_TALK ) );
-	}
-
 	/**
 	 * @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
+	 * Note if we add a namespace registration system with keys like 'MAIN'
+	 * we should add tests here for equivilance on things like 'MAIN' == 0
+	 * and 'MAIN' == NS_MAIN.
+	 */
+	public function testEquals() {
+		$this->assertTrue( MWNamespace::equals( NS_MAIN, NS_MAIN ) );
+		$this->assertTrue( MWNamespace::equals( NS_MAIN, 0 ) ); // In case we make NS_MAIN 'MAIN'
+		$this->assertTrue( MWNamespace::equals( NS_USER, NS_USER ) );
+		$this->assertTrue( MWNamespace::equals( NS_USER, 2 ) );
+		$this->assertTrue( MWNamespace::equals( NS_USER_TALK, NS_USER_TALK ) );
+		$this->assertTrue( MWNamespace::equals( NS_SPECIAL, NS_SPECIAL ) );
+		$this->assertFalse( MWNamespace::equals( NS_MAIN, NS_TALK ) );
+		$this->assertFalse( MWNamespace::equals( NS_USER, NS_USER_TALK ) );
+		$this->assertFalse( MWNamespace::equals( NS_PROJECT, NS_TEMPLATE ) );
+	}
+
+	/**
+	 * Test MWNamespace::subjectEquals
+	 */
+	public function testSubjectEquals() {
+		$this->assertSameSubject( NS_MAIN, NS_MAIN );
+		$this->assertSameSubject( NS_MAIN, 0 ); // In case we make NS_MAIN 'MAIN'
+		$this->assertSameSubject( NS_USER, NS_USER );
+		$this->assertSameSubject( NS_USER, 2 );
+		$this->assertSameSubject( NS_USER_TALK, NS_USER_TALK );
+		$this->assertSameSubject( NS_SPECIAL, NS_SPECIAL );
+		$this->assertSameSubject( NS_MAIN, NS_TALK );
+		$this->assertSameSubject( NS_USER, NS_USER_TALK );
+
+		$this->assertDifferentSubject( NS_PROJECT, NS_TEMPLATE );
+		$this->assertDifferentSubject( NS_SPECIAL, NS_MAIN     );
+	}
+
+	public function testSpecialAndMediaAreDifferentSubjects() {
+		$this->assertDifferentSubject(
+			NS_MEDIA, NS_SPECIAL,
+			"NS_MEDIA and NS_SPECIAL are different subject namespaces"
+		);
+		$this->assertDifferentSubject(
+			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.'
 		);
 	}
-
+*/
 	/**
 	 * @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->assertFalse( MWNamespace::canTalk( NS_MEDIA   ) );
-		$this->assertFalse( MWNamespace::canTalk( NS_SPECIAL ) );
+		$this->assertCanNotTalk( NS_MEDIA   );
+		$this->assertCanNotTalk( NS_SPECIAL );
 
-		$this->assertTrue( MWNamespace::canTalk( NS_MAIN      ) );
-		$this->assertTrue( MWNamespace::canTalk( NS_TALK      ) );
-		$this->assertTrue( MWNamespace::canTalk( NS_USER      ) );
-		$this->assertTrue( MWNamespace::canTalk( NS_USER_TALK ) );
+		$this->assertCanTalk( NS_MAIN      );
+		$this->assertCanTalk( NS_TALK      );
+		$this->assertCanTalk( NS_USER      );
+		$this->assertCanTalk( NS_USER_TALK );
 
 		// User defined namespaces
-		$this->assertTrue( MWNamespace::canTalk( 100 ) );
-		$this->assertTrue( MWNamespace::canTalk( 101 ) );
+		$this->assertCanTalk( 100 );
+		$this->assertCanTalk( 101 );
 	}
 
 	/**
@@ -212,16 +272,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
 	public function testIsContent() {
 		// NS_MAIN is a content namespace per DefaultSettings.php
 		// and per function definition.
-		$this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+		$this->assertIsContent( NS_MAIN );
+
+		global $wgContentNamespaces;
+
+		$saved = $wgContentNamespaces;
+
+		$wgContentNamespaces[] = NS_MAIN;
+		$this->assertIsContent( NS_MAIN );
 
 		// Other namespaces which are not expected to be content
-		$this->assertFalse( MWNamespace::isContent( NS_MEDIA    ) );
-		$this->assertFalse( MWNamespace::isContent( NS_SPECIAL  ) );
-		$this->assertFalse( MWNamespace::isContent( NS_TALK     ) );
-		$this->assertFalse( MWNamespace::isContent( NS_USER     ) );
-		$this->assertFalse( MWNamespace::isContent( NS_CATEGORY ) );
-		// User defined namespace:
-		$this->assertFalse( MWNamespace::isContent( 100 ) );
+		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_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;
 	}
 
 	/**
@@ -231,47 +322,47 @@ class MWNamespaceTest extends MediaWikiTestCase {
 	public function testIsContentWithAdditionsInWgContentNamespaces() {
 		// NS_MAIN is a content namespace per DefaultSettings.php
 		// and per function definition.
-		$this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+		$this->assertIsContent( NS_MAIN );
 
 		// Tests that user defined namespace #252 is not content:
-		$this->assertFalse( MWNamespace::isContent( 252 ) );
+		$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->assertTrue( MWNamespace::isContent( 252 ) );
+		$this->assertIsContent( 252 );
 
 		// Makes sure NS_MAIN was not impacted
-		$this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
+		$this->assertIsContent( NS_MAIN );
 
 		// Restore global
 		$wgContentNamespaces = $savedGlobal;
 
 		// Verify namespaces after global restauration
-		$this->assertTrue( MWNamespace::isContent( NS_MAIN ) );
-		$this->assertFalse( MWNamespace::isContent( 252 ) );
+		$this->assertIsContent( NS_MAIN  );
+		$this->assertIsNotContent( 252 );
 	}
 
 	public function testIsWatchable() {
 		// Specials namespaces are not watchable
-		$this->assertFalse( MWNamespace::isWatchable( NS_MEDIA   ) );
-		$this->assertFalse( MWNamespace::isWatchable( NS_SPECIAL ) );
+		$this->assertIsNotWatchable( NS_MEDIA   );
+		$this->assertIsNotWatchable( NS_SPECIAL );
 
 		// Core defined namespaces are watchables
-		$this->assertTrue( MWNamespace::isWatchable( NS_MAIN ) );
-		$this->assertTrue( MWNamespace::isWatchable( NS_TALK ) );
+		$this->assertIsWatchable( NS_MAIN );
+		$this->assertIsWatchable( NS_TALK );
 
 		// Additional, user defined namespaces are watchables
-		$this->assertTrue( MWNamespace::isWatchable( 100 ) );
-		$this->assertTrue( MWNamespace::isWatchable( 101 ) );
+		$this->assertIsWatchable( 100 );
+		$this->assertIsWatchable( 101 );
 	}
 
 	public function testHasSubpages() {
 		// Special namespaces:
-		$this->assertFalse( MWNamespace::hasSubpages( NS_MEDIA   ) );
-		$this->assertFalse( MWNamespace::hasSubpages( NS_SPECIAL ) );
+		$this->assertHasNotSubpages( NS_MEDIA   );
+		$this->assertHasNotSubpages( NS_SPECIAL );
 
 		// namespaces without subpages
 		# save up global
@@ -282,12 +373,12 @@ class MWNamespaceTest extends MediaWikiTestCase {
 			unset( $wgNamespacesWithSubpages[NS_MAIN] );
 		}
 
-		$this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+		$this->assertHasNotSubpages( NS_MAIN );
 
 		$wgNamespacesWithSubpages[NS_MAIN] = true;
-		$this->assertTrue( MWNamespace::hasSubpages( NS_MAIN ) );
+		$this->assertHasSubpages( NS_MAIN );
 		$wgNamespacesWithSubpages[NS_MAIN] = false;
-		$this->assertFalse( MWNamespace::hasSubpages( NS_MAIN ) );
+		$this->assertHasNotSubpages( NS_MAIN );
 
 		# restore global
 		if( $saved !== null ) {
@@ -295,9 +386,9 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		}
 
 		// Some namespaces with subpages
-		$this->assertTrue( MWNamespace::hasSubpages( NS_TALK      ) );
-		$this->assertTrue( MWNamespace::hasSubpages( NS_USER      ) );
-		$this->assertTrue( MWNamespace::hasSubpages( NS_USER_TALK ) );
+		$this->assertHasSubpages( NS_TALK      );
+		$this->assertHasSubpages( NS_USER      );
+		$this->assertHasSubpages( NS_USER_TALK );
 	}
 
 	/**
@@ -311,6 +402,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
 
 		global $wgContentNamespaces;
 
+		$saved = $wgContentNamespaces;
 		# test !is_array( $wgcontentNamespaces )
 		$wgContentNamespaces = '';
 		$this->assertEquals( NS_MAIN, MWNamespace::getcontentNamespaces() );
@@ -330,7 +422,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$this->assertEquals(
 			array( NS_MAIN, NS_USER, NS_CATEGORY ),
 			MWNamespace::getcontentNamespaces(),
-			'NS_MAIN is forced in wgContentNamespaces even if unwanted'
+			'NS_MAIN is forced in $wgContentNamespaces even if unwanted'
 		);
 
 		# test other cases, return $wgcontentNamespaces as is
@@ -346,6 +438,7 @@ class MWNamespaceTest extends MediaWikiTestCase {
 			MWNamespace::getcontentNamespaces()
 		);
 
+		$wgContentNamespaces = $saved;
 	}
 
 	/**
@@ -361,14 +454,14 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		);
 
 		// Boths are capitalized by default
-		$this->assertTrue( MWNamespace::isCapitalized( NS_MEDIA ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_FILE  ) );
+		$this->assertIsCapitalized( NS_MEDIA );
+		$this->assertIsCapitalized( NS_FILE  );
 
 		// Always capitalized namespaces
 		// @see MWNamespace::$alwaysCapitalizedNamespaces
-		$this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL   ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_USER      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+		$this->assertIsCapitalized( NS_SPECIAL   );
+		$this->assertIsCapitalized( NS_USER      );
+		$this->assertIsCapitalized( NS_MEDIAWIKI );
 	}
 
 	/**
@@ -389,17 +482,17 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$savedGlobal = $wgCapitalLinks;
 
 		$wgCapitalLinks = true;
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+		$this->assertIsCapitalized( NS_PROJECT      );
+		$this->assertIsCapitalized( NS_PROJECT_TALK );
 
 		$wgCapitalLinks = false;
 		// hardcoded namespaces (see above function) are still capitalized:
-		$this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL   ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_USER      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+		$this->assertIsCapitalized( NS_SPECIAL   );
+		$this->assertIsCapitalized( NS_USER      );
+		$this->assertIsCapitalized( NS_MEDIAWIKI );
 		// setting is correctly applied
-		$this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT   ) );
-		$this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+		$this->assertIsNotCapitalized( NS_PROJECT      );
+		$this->assertIsNotCapitalized( NS_PROJECT_TALK );
 
 		// reset global state:
 		$wgCapitalLinks = $savedGlobal;
@@ -417,28 +510,28 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$savedGlobal = $wgCapitalLinkOverrides;
 
 		// Test default settings
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT_TALK ) );
+		$this->assertIsCapitalized( NS_PROJECT      );
+		$this->assertIsCapitalized( NS_PROJECT_TALK );
 		// hardcoded namespaces (see above function) are capitalized:
-		$this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL   ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_USER      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+		$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_MEDIAWIKI] = false;
-		$this->assertTrue( MWNamespace::isCapitalized( NS_SPECIAL   ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_USER      ) );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_MEDIAWIKI ) );
+		$this->assertIsCapitalized( NS_SPECIAL   );
+		$this->assertIsCapitalized( NS_USER      );
+		$this->assertIsCapitalized( NS_MEDIAWIKI );
 
 		$wgCapitalLinkOverrides = $savedGlobal;
 		$wgCapitalLinkOverrides[NS_PROJECT] = false;
-		$this->assertFalse( MWNamespace::isCapitalized( NS_PROJECT ) );
+		$this->assertIsNotCapitalized( NS_PROJECT );
 		$wgCapitalLinkOverrides[NS_PROJECT] = true ;
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+		$this->assertIsCapitalized( NS_PROJECT );
 		unset(  $wgCapitalLinkOverrides[NS_PROJECT] );
-		$this->assertTrue( MWNamespace::isCapitalized( NS_PROJECT ) );
+		$this->assertIsCapitalized( NS_PROJECT );
 
 		// reset global state:
 		$wgCapitalLinkOverrides = $savedGlobal;
@@ -456,5 +549,45 @@ class MWNamespaceTest extends MediaWikiTestCase {
 		$this->assertFalse( MWNamespace::hasGenderDistinction( NS_TALK    ) );
 
 	}
+
+	####### HELPERS ###########################################################
+	function __call( $method, $args ) {
+		// Call the real method if it exists
+		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 ) ) {
+			# Interprets arguments:
+			$ns  = $args[0];
+			$msg = isset($args[1]) ? $args[1] : " dummy message";
+
+			# Forge the namespace constant name:
+			if( $ns === 0 ) {
+				$ns_name = "NS_MAIN";
+			} else {
+				$ns_name = "NS_" . strtoupper(  MWNamespace::getCanonicalName( $ns ) );
+			}
+			# ... and the MWNamespace method name
+			$nsMethod = strtolower( $m[1] ) . $m[3];
+
+			$expect = ($m[2] === '');
+			$expect_name = $expect ? 'TRUE' : 'FALSE';
+
+			return $this->assertEquals( $expect,
+				MWNamespace::$nsMethod( $ns, $msg ),
+				"MWNamespace::$nsMethod( $ns_name ) should returns $expect_name"
+			);
+		}
+
+		throw new Exception( __METHOD__ . " could not find a method named $method\n" );
+	}
+
+	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 45c02bbe..295b6d74 100644
--- a/tests/phpunit/includes/MessageTest.php
+++ b/tests/phpunit/includes/MessageTest.php
@@ -47,7 +47,7 @@ class MessageTest extends MediaWikiLangTestCase {
 		$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'] );
diff --git a/tests/phpunit/includes/ParserOptionsTest.php b/tests/phpunit/includes/ParserOptionsTest.php
index 58c89146..59c955fe 100644
--- a/tests/phpunit/includes/ParserOptionsTest.php
+++ b/tests/phpunit/includes/ParserOptionsTest.php
@@ -6,10 +6,9 @@ class ParserOptionsTest extends MediaWikiTestCase {
 	private $pcache;
 
 	function setUp() {
-		ParserTest::setUp(); //reuse setup from parser tests
 		global $wgContLang, $wgUser, $wgLanguageCode;
 		$wgContLang = Language::factory( $wgLanguageCode );
-		$this->popts = new ParserOptions( $wgUser );
+		$this->popts = ParserOptions::newFromUserAndLang( $wgUser, $wgContLang );
 		$this->pcache = ParserCache::singleton();
 	}
 
@@ -26,11 +25,11 @@ class ParserOptionsTest extends MediaWikiTestCase {
 		$wgUseDynamicDates = true;
 
 		$title = Title::newFromText( "Some test article" );
-		$article = new Article( $title );
+		$page = WikiPage::factory( $title );
 
-		$pcacheKeyBefore = $this->pcache->getKey( $article, $this->popts );
+		$pcacheKeyBefore = $this->pcache->getKey( $page, $this->popts );
 		$this->assertNotNull( $this->popts->getDateFormat() );
-		$pcacheKeyAfter = $this->pcache->getKey( $article, $this->popts );
+		$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
new file mode 100644
index 00000000..f6274584
--- /dev/null
+++ b/tests/phpunit/includes/PathRouterTest.php
@@ -0,0 +1,254 @@
+<?php
+/**
+ * Tests for the PathRouter parsing
+ */
+
+class PathRouterTest extends MediaWikiTestCase {
+
+	public function setUp() {
+		$router = new PathRouter;
+		$router->add("/wiki/$1");
+		$this->basicRouter = $router;
+	}
+
+	/**
+	 * Test basic path parsing
+	 */
+	public function testBasic() {
+		$matches = $this->basicRouter->parse( "/wiki/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo" ) );
+	}
+
+	/**
+	 * Test loose path auto-$1
+	 */
+	public function testLoose() {
+		$router = new PathRouter;
+		$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
+		$matches = $router->parse( "/wiki/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo" ) );
+
+		$router = new PathRouter;
+		$router->add("/wiki/"); # Should be the same as /wiki/$1
+		$matches = $router->parse( "/wiki/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo" ) );
+	}
+
+	/**
+	 * Test to ensure that path is based on specifity, not order
+	 */
+	public function testOrder() {
+		$router = new PathRouter;
+		$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");
+		$matches = $router->parse( "/a/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo" ) );
+	}
+
+	/**
+	 * Test the handling of key based arrays with a url parameter
+	 */
+	public function testKeyParameter() {
+		$router = new PathRouter;
+		$router->add( array( 'edit' => "/edit/$1" ), array( 'action' => '$key' ) );
+		$matches = $router->parse( "/edit/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo", 'action' => 'edit' ) );
+	}
+
+	/**
+	 * Test the handling of $2 inside paths
+	 */
+	public function testAdditionalParameter() {
+		// Basic $2
+		$router = new PathRouter;
+		$router->add( '/$2/$1', array( 'test' => '$2' ) );
+		$matches = $router->parse( "/asdf/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'asdf' ) );
+	}
+
+	/**
+	 * Test additional restricted value parameter
+	 */
+	public function testRestrictedValue() {
+		$router = new PathRouter;
+		$router->add( '/$2/$1',
+			array( 'test' => '$2' ),
+			array( '$2' => array( 'a', 'b' ) )
+		);
+		$router->add( '/$2/$1',
+			array( 'test2' => '$2' ),
+			array( '$2' => 'c' )
+		);
+		$router->add( '/$1' );
+
+		$matches = $router->parse( "/asdf/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "asdf/Foo" ) );
+
+		$matches = $router->parse( "/a/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo", 'test' => 'a' ) );
+
+		$matches = $router->parse( "/c/Foo" );
+		$this->assertEquals( $matches, array( 'title' => "Foo", 'test2' => 'c' ) );
+	}
+
+	public function callbackForTest( &$matches, $data ) {
+		$matches['x'] = $data['$1'];
+		$matches['foo'] = $data['foo'];
+	}
+
+	public function testCallback() {
+		$router = new PathRouter;
+		$router->add( "/$1",
+			array( 'a' => 'b', 'data:foo' => 'bar' ),
+			array( 'callback' => array( $this, 'callbackForTest' ) )
+		);
+		$matches = $router->parse( '/Foo' );
+		$this->assertEquals( $matches, array(
+			'title' => "Foo",
+			'x' => 'Foo',
+			'a' => 'b',
+			'foo' => 'bar'
+		) );
+	}
+
+	/**
+	 * Test to ensure that matches are not made if a parameter expects nonexistent input
+	 */
+	public function testFail() {
+		$router = new PathRouter;
+		$router->add( "/wiki/$1", array( 'title' => "$1$2" ) );
+		$matches = $router->parse( "/wiki/A" );
+		$this->assertEquals( array(), $matches );
+	}
+
+	/**
+	 * Test to ensure weight of paths is handled correctly
+	 */
+	public function testWeight() {
+		$router = new PathRouter;
+		$router->addStrict( "/Bar", array( 'ping' => 'pong' ) );
+		$router->add( "/asdf-$1", array( 'title' => 'qwerty-$1' ) );
+		$router->add( "/$1" );
+		$router->add( "/qwerty-$1", array( 'title' => 'asdf-$1' ) );
+		$router->addStrict( "/Baz", array( 'marco' => 'polo' ) );
+		$router->add( "/a/$1" );
+		$router->add( "/asdf/$1" );
+		$router->add( "/$2/$1", array( 'unrestricted' => '$2' ) );
+		$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 ) {
+			$this->assertEquals( $router->parse( $path ), $result );
+		}
+	}
+
+	/**
+	 * Make sure the router handles titles like Special:Recentchanges correctly
+	 */
+	public function testSpecial() {
+		$matches = $this->basicRouter->parse( "/wiki/Special:Recentchanges" );
+		$this->assertEquals( $matches, array( 'title' => "Special:Recentchanges" ) );
+	}
+
+	/**
+	 * Make sure the router decodes urlencoding properly
+	 */
+	public function testUrlencoding() {
+		$matches = $this->basicRouter->parse( "/wiki/Title_With%20Space" );
+		$this->assertEquals( $matches, array( 'title' => "Title_With Space" ) );
+	}
+
+	public function dataRegexpChars() {
+		return array(
+			array( "$" ),
+			array( "$1" ),
+			array( "\\" ),
+			array( "\\$1" ),
+		);
+	}
+
+	/**
+	 * Make sure the router doesn't break on special characters like $ used in regexp replacements
+	 * @dataProvider dataRegexpChars
+	 */
+	public function testRegexpChars( $char ) {
+		$matches = $this->basicRouter->parse( "/wiki/$char" );
+		$this->assertEquals( $matches, array( 'title' => "$char" ) );
+	}
+
+	/**
+	 * Make sure the router handles characters like +&() properly
+	 */
+	public function testCharacters() {
+		$matches = $this->basicRouter->parse( "/wiki/Plus+And&Dollar\\Stuff();[]{}*" );
+		$this->assertEquals( $matches, array( 'title' => "Plus+And&Dollar\\Stuff();[]{}*" ) );
+	}
+
+	/**
+	 * Make sure the router handles unicode characters correctly
+	 * @depends testSpecial
+	 * @depends testUrlencoding
+	 * @depends testCharacters
+	 */
+	public function testUnicode() {
+		$matches = $this->basicRouter->parse( "/wiki/Spécial:Modifications_récentes" );
+		$this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+
+		$matches = $this->basicRouter->parse( "/wiki/Sp%C3%A9cial:Modifications_r%C3%A9centes" );
+		$this->assertEquals( $matches, array( 'title' => "Spécial:Modifications_récentes" ) );
+	}
+
+	/**
+	 * Ensure the router doesn't choke on long paths.
+	 */
+	public function testLength() {
+		$matches = $this->basicRouter->parse( "/wiki/Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." );
+		$this->assertEquals( $matches, array( 'title' => "Lorem_ipsum_dolor_sit_amet,_consectetur_adipisicing_elit,_sed_do_eiusmod_tempor_incididunt_ut_labore_et_dolore_magna_aliqua._Ut_enim_ad_minim_veniam,_quis_nostrud_exercitation_ullamco_laboris_nisi_ut_aliquip_ex_ea_commodo_consequat._Duis_aute_irure_dolor_in_reprehenderit_in_voluptate_velit_esse_cillum_dolore_eu_fugiat_nulla_pariatur._Excepteur_sint_occaecat_cupidatat_non_proident,_sunt_in_culpa_qui_officia_deserunt_mollit_anim_id_est_laborum." ) );
+	}
+
+
+	/**
+	 * Ensure that the php passed site of parameter values are not urldecoded
+	 */
+	public function testPatternUrlencoding() {
+		$router = new PathRouter;
+		$router->add( "/wiki/$1", array( 'title' => '%20:$1' ) );
+		$matches = $router->parse( "/wiki/Foo" );
+		$this->assertEquals( $matches, array( 'title' => '%20:Foo' ) );
+	}
+
+	/**
+	 * Ensure that raw parameter values do not have any variable replacements or urldecoding
+	 */
+	public function testRawParamValue() {
+		$router = new PathRouter;
+		$router->add( "/wiki/$1", array( 'title' => array( 'value' => 'bar%20$1' ) ) );
+		$matches = $router->parse( "/wiki/Foo" );
+		$this->assertEquals( $matches, array( 'title' => 'bar%20$1' ) );
+	}
+
+}
diff --git a/tests/phpunit/includes/Providers.php b/tests/phpunit/includes/Providers.php
index 02898673..f451f8a0 100644
--- a/tests/phpunit/includes/Providers.php
+++ b/tests/phpunit/includes/Providers.php
@@ -2,8 +2,8 @@
 /**
  * Generic providers for the MediaWiki PHPUnit test suite
  *
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
  * @file
  */
 
diff --git a/tests/phpunit/includes/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoaderTest.php
index 30a69c5e..ab704839 100644
--- a/tests/phpunit/includes/ResourceLoaderTest.php
+++ b/tests/phpunit/includes/ResourceLoaderTest.php
@@ -1,6 +1,6 @@
 <?php
 
-class ResourceLoaderTest extends PHPUnit_Framework_TestCase {
+class ResourceLoaderTest extends MediaWikiTestCase {
 
 	protected static $resourceLoaderRegisterModulesHook;
 
diff --git a/tests/phpunit/includes/SanitizerTest.php b/tests/phpunit/includes/SanitizerTest.php
index 40d6cf77..b76aa5c7 100644
--- a/tests/phpunit/includes/SanitizerTest.php
+++ b/tests/phpunit/includes/SanitizerTest.php
@@ -109,5 +109,48 @@ class SanitizerTest extends MediaWikiTestCase {
 		$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&amp;&quot;' ), array( 'foo' => '&"' ), 'Special chars can be provided as entities' );
 		$this->assertEquals( Sanitizer::decodeTagAttributes( 'foo=&foobar;' ), array( 'foo' => '&foobar;' ), 'Entity-like items are accepted' );
 	}
+
+	function testDeprecatedAttributes() {
+		$GLOBALS['wgCleanupPresentationalAttributes'] = true;
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' style="clear: left;"', 'Deprecated attributes are converted to styles when enabled.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'clear="all"', 'br' ), ' style="clear: both;"', 'clear=all is converted to clear: both; not clear: all;' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'CLEAR="ALL"', 'br' ), ' style="clear: both;"', 'clear=ALL is not treated differently from clear=all' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'width="100"', 'td' ), ' style="width: 100px;"', 'Numeric sizes use pixels instead of numbers.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'width="100%"', 'td' ), ' style="width: 100%;"', 'Units are allowed in sizes.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'WIDTH="100%"', 'td' ), ' style="width: 100%;"', 'Uppercase WIDTH is treated as lowercase width.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'WiDTh="100%"', 'td' ), ' style="width: 100%;"', 'Mixed case does not break WiDTh.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute is output as white-space: nowrap; not something else.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'nowrap=""', 'td' ), ' style="white-space: nowrap;"', 'nowrap="" is considered true, not false' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'NOWRAP="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when uppercase.' );
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'NoWrAp="true"', 'td' ), ' style="white-space: nowrap;"', 'nowrap attribute works when mixed-case.' );
+		$GLOBALS['wgCleanupPresentationalAttributes'] = false;
+		$this->assertEquals( Sanitizer::fixTagAttributes( 'clear="left"', 'br' ), ' clear="left"', 'Deprecated attributes are not converted to styles when enabled.' );
+	}
+
+	/**
+	 * @dataProvider provideCssCommentsFixtures
+	 */
+	function testCssCommentsChecking( $expected, $css, $message = '' ) {
+		$this->assertEquals(
+			$expected,
+			Sanitizer::checkCss( $css ),
+			$message
+		);
+	}
+
+	function provideCssCommentsFixtures() {
+		/** array( <expected>, <css>, [message] ) */
+		return array(
+			array( ' ', '/**/' ),
+			array( ' ', '/****/' ),
+			array( ' ', '/* comment */' ),
+			array( ' ', "\\2f\\2a foo \\2a\\2f",
+				'Backslash-escaped comments must be stripped (bug 28450)' ),
+			array( '', '/* unfinished comment structure',
+	 			'Remove anything after a comment-start token' ),
+			array( '', "\\2f\\2a unifinished comment'",
+	 			'Remove anything after a backslash-escaped comment-start token' ),
+		);
+	}
 }
 
diff --git a/tests/phpunit/includes/SanitizerValidateEmailTest.php b/tests/phpunit/includes/SanitizerValidateEmailTest.php
new file mode 100644
index 00000000..14d799cf
--- /dev/null
+++ b/tests/phpunit/includes/SanitizerValidateEmailTest.php
@@ -0,0 +1,79 @@
+<?php
+
+class SanitizerValidateEmailTest extends MediaWikiTestCase {
+
+	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() {
+		$this->valid( 'user@example.com' );
+		$this->valid( 'user@example.museum' );
+	}
+	function testEmailWithUpperCaseCharactersAreValid() {
+		$this->valid( 'USER@example.com' );
+		$this->valid( 'user@EXAMPLE.COM' );
+		$this->valid( 'user@Example.com' );
+		$this->valid( 'USER@eXAMPLE.com' );
+	}
+	function testEmailWithAPlusInUserName() {
+		$this->valid( 'user+sub@example.com' );
+		$this->valid( 'user+@example.com' );
+	}
+	function testEmailDoesNotNeedATopLevelDomain() {
+		$this->valid( "user@localhost" );
+		$this->valid( "FooBar@localdomain" );
+		$this->valid( "nobody@mycompany" );
+	}
+	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() {
+		$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() {
+		$this->invalid( "user,foo@example.org" );
+		$this->invalid( "userfoo@ex,ample.org" );
+	}
+	function testEmailWithHyphens() {
+		$this->valid( "user-foo@example.org" );
+		$this->valid( "userfoo@ex-ample.org" );
+	}
+	function testEmailDomainCanNotBeginWithDot() {
+		$this->invalid( "user@." );
+		$this->invalid( "user@.localdomain" );
+		$this->invalid( "user@localdomain." );
+		$this->valid( "user.@localdomain" );
+		$this->valid( ".@localdomain" );
+		$this->invalid( ".@a............" );
+	}
+	function testEmailWithFunnyCharacters() {
+		$this->valid( "\$user!ex{this}@123.com" );
+	}
+	function testEmailTopLevelDomainCanBeNumerical() {
+		$this->valid( "user@example.1234" );
+	}
+	function testEmailWithoutAtSignIsInvalid() {
+		$this->invalid( 'useràexample.com' );
+	}
+	function testEmailWithOneCharacterDomainIsValid() {
+		$this->valid( 'user@a' );
+	}
+}
diff --git a/tests/phpunit/includes/SeleniumConfigurationTest.php b/tests/phpunit/includes/SeleniumConfigurationTest.php
index 750524eb..8589c188 100644
--- a/tests/phpunit/includes/SeleniumConfigurationTest.php
+++ b/tests/phpunit/includes/SeleniumConfigurationTest.php
@@ -2,13 +2,13 @@
 
 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 =
@@ -32,14 +32,14 @@ runAgainstGrid	= false
 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(
@@ -55,7 +55,7 @@ testSuite[TestSuiteName] = "testSuitePath"
 		'jUnitLogFile' => null,
 		'runAgainstGrid' => null
 	);
-	/*
+	/**
 	 * Array of expected testSuites from $testConfig0
 	 */
 	private $testSuites0 = array(
@@ -64,7 +64,7 @@ testSuite[TestSuiteName] = "testSuitePath"
 	);
 
 
-	/*
+	/**
 	 * Another sample selenium settings file contents
 	 */
 	private $testConfig1 =
@@ -73,11 +73,11 @@ testSuite[TestSuiteName] = "testSuitePath"
 host 				= "localhost"
 testBrowser 		= "firefox"
 ';
-	/*
+	/**
 	 * Expected browsers from $testConfig1
 	 */
 	private $testBrowsers1 = null;
-	/*
+	/**
 	 * Expected selenium settings from $testConfig1
 	 */
 	private $testSettings1 = array(
@@ -93,7 +93,7 @@ testBrowser 		= "firefox"
 		'jUnitLogFile' => null,
 		'runAgainstGrid' => null
 	);
-	/*
+	/**
 	 * Expected test suites from $testConfig1
 	 */
 	private $testSuites1 = null;
@@ -105,7 +105,7 @@ testBrowser 		= "firefox"
 		}
 	}
 
-	/*
+	/**
 	 * Clean up the temporary file used to store the selenium settings.
 	 */
 	public function tearDown() {
@@ -199,7 +199,7 @@ testBrowser 		= "firefox"
 
 	}
 
-	/*
+	/**
 	 * create a temp file and write text to it.
 	 * @param $testToWrite the text to write to the temp file
 	 */
@@ -210,7 +210,7 @@ testBrowser 		= "firefox"
 		fclose($tempFile);
 	}
 
-	/*
+	/**
 	 * Returns an array containing:
 	 * 	The contents of the selenium cingiguration ini file
 	 *  The expected selenium configuration array that getSeleniumSettings should return
diff --git a/tests/phpunit/includes/TemplateCategoriesTest.php b/tests/phpunit/includes/TemplateCategoriesTest.php
new file mode 100644
index 00000000..de9d6dc6
--- /dev/null
+++ b/tests/phpunit/includes/TemplateCategoriesTest.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @group Database
+ */
+require dirname( __FILE__ ) . "/../../../maintenance/runJobs.php";
+
+class TemplateCategoriesTest extends MediaWikiLangTestCase {
+
+	function testTemplateCategories() {
+		global $wgUser;
+
+		$title = Title::newFromText( "Categorized from template" );
+		$article = new Article( $title );
+		$wgUser = new User();
+		$wgUser->mRights['*'] = array( 'createpage', 'edit', 'purge' );
+
+		$status = $article->doEdit( '{{Categorising template}}', 'Create a page with a template', 0 );
+		$this->assertEquals(
+			array()
+			, $title->getParentCategories()
+		);
+
+		$template = new Article( Title::newFromText( 'Template:Categorising template' ) );
+		$status = $template->doEdit( '[[Category:Solved bugs]]', 'Add a category through a template', 0 );
+
+		// Run the job queue
+		$jobs = new RunJobs;
+		$jobs->loadParamsAndArgs( null, array( 'quiet' => true ), null );
+		$jobs->execute();
+
+		$this->assertEquals(
+			array( 'Category:Solved_bugs' => $title->getPrefixedText() )
+			, $title->getParentCategories()
+		);
+	}
+
+}
diff --git a/tests/phpunit/includes/TitleMethodsTest.php b/tests/phpunit/includes/TitleMethodsTest.php
new file mode 100644
index 00000000..2f1103e8
--- /dev/null
+++ b/tests/phpunit/includes/TitleMethodsTest.php
@@ -0,0 +1,78 @@
+<?php
+
+class TitleMethodsTest extends MediaWikiTestCase {
+
+	public function dataEquals() {
+		return array(
+			array( 'Main Page', 'Main Page', true ),
+			array( 'Main Page', 'Not The Main Page', false ),
+			array( 'Main Page', 'Project:Main Page', false ),
+			array( 'File:Example.png', 'Image:Example.png', true ),
+			array( 'Special:Version', 'Special:Version', true ),
+			array( 'Special:Version', 'Special:Recentchanges', false ),
+			array( 'Special:Version', 'Main Page', false ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataEquals
+	 */
+	public function testEquals( $titleA, $titleB, $expectedBool ) {
+		$titleA = Title::newFromText( $titleA );
+		$titleB = Title::newFromText( $titleB );
+
+		$this->assertEquals( $titleA->equals( $titleB ), $expectedBool );
+		$this->assertEquals( $titleB->equals( $titleA ), $expectedBool );
+	}
+
+	public function dataInNamespace() {
+		return array(
+			array( 'Main Page', NS_MAIN, true ),
+			array( 'Main Page', NS_TALK, false ),
+			array( 'Main Page', NS_USER, false ),
+			array( 'User:Foo', NS_USER, true ),
+			array( 'User:Foo', NS_USER_TALK, false ),
+			array( 'User:Foo', NS_TEMPLATE, false ),
+			array( 'User_talk:Foo', NS_USER_TALK, true ),
+			array( 'User_talk:Foo', NS_USER, false ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataInNamespace
+	 */
+	public function testInNamespace( $title, $ns, $expectedBool ) {
+		$title = Title::newFromText( $title );
+		$this->assertEquals( $title->inNamespace( $ns ), $expectedBool );
+	}
+
+	public function testInNamespaces() {
+		$mainpage = Title::newFromText( 'Main Page' );
+		$this->assertTrue( $mainpage->inNamespaces( NS_MAIN, NS_USER ) );
+		$this->assertTrue( $mainpage->inNamespaces( array( NS_MAIN, NS_USER ) ) );
+		$this->assertTrue( $mainpage->inNamespaces( array( NS_USER, NS_MAIN ) ) );
+		$this->assertFalse( $mainpage->inNamespaces( array( NS_PROJECT, NS_TEMPLATE ) ) );
+	}
+
+	public function dataHasSubjectNamespace() {
+		return array(
+			array( 'Main Page', NS_MAIN, true ),
+			array( 'Main Page', NS_TALK, true ),
+			array( 'Main Page', NS_USER, false ),
+			array( 'User:Foo', NS_USER, true ),
+			array( 'User:Foo', NS_USER_TALK, true ),
+			array( 'User:Foo', NS_TEMPLATE, false ),
+			array( 'User_talk:Foo', NS_USER_TALK, true ),
+			array( 'User_talk:Foo', NS_USER, true ),
+		);
+	}
+
+	/**
+	 * @dataProvider dataHasSubjectNamespace
+	 */
+	public function testHasSubjectNamespace( $title, $ns, $expectedBool ) {
+		$title = Title::newFromText( $title );
+		$this->assertEquals( $title->hasSubjectNamespace( $ns ), $expectedBool );
+	}
+
+}
diff --git a/tests/phpunit/includes/TitlePermissionTest.php b/tests/phpunit/includes/TitlePermissionTest.php
index 1b179686..f62ac5dd 100644
--- a/tests/phpunit/includes/TitlePermissionTest.php
+++ b/tests/phpunit/includes/TitlePermissionTest.php
@@ -5,12 +5,16 @@
  */
 class TitlePermissionTest extends MediaWikiLangTestCase {
 	protected $title;
-	protected $user;
-	protected $anonUser;
-	protected $userUser;
-	protected $altUser;
-	protected $userName;
-	protected $altUserName;
+
+	/**
+	 * @var User
+	 */
+	protected $user, $anonUser, $userUser, $altUser;
+
+	/**
+	 * @var string
+	 */
+	protected $userName, $altUserName;
 
 	function setUp() {
 		global $wgLocaltimezone, $wgLocalTZoffset, $wgMemc, $wgContLang, $wgLang;
@@ -56,6 +60,8 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 	}
 
 	function setUserPerm( $perm ) {
+		// Setting member variables is evil!!!
+
 		if ( is_array( $perm ) ) {
 			$this->user->mRights = $perm;
 		} else {
@@ -299,7 +305,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 			$this->assertEquals( $check[$action][3],
 				$this->title->userCan( $action, true ) );
 			$this->assertEquals( $check[$action][3],
-				$this->title->quickUserCan( $action, false ) );
+				$this->title->quickUserCan( $action ) );
 
 			# count( User::getGroupsWithPermissions( $action ) ) < 1
 		}
@@ -451,7 +457,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 																	 $this->user ) );
 
 		$this->assertEquals( true,
-							 $this->title->quickUserCan( 'edit', false ) );
+							 $this->title->quickUserCan( 'edit' ) );
 		$this->title->mRestrictions = array( "edit" => array( 'bogus', "sysop", "protect", "" ),
 										   "bogus" => array( 'bogus', "sysop", "protect", "" ) );
 
@@ -491,9 +497,9 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 																	 $this->user ) );
 		$this->title->mCascadeRestriction = true;
 		$this->assertEquals( false,
-							 $this->title->quickUserCan( 'bogus', false ) );
+							 $this->title->quickUserCan( 'bogus' ) );
 		$this->assertEquals( false,
-							 $this->title->quickUserCan( 'edit', false ) );
+							 $this->title->quickUserCan( 'edit' ) );
 		$this->assertEquals( array( array( 'badaccess-group0' ),
 									array( 'protectedpagetext', 'bogus' ),
 									array( 'protectedpagetext', 'protect' ),
@@ -537,7 +543,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 		$this->setTitle( NS_MAIN, "test page" );
 		$this->title->mTitleProtection['pt_create_perm'] = '';
 		$this->title->mTitleProtection['pt_user'] = $this->user->getID();
-		$this->title->mTitleProtection['pt_expiry'] = Block::infinity();
+		$this->title->mTitleProtection['pt_expiry'] = wfGetDB( DB_SLAVE )->getInfinity();
 		$this->title->mTitleProtection['pt_reason'] = 'test';
 		$this->title->mCascadeRestriction = false;
 
@@ -574,7 +580,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 							 $this->title->userCan( 'move' ) );
 
 		$this->title->mInterwiki = "no";
-		$this->assertEquals( array( array( 'immobile-page' ) ),
+		$this->assertEquals( array( array( 'immobile-source-page' ) ),
 							 $this->title->getUserPermissionsErrors( 'move', $this->user ) );
 		$this->assertEquals( false,
 							 $this->title->userCan( 'move' ) );
@@ -623,7 +629,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 		$prev = time();
 		$now = time() + 120;
 		$this->user->mBlockedby = $this->user->getId();
-		$this->user->mBlock = new Block( '127.0.8.1', $this->user->getId(), $this->user->getId(),
+		$this->user->mBlock = new Block( '127.0.8.1', 0, $this->user->getId(),
 										'no reason given', $prev + 3600, 1, 0 );
 		$this->user->mBlock->mTimestamp = 0;
 		$this->assertEquals( array( array( 'autoblockedtext',
@@ -640,7 +646,7 @@ class TitlePermissionTest extends MediaWikiLangTestCase {
 		global $wgLocalTZoffset;
 		$wgLocalTZoffset = -60;
 		$this->user->mBlockedby = $this->user->getName();
-		$this->user->mBlock = new Block( '127.0.8.1', 2, 1, 'no reason given', $now, 0, 10 );
+		$this->user->mBlock = new Block( '127.0.8.1', 0, 1, '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',
diff --git a/tests/phpunit/includes/TitleTest.php b/tests/phpunit/includes/TitleTest.php
index e7bb98ac..1c8be5f9 100644
--- a/tests/phpunit/includes/TitleTest.php
+++ b/tests/phpunit/includes/TitleTest.php
@@ -41,10 +41,10 @@ class TitleTest extends MediaWikiTestCase {
 	/**
 	 * Auth-less test of Title::isValidMoveOperation
 	 * 
+	 * @group Database
 	 * @param string $source
 	 * @param string $target
-	 * @param array|string|true $requiredErrors
-	 * @group Database
+	 * @param array|string|true $expected Required error
 	 * @dataProvider dataTestIsValidMoveOperation
 	 */
 	function testIsValidMoveOperation( $source, $target, $expected ) {
diff --git a/tests/phpunit/includes/UserIsValidEmailAddrTest.php b/tests/phpunit/includes/UserIsValidEmailAddrTest.php
deleted file mode 100644
index 99bf718e..00000000
--- a/tests/phpunit/includes/UserIsValidEmailAddrTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-<?php
-
-class UserIsValidEmailAddrTest extends MediaWikiTestCase {
-
-	private function checkEmail( $addr, $expected = true, $msg = '') {
-		if( $msg == '' ) { $msg = "Testing $addr"; }
-		$this->assertEquals(
-			$expected,
-			User::isValidEmailAddr( $addr ),
-			$msg
-		);
-	}
-	private function valid( $addr, $msg = '' ) {
-		$this->checkEmail( $addr, true, $msg );
-	}
-	private function invalid( $addr, $msg = '' ) {
-		$this->checkEmail( $addr, false, $msg );
-	}
-
-	function testEmailWellKnownUserAtHostDotTldAreValid() {
-		$this->valid( 'user@example.com' );
-		$this->valid( 'user@example.museum' );
-	}
-	function testEmailWithUpperCaseCharactersAreValid() {
-		$this->valid( 'USER@example.com' );
-		$this->valid( 'user@EXAMPLE.COM' );
-		$this->valid( 'user@Example.com' );
-		$this->valid( 'USER@eXAMPLE.com' );
-	}
-	function testEmailWithAPlusInUserName() {
-		$this->valid( 'user+sub@example.com' );
-		$this->valid( 'user+@example.com' );
-	}
-	function testEmailDoesNotNeedATopLevelDomain() {
-		$this->valid( "user@localhost" );
-		$this->valid( "FooBar@localdomain" );
-		$this->valid( "nobody@mycompany" );
-	}
-	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() {
-		$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() {
-		$this->invalid( "user,foo@example.org" );
-		$this->invalid( "userfoo@ex,ample.org" );
-	}
-	function testEmailWithHyphens() {
-		$this->valid( "user-foo@example.org" );
-		$this->valid( "userfoo@ex-ample.org" );
-	}
-	function testEmailDomainCanNotBeginWithDot() {
-		$this->invalid( "user@." );
-		$this->invalid( "user@.localdomain" );
-		$this->invalid( "user@localdomain." );
-		$this->valid( "user.@localdomain" );
-		$this->valid( ".@localdomain" );
-		$this->invalid( ".@a............" );
-	}
-	function testEmailWithFunnyCharacters() {
-		$this->valid( "\$user!ex{this}@123.com" );
-	}
-	function testEmailTopLevelDomainCanBeNumerical() {
-		$this->valid( "user@example.1234" );
-	}
-	function testEmailWithoutAtSignIsInvalid() {
-		$this->invalid( 'useràexample.com' );
-	}
-	function testEmailWithOneCharacterDomainIsValid() {
-		$this->valid( 'user@a' );
-	}
-}
diff --git a/tests/phpunit/includes/UserTest.php b/tests/phpunit/includes/UserTest.php
index df91aca8..ef03e835 100644
--- a/tests/phpunit/includes/UserTest.php
+++ b/tests/phpunit/includes/UserTest.php
@@ -1,47 +1,67 @@
 <?php
 
+define( 'NS_UNITTEST', 5600 );
+define( 'NS_UNITTEST_TALK', 5601 );
+
+/**
+ * @group Database
+ */
 class UserTest extends MediaWikiTestCase {
 	protected $savedGroupPermissions, $savedRevokedPermissions;
-	
+
+	/**
+	 * @var User
+	 */
+	protected $user;
+
 	public function setUp() {
 		parent::setUp();
-		
+
 		$this->savedGroupPermissions = $GLOBALS['wgGroupPermissions'];
 		$this->savedRevokedPermissions = $GLOBALS['wgRevokePermissions'];
-		
+
 		$this->setUpPermissionGlobals();
+		$this->setUpUser();
 	}
 	private function setUpPermissionGlobals() {
 		global $wgGroupPermissions, $wgRevokePermissions;
-		
+
+		# Data for regular $wgGroupPermissions test
 		$wgGroupPermissions['unittesters'] = array(
+			'test' => true,
 			'runtest' => true,
 			'writetest' => false,
 			'nukeworld' => false,
 		);
 		$wgGroupPermissions['testwriters'] = array(
+			'test' => true,
 			'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;
 	}
-	
+
 	public function testGroupPermissions() {
 		$rights = User::getGroupPermissions( array( 'unittesters' ) );
 		$this->assertContains( 'runtest', $rights );
 		$this->assertNotContains( 'writetest', $rights );
 		$this->assertNotContains( 'modifytest', $rights );
 		$this->assertNotContains( 'nukeworld', $rights );
-		
+
 		$rights = User::getGroupPermissions( array( 'unittesters', 'testwriters' ) );
 		$this->assertContains( 'runtest', $rights );
 		$this->assertContains( 'writetest', $rights );
@@ -53,6 +73,71 @@ class UserTest extends MediaWikiTestCase {
 		$this->assertNotContains( 'runtest', $rights );
 		$this->assertNotContains( 'writetest', $rights );
 		$this->assertNotContains( 'modifytest', $rights );
-		$this->assertNotContains( 'nukeworld', $rights );		
+		$this->assertNotContains( 'nukeworld', $rights );
+	}
+
+	public function testUserPermissions() {
+		$rights = $this->user->getRights();
+		$this->assertContains( 'runtest', $rights );
+		$this->assertNotContains( 'writetest', $rights );
+		$this->assertNotContains( 'modifytest', $rights );
+		$this->assertNotContains( 'nukeworld', $rights );
+	}
+
+	/**
+	 * @dataProvider provideGetGroupsWithPermission
+	 */
+	public function testGetGroupsWithPermission( $expected, $right ) {
+		$result = User::getGroupsWithPermission( $right );
+		sort( $result );
+		sort( $expected );
+
+		$this->assertEquals( $expected, $result, "Groups with permission $right" );
+	}
+
+	public function provideGetGroupsWithPermission() {
+		return array(
+			array(
+				array( 'unittesters', 'testwriters' ),
+				'test'
+			),
+			array(
+				array( 'unittesters' ),
+				'runtest'
+			),
+			array(
+				array( 'testwriters' ),
+				'writetest'
+			),
+			array(
+				array( 'testwriters' ),
+				'modifytest'
+			),
+		);
+	}
+
+	/**
+	 * @dataProvider provideUserNames
+	 */
+	public function testIsValidUserName( $username, $result, $message ) {
+		$this->assertEquals( $this->user->isValidUserName( $username ), $result, $message );
+	}
+
+	public 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( 'User:Abcd', false, 'Reserved Namespace' ),
+			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( 'Ab cd', false, ' Ideographic space' ),
+		);
 	}
-}
\ No newline at end of file
+}
diff --git a/tests/phpunit/includes/WebRequestTest.php b/tests/phpunit/includes/WebRequestTest.php
index 1cfbd3fc..e72408f6 100644
--- a/tests/phpunit/includes/WebRequestTest.php
+++ b/tests/phpunit/includes/WebRequestTest.php
@@ -85,4 +85,101 @@ class WebRequestTest extends MediaWikiTestCase {
 			),
 		);
 	}
+
+	/**
+	 * @dataProvider provideGetIP
+	 */
+	function testGetIP( $expected, $input, $squid, $private, $description ) {
+		global $wgSquidServersNoPurge, $wgUsePrivateIPs;
+		$oldServer = $_SERVER;
+		$_SERVER = $input;
+		$wgSquidServersNoPurge = $squid;
+		$wgUsePrivateIPs = $private;
+		$request = new WebRequest();
+		$result = $request->getIP();
+		$_SERVER = $oldServer;
+		$this->assertEquals( $expected, $result, $description );
+	}
+
+	function provideGetIP() {
+		return array(
+			array(
+				'127.0.0.1',
+				array(
+					'REMOTE_ADDR' => '127.0.0.1'
+				),
+				array(),
+				false,
+				'Simple IPv4'
+			),
+			array(
+				'::1',
+				array(
+					'REMOTE_ADDR' => '::1'
+				),
+				array(),
+				false,
+				'Simple IPv6'
+			),
+			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' ),
+				false,
+				'With X-Forwaded-For'
+			),
+			array(
+				'12.0.0.1',
+				array(
+					'REMOTE_ADDR' => '12.0.0.1',
+					'HTTP_X_FORWARDED_FOR' => '12.0.0.3, 12.0.0.2'
+				),
+				array(),
+				false,
+				'With X-Forwaded-For and disallowed server'
+			),
+			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( '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( '12.0.0.1', '12.0.0.2' ),
+				false,
+				'With X-Forwaded-For and private IP'
+			),
+			array(
+				'10.0.0.3',
+				array(
+					'REMOTE_ADDR' => '12.0.0.2',
+					'HTTP_X_FORWARDED_FOR' => '10.0.0.3, 12.0.0.2'
+				),
+				array( '12.0.0.1', '12.0.0.2' ),
+				true,
+				'With X-Forwaded-For and private IP (allowed)'
+			),
+		);
+	}
+
+	/**
+	 * @expectedException MWException
+	 */
+	function testGetIpLackOfRemoteAddrThrowAnException() {
+		$request = new WebRequest();
+		# Next call throw an exception about lacking an IP
+		$request->getIP();
+	}
 }
diff --git a/tests/phpunit/includes/XmlSelectTest.php b/tests/phpunit/includes/XmlSelectTest.php
index bf761e3d..2407c151 100644
--- a/tests/phpunit/includes/XmlSelectTest.php
+++ b/tests/phpunit/includes/XmlSelectTest.php
@@ -80,7 +80,7 @@ class XmlSelectTest extends MediaWikiTestCase {
 		$this->select->addOption( 'foo2' );
 		$this->assertEquals(
 '<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
 '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
 	}
 
@@ -96,7 +96,7 @@ class XmlSelectTest extends MediaWikiTestCase {
 		$this->select->setDefault( 'bar1' ); # setting default after adding options
 		$this->assertEquals(
 '<select><option value="foo1">foo1</option>' . "\n" .
-'<option value="bar1" selected="selected">bar1</option>' . "\n" .
+'<option value="bar1" selected="">bar1</option>' . "\n" .
 '<option value="foo2">foo2</option></select>', $this->select->getHTML() );
 	}
 
diff --git a/tests/phpunit/includes/XmlTest.php b/tests/phpunit/includes/XmlTest.php
index a6058ef6..1d9361f2 100644
--- a/tests/phpunit/includes/XmlTest.php
+++ b/tests/phpunit/includes/XmlTest.php
@@ -2,19 +2,43 @@
 
 class XmlTest extends MediaWikiTestCase {
 	private static $oldLang;
+	private static $oldNamespaces;
 
 	public function setUp() {
-		global $wgLang, $wgLanguageCode;
-		
+		global $wgLang, $wgContLang;
+
 		self::$oldLang = $wgLang;
-		$wgLanguageCode = 'en';
-		$wgLang = Language::factory( $wgLanguageCode );
+		$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(
+			-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',
+		) );
 	}
-	
+
 	public function tearDown() {
-		global $wgLang, $wgLanguageCode;
+		global $wgLang, $wgContLang;
 		$wgLang = self::$oldLang;
-		$wgLanguageCode = $wgLang->getCode();
+		
+		$wgContLang->setNamespaces( self::$oldNamespaces );
 	}
 
 	public function testExpandAttributes() {
@@ -88,6 +112,9 @@ class XmlTest extends MediaWikiTestCase {
 		$this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' );
 	}
 
+	/**
+	 * @group Broken
+	 */
 	public function testDateMenu( ) {
 		$curYear   = intval(gmdate('Y'));
 		$prevYear  = $curYear - 1;
@@ -98,11 +125,10 @@ class XmlTest extends MediaWikiTestCase {
 		$nextMonth = $curMonth + 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="selected">February</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" .
@@ -139,7 +165,6 @@ class XmlTest extends MediaWikiTestCase {
 			"Date menu year is the current one when not specified"
 		);
 
-		$this->markTestIncomplete( "Broken" );
 		// @todo FIXME: next month can be in the next year
 		// test failing because it is now december
 		$this->assertEquals(
@@ -163,11 +188,57 @@ class XmlTest extends MediaWikiTestCase {
 '<option value="10">October</option>' . "\n" .
 '<option value="11">November</option>' . "\n" .
 '<option value="12">December</option></select>',
-			Xml::dateMenu( '', ''),
+			Xml::dateMenu( '', '' ),
 			"Date menu with neither year or month"
 		);
 	}
 
+	function testNamespaceSelector() {
+		$this->assertEquals(
+			'<select class="namespaceselector" 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="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+			Xml::namespaceSelector(),
+			'Basic namespace selector without custom options'
+		);
+		$this->assertEquals(
+			'<label for="namespace">Select a namespace:</label>' .
+'&#160;<select class="namespaceselector" id="namespace" name="myname">' . "\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="100">Custom</option>' . "\n" .
+'<option value="101">Custom talk</option>' . "\n" .
+'</select>',
+			Xml::namespaceSelector( $selected = '2', $all = 'all', $element_name = 'myname', $label = 'Select a namespace:' ),
+			'Basic namespace selector with custom values'
+		);
+	}
+
+
 	#
 	# textarea
 	#
@@ -255,12 +326,12 @@ class XmlTest extends MediaWikiTestCase {
 
 	function testEncodeJsVarArray() {
 		$this->assertEquals(
-			'["a", 1]',
+			'["a",1]',
 			Xml::encodeJsVar( array( 'a', 1 ) ),
 			'encodeJsVar() with array'
 		);
 		$this->assertEquals(
-			'{"a": "a", "b": 1}',
+			'{"a":"a","b":1}',
 			Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ),
 			'encodeJsVar() with associative array'
 		);
@@ -268,7 +339,7 @@ class XmlTest extends MediaWikiTestCase {
 
 	function testEncodeJsVarObject() {
 		$this->assertEquals(
-			'{"a": "a", "b": 1}',
+			'{"a":"a","b":1}',
 			Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ),
 			'encodeJsVar() with object'
 		);
diff --git a/tests/phpunit/includes/api/ApiBlockTest.php b/tests/phpunit/includes/api/ApiBlockTest.php
index 227555eb..b95d8214 100644
--- a/tests/phpunit/includes/api/ApiBlockTest.php
+++ b/tests/phpunit/includes/api/ApiBlockTest.php
@@ -15,24 +15,34 @@ class ApiBlockTest extends ApiTestCase {
 	}
 
 	function addDBData() {
-		$user = User::newFromName( 'UTBlockee' );
+		$user = User::newFromName( 'UTApiBlockee' );
 
 		if ( $user->getId() == 0 ) {
 			$user->addToDatabase();
-			$user->setPassword( 'UTBlockeePassword' );
+			$user->setPassword( 'UTApiBlockeePassword' );
 
 			$user->saveSettings();
 		}
 	}
 
+	/**
+	 * This test has probably always been broken and use an invalid token
+	 * Bug tracking brokenness is https://bugzilla.wikimedia.org/35646
+	 *
+	 * Root cause is https://gerrit.wikimedia.org/r/3434
+	 * Which made the Block/Unblock API to actually verify the token
+	 * previously always considered valid (bug 34212).
+	 *
+	 * @group Broken
+	 */
 	function testMakeNormalBlock() {
 
 		$data = $this->getTokens();
 
-		$user = User::newFromName( 'UTBlockee' );
+		$user = User::newFromName( 'UTApiBlockee' );
 
 		if ( !$user->getId() ) {
-			$this->markTestIncomplete( "The user UTBlockee does not exist" );
+			$this->markTestIncomplete( "The user UTApiBlockee does not exist" );
 		}
 
 		if( !isset( $data[0]['query']['pages'] ) ) {
@@ -45,15 +55,15 @@ class ApiBlockTest extends ApiTestCase {
 
 		$data = $this->doApiRequest( array(
 			'action' => 'block',
-			'user' => 'UTBlockee',
-			'reason' => BlockTest::REASON,
-			'token' => $pageinfo['blocktoken'] ), $data );
+			'user' => 'UTApiBlockee',
+			'reason' => 'Some reason',
+			'token' => $pageinfo['blocktoken'] ), $data, false, self::$users['sysop']->user );
 
-		$block = Block::newFromTarget('UTBlockee');
+		$block = Block::newFromTarget('UTApiBlockee');
 
 		$this->assertTrue( !is_null( $block ), 'Block is valid' );
 
-		$this->assertEquals( 'UTBlockee', (string)$block->getTarget() );
+		$this->assertEquals( 'UTApiBlockee', (string)$block->getTarget() );
 		$this->assertEquals( 'Some reason', $block->mReason );
 		$this->assertEquals( 'infinity', $block->mExpiry );
 
diff --git a/tests/phpunit/includes/api/ApiPurgeTest.php b/tests/phpunit/includes/api/ApiPurgeTest.php
index db1563e9..70c20746 100644
--- a/tests/phpunit/includes/api/ApiPurgeTest.php
+++ b/tests/phpunit/includes/api/ApiPurgeTest.php
@@ -9,33 +9,31 @@ class ApiPurgeTest extends ApiTestCase {
 		parent::setUp();
 		$this->doLogin();
 	}
-	
+
+	/**
+	 * @group Broken
+	 */
 	function testPurgeMainPage() {
-		
 		if ( !Title::newFromText( 'UTPage' )->exists() ) {
 			$this->markTestIncomplete( "The article [[UTPage]] does not exist" );
 		}
-		
+
 		$somePage = mt_rand();
 
 		$data = $this->doApiRequest( array(
 			'action' => 'purge',
 			'titles' => 'UTPage|' . $somePage . '|%5D' ) );
-	
-		$this->assertArrayHasKey( 'purge', $data[0] );
-		
-		$this->assertArrayHasKey( 0, $data[0]['purge'] );
-		$this->assertArrayHasKey( 'purged', $data[0]['purge'][0] );
-		$this->assertEquals( 'UTPage', $data[0]['purge'][0]['title'] );
-		
-		$this->assertArrayHasKey( 1, $data[0]['purge'] );
-		$this->assertArrayHasKey( 'missing', $data[0]['purge'][1] );
-		$this->assertEquals( $somePage, $data[0]['purge'][1]['title'] );
-		
-		$this->assertArrayHasKey( 2, $data[0]['purge'] );
-		$this->assertArrayHasKey( 'invalid', $data[0]['purge'][2] );
-		$this->assertEquals( '%5D', $data[0]['purge'][2]['title'] );
-		
+
+		$this->assertArrayHasKey( 'purge', $data[0],
+			"Must receive a 'purge' result from API" );
+
+		$this->assertEquals( 3, count( $data[0]['purge'] ),
+			"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 ) {
+			$this->assertArrayHasKey( $pages[$v['title']], $v );
+		}
 	}
 
 }
diff --git a/tests/phpunit/includes/api/ApiQueryTest.php b/tests/phpunit/includes/api/ApiQueryTest.php
index 114eadf3..ae05a30a 100644
--- a/tests/phpunit/includes/api/ApiQueryTest.php
+++ b/tests/phpunit/includes/api/ApiQueryTest.php
@@ -22,10 +22,13 @@ class ApiQueryTest extends ApiTestCase {
 		$this->assertArrayHasKey( 'query', $data[0] );
 		$this->assertArrayHasKey( 'normalized', $data[0]['query'] );
 
+		// Forge a normalized title
+		$to = Title::newFromText( $wgMetaNamespace.':ArticleA' );
+
 		$this->assertEquals(
 			array(
 				'from' => 'Project:articleA',
-				'to' => $wgMetaNamespace . ':ArticleA'
+				'to' => $to->getPrefixedText(),
 			),
 			$data[0]['query']['normalized'][0]
 		);
@@ -50,7 +53,6 @@ class ApiQueryTest extends ApiTestCase {
 			'action' => 'query',
 			'titles' => $title . '|Talk:' ) );
 
-
 		$this->assertArrayHasKey( 'query', $data[0] );
 		$this->assertArrayHasKey( 'pages', $data[0]['query'] );
 		$this->assertEquals( 2, count( $data[0]['query']['pages'] ) );
@@ -60,8 +62,6 @@ 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/ApiTest.php b/tests/phpunit/includes/api/ApiTest.php
index a587e6b1..1d9c3238 100644
--- a/tests/phpunit/includes/api/ApiTest.php
+++ b/tests/phpunit/includes/api/ApiTest.php
@@ -96,7 +96,7 @@ class ApiTest extends ApiTestCase {
 			"lgtoken" => $token,
 			"lgname" => $user->username,
 			"lgpassword" => "badnowayinhell",
-			)
+			), $ret[2]
 		);
 
 		$result = $ret[0];
@@ -137,7 +137,7 @@ class ApiTest extends ApiTestCase {
 			"lgtoken" => $token,
 			"lgname" => $user->username,
 			"lgpassword" => $user->password,
-			)
+			), $ret[2]
 		);
 
 		$result = $ret[0];
@@ -148,6 +148,9 @@ class ApiTest extends ApiTestCase {
 		$this->assertEquals( "Success", $a );
 	}
 
+	/**
+	 * @group Broken
+	 */
 	function testApiGotCookie() {
 		$this->markTestIncomplete( "The server can't do external HTTP requests, and the internal one won't give cookies"  );
 
@@ -192,24 +195,23 @@ class ApiTest extends ApiTestCase {
 	}
 
 	/**
-	 * @depends testApiGotCookie
+	 * @todo Finish filling me out...what are we trying to test here?
 	 */
-	function testApiListPages( CookieJar $cj ) {
-		$this->markTestIncomplete( "Not done with this yet" );
+	function testApiListPages() {
 		global $wgServer;
-
-		if ( $wgServer == "http://localhost" ) {
+		if ( !isset( $wgServer ) ) {
 			$this->markTestIncomplete( 'This test needs $wgServer to be set in LocalSettings.php' );
 		}
-		$req = MWHttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&" .
-									 "titles=Main%20Page&rvprop=timestamp|user|comment|content" );
-		$req->setCookieJar( $cj );
-		$req->execute();
-		libxml_use_internal_errors( true );
-		$sxe = simplexml_load_string( $req->getContent() );
-		$this->assertNotInternalType( "bool", $sxe );
-		$this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) );
-		$a = $sxe->query[0]->pages[0]->page[0]->attributes();
+
+		$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() {
@@ -228,7 +230,7 @@ class ApiTest extends ApiTestCase {
 			'action' => 'login',
 			"lgtoken" => $token,
 			"lgname" => $sysopUser->username,
-			"lgpassword" => $sysopUser->password ), $data );
+			"lgpassword" => $sysopUser->password ), $data[2] );
 
 		$this->assertArrayHasKey( "login", $data[0] );
 		$this->assertArrayHasKey( "result", $data[0]['login'] );
diff --git a/tests/phpunit/includes/api/ApiTestCase.php b/tests/phpunit/includes/api/ApiTestCase.php
index 2917c880..8801391f 100644
--- a/tests/phpunit/includes/api/ApiTestCase.php
+++ b/tests/phpunit/includes/api/ApiTestCase.php
@@ -7,6 +7,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 	public static $users;
 	protected static $apiUrl;
 
+	/**
+	 * @var ApiTestContext
+	 */
+	protected $apiContext;
+
 	function setUp() {
 		global $wgContLang, $wgAuth, $wgMemc, $wgRequest, $wgUser, $wgServer;
 
@@ -21,31 +26,37 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 			'sysop' => new ApiTestUser(
 				'Apitestsysop',
 				'Api Test Sysop',
-				'api_test_sysop@sample.com',
+				'api_test_sysop@example.com',
 				array( 'sysop' )
 			),
 			'uploader' => new ApiTestUser(
 				'Apitestuser',
 				'Api Test User',
-				'api_test_user@sample.com',
+				'api_test_user@example.com',
 				array()
 			)
 		);
 
 		$wgUser = self::$users['sysop']->user;
 
+		$this->apiContext = new ApiTestContext();
+
 	}
 
-	protected function doApiRequest( $params, $session = null, $appendModule = false ) {
+	protected function doApiRequest( $params, $session = null, $appendModule = false, $user = null ) {
 		if ( is_null( $session ) ) {
 			$session = array();
 		}
 
-		$request = new FauxRequest( $params, true, $session );
-		$module = new ApiMain( $request, true );
+		$context = $this->apiContext->newTestContext( $params, $session, $user );
+		$module = new ApiMain( $context, true );
 		$module->execute();
 
-		$results = array( $module->getResultData(), $request, $request->getSessionArray() );
+		$results = array(
+			$module->getResultData(),
+			$context->getRequest(),
+			$context->getRequest()->getSessionArray()
+		);
 		if( $appendModule ) {
 			$results[] = $module;
 		}
@@ -59,14 +70,15 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 	 * request, without actually requesting a "real" edit token
 	 * @param $params: key-value API params
 	 * @param $session: session array
+	 * @param $user String|null A User object for the context 
 	 */
-	protected function doApiRequestWithToken( $params, $session ) {
+	protected function doApiRequestWithToken( $params, $session, $user = null ) {
 		if ( $session['wsToken'] ) {
 			// add edit token to fake session
 			$session['wsEditToken'] = $session['wsToken'];
 			// add token to request parameters
 			$params['token'] = md5( $session['wsToken'] ) . User::EDIT_TOKEN_SUFFIX;
-			return $this->doApiRequest( $params, $session );
+			return $this->doApiRequest( $params, $session, false, $user );
 		} else {
 			throw new Exception( "request data not in right format" );
 		}
@@ -91,12 +103,11 @@ abstract class ApiTestCase extends MediaWikiLangTestCase {
 	}
 
 	protected function getTokenList( $user ) {
-		$GLOBALS['wgUser'] = $user->user;
 		$data = $this->doApiRequest( array(
 			'action' => 'query',
 			'titles' => 'Main Page',
 			'intoken' => 'edit|delete|protect|move|block|unblock',
-			'prop' => 'info' ) );
+			'prop' => 'info' ), false, $user->user );
 		return $data;
 	}
 }
@@ -137,3 +148,23 @@ class MockApi extends ApiBase {
 		);
 	}
 }
+
+class ApiTestContext extends RequestContext {
+
+	/**
+	 * Returns a DerivativeContext with the request variables in place
+	 *
+	 * @param $params Array key-value API params
+	 * @param $session Array session data
+	 * @param $user User or null
+	 * @return DerivativeContext
+	 */
+	public function newTestContext( $params, $session, $user = null ) {
+		$context = new DerivativeContext( $this );
+		$context->setRequest( new FauxRequest( $params, true, $session ) );
+		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 e51e7214..39c79547 100644
--- a/tests/phpunit/includes/api/ApiTestCaseUpload.php
+++ b/tests/phpunit/includes/api/ApiTestCaseUpload.php
@@ -19,6 +19,10 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
 		$this->clearFakeUploads();
 	}
 
+	public function tearDown() {
+		$this->clearTempUpload();
+	}
+
 	/**
 	 * Helper function -- remove files and associated articles by Title
 	 * @param $title Title: title to be removed
@@ -33,8 +37,8 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
 			if ( !$status->isGood() ) {
 				return false;
 			}
-			$article = new Article( $title );
-			$article->doDeleteArticle( "removing for test" );
+			$page = WikiPage::factory( $title );
+			$page->doDeleteArticle( "removing for test" );
 
 			// see if it now doesn't exist; reload
 			$title = Title::newFromText( $title->getText(), NS_FILE );
@@ -56,7 +60,7 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
 	 * @param $filePath String: path to file on the filesystem
 	 */
 	public function deleteFileByContent( $filePath ) {
-		$hash = File::sha1Base36( $filePath );
+		$hash = FSFile::getSha1Base36FromPath( $filePath );
 		$dupes = RepoGroup::singleton()->findBySha1( $hash );
 		$success = true;
 		foreach ( $dupes as $dupe ) {
@@ -100,6 +104,36 @@ abstract class ApiTestCaseUpload extends ApiTestCase {
 		return true;
 
 	}
+	function fakeUploadChunk(  $fieldName, $fileName, $type, & $chunkData ){
+		$tmpName = tempnam( wfTempDir(), "" );
+		// 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
+		);
+	}
+
+	function clearTempUpload() {
+		if( isset( $_FILES['file']['tmp_name'] ) ) {
+			$tmp = $_FILES['file']['tmp_name'];
+			if( file_exists( $tmp ) ) {
+				unlink( $tmp );
+			}
+		}
+	}
 
 	/**
 	 * Remove traces of previous fake uploads
diff --git a/tests/phpunit/includes/api/ApiTestUser.php b/tests/phpunit/includes/api/ApiTestUser.php
index df60682f..8d5f61a7 100644
--- a/tests/phpunit/includes/api/ApiTestUser.php
+++ b/tests/phpunit/includes/api/ApiTestUser.php
@@ -8,7 +8,7 @@ class ApiTestUser {
 	public $groups;
 	public $user;
 
-	function __construct( $username, $realname = 'Real Name', $email = 'sample@sample.com', $groups = array() ) {
+	function __construct( $username, $realname = 'Real Name', $email = 'sample@example.com', $groups = array() ) {
 		$this->username = $username;
 		$this->realname = $realname;
 		$this->email = $email;
diff --git a/tests/phpunit/includes/api/ApiUploadTest.php b/tests/phpunit/includes/api/ApiUploadTest.php
index 5c929784..7a700326 100644
--- a/tests/phpunit/includes/api/ApiUploadTest.php
+++ b/tests/phpunit/includes/api/ApiUploadTest.php
@@ -19,6 +19,9 @@ require_once( 'ApiTestCaseUpload.php' );
 
 /**
  * @group Database
+ * @group Broken
+ * Broken test, reports false errors from time to time.
+ * See https://bugzilla.wikimedia.org/26169
  *
  * This is pretty sucky... needs to be prettified.
  */
@@ -54,6 +57,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
 		$this->assertEquals( "Success", $result['login']['result'] );
 		$this->assertArrayHasKey( 'lgtoken', $result['login'] );
 
+		$this->assertNotEmpty( $session, 'API Login must return a session' );
 		return $session;
 
 	}
@@ -78,14 +82,11 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 * @depends testLogin
 	 */
 	public function testUploadMissingParams( $session ) {
-		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
-
 		$exception = false;
 		try {
 			$this->doApiRequestWithToken( array(
 				'action' => 'upload',
-			), $session );
+			), $session, self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$exception = true;
 			$this->assertEquals( "One of the parameters filekey, file, url, statuskey is required",
@@ -99,20 +100,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 * @depends testLogin
 	 */
 	public function testUpload( $session ) {
-		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
-
 		$extension = 'png';
 		$mimeType = 'image/png';
 
 		try {
 			$randomImageGenerator = new RandomImageGenerator();
+			$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
 		}
 		catch ( Exception $e ) {
 			$this->markTestIncomplete( $e->getMessage() );
 		}
 
-		$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
 		$filePath = $filePaths[0];
 		$fileSize = filesize( $filePath );
 		$fileName = basename( $filePath );
@@ -135,7 +133,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -155,9 +154,6 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 * @depends testLogin
 	 */
 	public function testUploadZeroLength( $session ) {
-		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
-
 		$mimeType = 'image/png';
 
 		$filePath = tempnam( wfTempDir(), "" );
@@ -179,7 +175,7 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			$this->doApiRequestWithToken( $params, $session );
+			$this->doApiRequestWithToken( $params, $session, self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$this->assertContains( 'The file you submitted was empty', $e->getMessage() );
 			$exception = true;
@@ -196,20 +192,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 * @depends testLogin
 	 */
 	public function testUploadSameFileName( $session ) {
-		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
-
 		$extension = 'png';
 		$mimeType = 'image/png';
 
 		try {
 			$randomImageGenerator = new RandomImageGenerator();
+			$filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
 		}
 		catch ( Exception $e ) {
 			$this->markTestIncomplete( $e->getMessage() );
 		}
 
-		$filePaths = $randomImageGenerator->writeImages( 2, $extension, wfTempDir() );
 		// we'll reuse this filename
 		$fileName = basename( $filePaths[0] );
 
@@ -233,7 +226,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, , $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -249,7 +243,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, , ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, , ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user ); // FIXME: leaks a temporary file
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -270,19 +265,17 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 * @depends testLogin
 	 */
 	public function testUploadSameContent( $session ) {
-		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
-
 		$extension = 'png';
 		$mimeType = 'image/png';
 
 		try {
 			$randomImageGenerator = new RandomImageGenerator();
+			$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
 		}
 		catch ( Exception $e ) {
 			$this->markTestIncomplete( $e->getMessage() );
 		}
-		$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
+
 		$fileNames[0] = basename( $filePaths[0] );
 		$fileNames[1] = "SameContentAs" . $fileNames[0];
 
@@ -307,7 +300,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -332,7 +326,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user ); // FIXME: leaks a temporary file
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -354,19 +349,19 @@ class ApiUploadTest extends ApiTestCaseUpload {
 	 */
 	public function testUploadStash( $session ) {
 		global $wgUser;
-		$wgUser = self::$users['uploader']->user;
+		$wgUser = self::$users['uploader']->user; // @todo FIXME: still used somewhere
 
 		$extension = 'png';
 		$mimeType = 'image/png';
 
 		try {
 			$randomImageGenerator = new RandomImageGenerator();
+			$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
 		}
 		catch ( Exception $e ) {
 			$this->markTestIncomplete( $e->getMessage() );
 		}
 
-		$filePaths = $randomImageGenerator->writeImages( 1, $extension, wfTempDir() );
 		$filePath = $filePaths[0];
 		$fileSize = filesize( $filePath );
 		$fileName = basename( $filePath );
@@ -389,7 +384,8 @@ class ApiUploadTest extends ApiTestCaseUpload {
 
 		$exception = false;
 		try {
-			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user ); // FIXME: leaks a temporary file
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
@@ -417,17 +413,156 @@ class ApiUploadTest extends ApiTestCaseUpload {
 		$this->clearFakeUploads();
 		$exception = false;
 		try {
-			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session );
+			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user );
 		} catch ( UsageException $e ) {
 			$exception = true;
 		}
 		$this->assertTrue( isset( $result['upload'] ) );
 		$this->assertEquals( 'Success', $result['upload']['result'] );
-		$this->assertFalse( $exception );
+		$this->assertFalse( $exception, "No UsageException exception." );
 
 		// clean up
 		$this->deleteFileByFilename( $fileName );
 		unlink( $filePath );
 	}
-}
+	
+	
+	/**
+	 * @depends testLogin
+	 */
+	public function testUploadChunks( $session ) {
+		global $wgUser;
+		$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 )
+		$mimeType = 'image/jpeg';
+		$url = 'http://upload.wikimedia.org/wikipedia/commons/e/ed/Oberaargletscher_from_Oberaar%2C_2010_07.JPG';
+		$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); 
+			}
+		}
+		catch ( Exception $e ) {
+			$this->markTestIncomplete( $e->getMessage() );
+		}
 
+		$fileSize = filesize( $filePath );
+		$fileName = basename( $filePath );
+
+		$this->deleteFileByFileName( $fileName );
+		$this->deleteFileByContent( $filePath );
+
+		// Base upload params: 
+		$params = array(
+			'action' => 'upload',
+			'stash'	=> 1,
+			'filename' => $fileName,
+			'filesize' => $fileSize,
+			'offset' => 0,
+		);
+		
+		// Upload chunks
+		$chunkSessionKey = false;
+		$resultOffset = 0;
+		// Open the file: 
+		$handle = @fopen ($filePath, "r");
+		if( $handle === false ){
+			$this->markTestIncomplete( "could not open file: $filePath" );
+		}
+		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 ){
+				// Upload fist chunk ( and get the session key )
+				try {
+					list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+						self::$users['uploader']->user );
+				} catch ( UsageException $e ) {
+					$this->markTestIncomplete( $e->getMessage() );
+				}
+				// 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'] ) ){
+					$this->markTestIncomplete( "no filekey provided" );
+				}
+				$chunkSessionKey = $result['upload']['filekey'];
+				$this->assertEquals( 'Continue', $result['upload']['result'] );
+				// First chunk should have chunkSize == offset
+				$this->assertEquals( $chunkSize, $result['upload']['offset'] );
+				$resultOffset = $result['upload']['offset'];
+				continue;
+			}
+			// Filekey set to chunk session
+			$params['filekey'] = $chunkSessionKey;
+			// Update the offset ( always add chunkSize for subquent chunks should be in-sync with $result['upload']['offset'] )
+			$params['offset'] += $chunkSize;
+			// Make sure param offset is insync with resultOffset:
+			$this->assertEquals( $resultOffset, $params['offset'] );
+			// Upload current chunk
+			try {
+				list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+					self::$users['uploader']->user );
+			} catch ( UsageException $e ) {
+				$this->markTestIncomplete( $e->getMessage() );
+			}
+			// 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 ){
+				$this->assertEquals( 'Success', $result['upload']['result'] );
+				break;
+			} else {
+				$this->assertEquals( 'Continue', $result['upload']['result'] );
+				// update $resultOffset
+				$resultOffset = $result['upload']['offset'];
+			}
+		} 
+		fclose ($handle); 
+        
+		// Check that we got a valid file result:
+		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'] ) );
+		$filekey = $result['upload']['filekey'];
+
+		// Now we should try to release the file from stash
+		$params = array(
+			'action' => 'upload',
+			'filekey' => $filekey,
+			'filename' => $fileName,
+			'comment' => 'dummy comment',
+			'text'	=> "This is the page text for $fileName, altered",
+		);
+		$this->clearFakeUploads();
+		$exception = false;
+		try {
+			list( $result, $request, $session ) = $this->doApiRequestWithToken( $params, $session,
+				self::$users['uploader']->user );
+		} catch ( UsageException $e ) {
+			$exception = true;
+		}
+		$this->assertTrue( isset( $result['upload'] ) );
+		$this->assertEquals( 'Success', $result['upload']['result'] );
+		$this->assertFalse( $exception );
+
+		// clean up
+		$this->deleteFileByFilename( $fileName );
+		// 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 3c7ff304..b7803746 100644
--- a/tests/phpunit/includes/api/ApiWatchTest.php
+++ b/tests/phpunit/includes/api/ApiWatchTest.php
@@ -41,6 +41,7 @@ class ApiWatchTest extends ApiTestCase {
 
 	/**
 	 * @depends testWatchEdit
+	 * @group Broken
 	 */
 	function testWatchClear() {
 	
@@ -92,7 +93,9 @@ class ApiWatchTest extends ApiTestCase {
 		$this->assertArrayHasKey( 'edit', $data[0]['protect']['protections'][0] );
 	}
 
-	
+	/**
+	 * @group Broken
+	 */
 	function testGetRollbackToken() {
 		
 		$data = $this->getTokens();
diff --git a/tests/phpunit/includes/api/RandomImageGenerator.php b/tests/phpunit/includes/api/RandomImageGenerator.php
index ae349978..86c0a828 100644
--- a/tests/phpunit/includes/api/RandomImageGenerator.php
+++ b/tests/phpunit/includes/api/RandomImageGenerator.php
@@ -1,6 +1,6 @@
 <?php
 
-/*
+/**
  * RandomImageGenerator -- does what it says on the tin.
  * Requires Imagick, the ImageMagick library for PHP, or the command line equivalent (usually 'convert').
  *
@@ -27,12 +27,11 @@
 class RandomImageGenerator {
 
 	private $dictionaryFile;
-	private $minWidth = 400;
-	private $maxWidth = 800;
-	private $minHeight = 400;
-	private $maxHeight = 800;
-	private $shapesToDraw = 5;
-	private $imageWriteMethod;
+	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
@@ -41,35 +40,35 @@ class RandomImageGenerator {
 	 * (we also would need a non-symmetric shape for the images to test those, like a letter F)
 	 */
 	private static $orientations = array(
-		array( 	
-			'0thRow' => 'top', 	
-			'0thCol' => 'left', 	
-			'exifCode' => 1, 	
-			'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) ) 	 	
+		array(
+			'0thRow' => 'top',
+			'0thCol' => 'left',
+			'exifCode' => 1,
+			'counterRotation' => array( array( 1, 0 ), array( 0, 1 ) )
 		),
-		array( 
+		array(
 			'0thRow' => 'bottom',
-			'0thCol' => 'right', 	
-			'exifCode' => 3, 	
-			'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) ) 		
+			'0thCol' => 'right',
+			'exifCode' => 3,
+			'counterRotation' => array( array( -1, 0 ), array( 0, -1 ) )
 		),
-		array( 
-			'0thRow' => 'right', 
-			'0thCol' => 'top', 		
-			'exifCode' => 6, 	
-			'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) ) 	
+		array(
+			'0thRow' => 'right',
+			'0thCol' => 'top',
+			'exifCode' => 6,
+			'counterRotation' => array( array( 0, 1 ), array( 1, 0 ) )
 		),
-		array( 
-			'0thRow' => 'left', 	
-			'0thCol' => 'bottom', 	
-			'exifCode' => 8, 	
-			'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) ) 		
+		array(
+			'0thRow' => 'left',
+			'0thCol' => 'bottom',
+			'exifCode' => 8,
+			'counterRotation' => array( array( 0, -1 ), array( -1, 0 ) )
 		)
 	);
 
 
 	public function __construct( $options = array() ) {
-		foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxHeight', 'shapesToDraw' ) as $property ) {
+		foreach ( array( 'dictionaryFile', 'minWidth', 'minHeight', 'maxWidth', 'maxHeight', 'shapesToDraw' ) as $property ) {
 			if ( isset( $options[$property] ) ) {
 				$this->$property = $options[$property];
 			}
@@ -77,10 +76,10 @@ class RandomImageGenerator {
 
 		// find the dictionary file, to generate random names
 		if ( !isset( $this->dictionaryFile ) ) {
-			foreach ( array( 
-					'/usr/share/dict/words', 
-					'/usr/dict/words', 
-					dirname( __FILE__ ) . '/words.txt' ) 
+			foreach ( array(
+					'/usr/share/dict/words',
+					'/usr/dict/words',
+					dirname( __FILE__ ) . '/words.txt' )
 					as $dictionaryFile ) {
 				if ( is_file( $dictionaryFile ) and is_readable( $dictionaryFile ) ) {
 					$this->dictionaryFile = $dictionaryFile;
@@ -91,14 +90,6 @@ class RandomImageGenerator {
 		if ( !isset( $this->dictionaryFile ) ) {
 			throw new Exception( "RandomImageGenerator: dictionary file not found or not specified properly" );
 		}
-		
-		if ( !class_exists( 'Imagick' ) ) {
-			throw new Exception( 'No Imagick extension' );
-		}
-		global $wgExiv2Command;
-		if ( !$wgExiv2Command || !is_executable( $wgExiv2Command ) ) {
-			throw new Exception( 'exiv2 not executable or $wgExiv2Command not set' );
-		}
 	}
 
 	/**
@@ -125,15 +116,16 @@ class RandomImageGenerator {
 	 */
 	function getImageWriteMethod( $format ) {
 		global $wgUseImageMagick, $wgImageMagickConvertCommand;
-		if ( $format === 'svg' ) { 
+		if ( $format === 'svg' ) {
 			return 'writeSvg';
 		} else {
 			// figure out how to write images
-			if ( class_exists( 'Imagick' ) ) {
+			global $wgExiv2Command;
+			if ( class_exists( 'Imagick' ) && $wgExiv2Command && is_executable( $wgExiv2Command ) ) {
 				return 'writeImageWithApi';
 			} elseif ( $wgUseImageMagick && $wgImageMagickConvertCommand && is_executable( $wgImageMagickConvertCommand ) ) {
 				return 'writeImageWithCommandLine';
-			} 
+			}
 		}
 		throw new Exception( "RandomImageGenerator: could not find a suitable method to write images in '$format' format" );
 	}
@@ -219,7 +211,7 @@ class RandomImageGenerator {
 	 */
 	static function shapePointsToString( $shape ) {
 		$points = array();
-		foreach ( $shape as $point ) { 
+		foreach ( $shape as $point ) {
 			$points[] = $point['x'] . ',' . $point['y'];
 		}
 		return join( " ", $points );
@@ -232,16 +224,16 @@ class RandomImageGenerator {
 	 * @param $format: file format to write (which is obviously always svg here)
 	 * @param $filename: filename to write to
 	 */
-	public function writeSvg( $spec, $format, $filename ) { 
+	public function writeSvg( $spec, $format, $filename ) {
 		$svg = new SimpleXmlElement( '<svg/>' );
 		$svg->addAttribute( 'xmlns', 'http://www.w3.org/2000/svg' );
-   		$svg->addAttribute( 'version', '1.1' );
- 		$svg->addAttribute( 'width', $spec['width'] );
- 		$svg->addAttribute( 'height', $spec['height'] );
+		$svg->addAttribute( 'version', '1.1' );
+		$svg->addAttribute( 'width', $spec['width'] );
+		$svg->addAttribute( 'height', $spec['height'] );
 		$g = $svg->addChild( 'g' );
 		foreach ( $spec['draws'] as $drawSpec ) {
 			$shape = $g->addChild( 'polygon' );
-			$shape->addAttribute( 'fill', $drawSpec['fill'] );		
+			$shape->addAttribute( 'fill', $drawSpec['fill'] );
 			$shape->addAttribute( 'points', self::shapePointsToString( $drawSpec['shape'] ) );
 		};
 		if ( ! $fh = fopen( $filename, 'w' ) ) {
@@ -260,20 +252,20 @@ class RandomImageGenerator {
 	 * @param $filename: filename to write to
 	 */
 	public function writeImageWithApi( $spec, $format, $filename ) {
-		// this is a hack because I can't get setImageOrientation() to work. See below. 
+		// this is a hack because I can't get setImageOrientation() to work. See below.
 		global $wgExiv2Command;
 
 		$image = new Imagick();
 		/**
-		 * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points 
+		 * If the format is 'jpg', will also add a random orientation -- the image will be drawn rotated with triangle points
 		 * facing in some direction (0, 90, 180 or 270 degrees) and a countering rotation should turn the triangle points upward again
 		 */
 		$orientation = self::$orientations[0]; // default is normal orientation
 		if ( $format == 'jpg' ) {
 			$orientation = self::$orientations[ array_rand( self::$orientations ) ];
-			$spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] ); 
+			$spec = self::rotateImageSpec( $spec, $orientation['counterRotation'] );
 		}
-			
+
 		$image->newImage( $spec['width'], $spec['height'], new ImagickPixel( $spec['fill'] ) );
 
 		foreach ( $spec['draws'] as $drawSpec ) {
@@ -296,7 +288,7 @@ class RandomImageGenerator {
 			$cmd = wfEscapeShellArg( $wgExiv2Command )
 				. " -M "
 				. wfEscapeShellArg( "set Exif.Image.Orientation " . $orientation['exifCode'] )
-				. " " 
+				. " "
 				. wfEscapeShellArg( $filename );
 
 			$retval = 0;
@@ -304,15 +296,13 @@ class RandomImageGenerator {
 			if ( $retval !== 0 ) {
 				print "Error with $cmd: $retval, $err\n";
 			}
-                }
-
-
+		}
 	}
 
 	/**
 	 * Given an image specification, produce rotated version
 	 * This is used when simulating a rotated image capture with EXIF orientation
-	 * @param $spec Object returned by getImageSpec 
+	 * @param $spec Object returned by getImageSpec
 	 * @param $matrix 2x2 transformation matrix
 	 * @return transformed Spec
 	 */
@@ -323,8 +313,8 @@ class RandomImageGenerator {
 		$correctionY = 0;
 		if ( $dims['x'] < 0 ) {
 			$correctionX = abs( $dims['x'] );
-		} 
-		if ( $dims['y'] < 0 ) { 
+		}
+		if ( $dims['y'] < 0 ) {
 			$correctionY = abs( $dims['y'] );
 		}
 		$tSpec['width'] = abs( $dims['x'] );
@@ -332,7 +322,7 @@ class RandomImageGenerator {
 		$tSpec['fill'] = $spec['fill'];
 		$tSpec['draws'] = array();
 		foreach( $spec['draws'] as $draw ) {
-			$tDraw = array( 
+			$tDraw = array(
 				'fill' => $draw['fill'],
 				'shape' => array()
 			);
@@ -349,13 +339,13 @@ class RandomImageGenerator {
 
 	/**
 	 * Given a matrix and a pair of images, return new position
-	 * @param $matrix: 2x2 rotation matrix 
+	 * @param $matrix: 2x2 rotation matrix
 	 * @param $x: x-coordinate number
 	 * @param $y: y-coordinate number
-	 * @return Array transformed with properties x, y 
+	 * @return Array transformed with properties x, y
 	 */
 	private static function matrixMultiply2x2( $matrix, $x, $y ) {
-		return array( 
+		return array(
 			'x' => $x * $matrix[0][0] + $y * $matrix[0][1],
 			'y' => $x * $matrix[1][0] + $y * $matrix[1][1]
 		);
@@ -366,10 +356,10 @@ class RandomImageGenerator {
 	 * Based on an image specification, write such an image to disk, using the command line ImageMagick program ('convert').
 	 *
 	 * 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(99,123,231) circle 59,39 56,57' \
-		 *      -draw 'fill rgb(240,12,32)  circle 50,21 50,3'  filename.png
+	 *  $ 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(99,123,231) circle 59,39 56,57' \
+	 *   -draw 'fill rgb(240,12,32)  circle 50,21 50,3'  filename.png
 	 *
 	 * @param $spec: spec describing background and shapes to draw
 	 * @param $format: file format to write (unused by this method but kept so it has the same signature as writeImageWithApi)
diff --git a/tests/phpunit/includes/db/DatabaseSqliteTest.php b/tests/phpunit/includes/db/DatabaseSqliteTest.php
index 914ab27c..067c731a 100644
--- a/tests/phpunit/includes/db/DatabaseSqliteTest.php
+++ b/tests/phpunit/includes/db/DatabaseSqliteTest.php
@@ -19,6 +19,7 @@ class MockDatabaseSqlite extends DatabaseSqliteStandalone {
 
 /**
  * @group sqlite
+ * @group Database
  */
 class DatabaseSqliteTest extends MediaWikiTestCase {
 	var $db;
@@ -98,7 +99,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 		$this->assertEquals( 'sqlite_master', $db->tableName( 'sqlite_master' ) );
 		$this->assertEquals( 'foobar', $db->tableName( 'bar' ) );
 	}
-	
+
 	public function testDuplicateTableStructure() {
 		$db = new DatabaseSqliteStandalone( ':memory:' );
 		$db->query( 'CREATE TABLE foo(foo, barfoo)' );
@@ -119,7 +120,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 			'Create a temporary duplicate only'
 		);
 	}
-	
+
 	public function testDuplicateTableStructureVirtual() {
 		$db = new DatabaseSqliteStandalone( ':memory:' );
 		if ( $db->getFulltextSearchModule() != 'FTS3' ) {
@@ -191,13 +192,14 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 			'1.15',
 			'1.16',
 			'1.17',
+			'1.18',
 		);
 
 		// Mismatches for these columns we can safely ignore
 		$ignoredColumns = array(
 			'user_newtalk.user_last_timestamp', // r84185
 		);
-			
+
 		$currentDB = new DatabaseSqliteStandalone( ':memory:' );
 		$currentDB->sourceFile( "$IP/maintenance/tables.sql" );
 		$currentTables = $this->getTables( $currentDB );
@@ -254,9 +256,10 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 			$maint = new FakeMaintenance();
 			$maint->loadParamsAndArgs( null, array( 'quiet' => 1 ) );
 		}
-		
+
+		global $IP;
 		$db = new DatabaseSqliteStandalone( ':memory:' );
-		$db->sourceFile( dirname( __FILE__ ) . "/sqlite/tables-$version.sql" );
+		$db->sourceFile( "$IP/tests/phpunit/data/db/sqlite/tables-$version.sql" );
 		$updater = DatabaseUpdater::newForDB( $db, false, $maint );
 		$updater->doUpdates( array( 'core' ) );
 		return $db;
@@ -266,6 +269,7 @@ class DatabaseSqliteTest extends MediaWikiTestCase {
 		$list = array_flip( $db->listTables() );
 		$excluded = array(
 			'math', // moved out of core in 1.18
+			'trackbacks', // removed from core in 1.19
 			'searchindex',
 			'searchindex_content',
 			'searchindex_segments',
diff --git a/tests/phpunit/includes/db/DatabaseTest.php b/tests/phpunit/includes/db/DatabaseTest.php
index d480ac6e..672e6645 100644
--- a/tests/phpunit/includes/db/DatabaseTest.php
+++ b/tests/phpunit/includes/db/DatabaseTest.php
@@ -2,12 +2,20 @@
 
 /**
  * @group Database
+ * @group DatabaseBase
  */
 class DatabaseTest extends MediaWikiTestCase {
-	var $db;
+	var $db, $functionTest = false;
 
 	function setUp() {
-		$this->db = wfGetDB( DB_SLAVE );
+		$this->db = wfGetDB( DB_MASTER );
+	}
+
+	function tearDown() {
+		if ( $this->functionTest ) {
+			$this->dropFunctions();
+			$this->functionTest = false;
+		}
 	}
 
 	function testAddQuotesNull() {
@@ -90,6 +98,26 @@ class DatabaseTest extends MediaWikiTestCase {
 			$sql );
 	}
 
+	/**
+	 * @group Broken
+	 */
+	function testStoredFunctions() {
+		if ( !in_array( wfGetDB( DB_MASTER )->getType(), array( 'mysql', 'postgres' ) ) ) {
+			$this->markTestSkipped( 'MySQL or Postgres required' );
+		}
+		global $IP;
+		$this->dropFunctions();
+		$this->functionTest = true;
+		$this->assertTrue( $this->db->sourceFile( "$IP/tests/phpunit/data/db/{$this->db->getType()}/functions.sql" ) );
+		$res = $this->db->query( 'SELECT mw_test_function() AS test', __METHOD__ );
+		$this->assertEquals( 42, $res->fetchObject()->test );
+	}
+
+	private function dropFunctions() {
+		$this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
+			. ( $this->db->getType() == 'postgres'  ? '()' : '' )
+		);
+	}
 }
 
 
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.13.sql b/tests/phpunit/includes/db/sqlite/tables-1.13.sql
deleted file mode 100644
index a0dcb553..00000000
--- a/tests/phpunit/includes/db/sqlite/tables-1.13.sql
+++ /dev/null
@@ -1,342 +0,0 @@
--- This is a copy of SQLite schema from MediaWiki 1.13 used for updater testing
-
-CREATE TABLE /*$wgDBprefix*/user (
-  user_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  user_name varchar(255)   default '',
-  user_real_name varchar(255)   default '',
-  user_password tinyblob ,
-  user_newpassword tinyblob ,
-  user_newpass_time BLOB,
-  user_email tinytext ,
-  user_options blob ,
-  user_touched BLOB  default '',
-  user_token BLOB  default '',
-  user_email_authenticated BLOB,
-  user_email_token BLOB,
-  user_email_token_expires BLOB,
-  user_registration BLOB,
-  user_editcount int) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/user_groups (
-  ug_user INTEGER  default '0',
-  ug_group varBLOB  default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/user_newtalk (
-  user_id INTEGER  default '0',
-  user_ip varBLOB  default '',
-  user_last_timestamp BLOB  default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/page (
-  page_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  page_namespace INTEGER ,
-  page_title varchar(255)  ,
-  page_restrictions tinyblob ,
-  page_counter bigint  default '0',
-  page_is_redirect tinyint  default '0',
-  page_is_new tinyint  default '0',
-  page_random real ,
-  page_touched BLOB  default '',
-  page_latest INTEGER ,
-  page_len INTEGER ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/revision (
-  rev_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  rev_page INTEGER ,
-  rev_text_id INTEGER ,
-  rev_comment tinyblob ,
-  rev_user INTEGER  default '0',
-  rev_user_text varchar(255)   default '',
-  rev_timestamp BLOB  default '',
-  rev_minor_edit tinyint  default '0',
-  rev_deleted tinyint  default '0',
-  rev_len int,
-  rev_parent_id INTEGER default NULL) /*$wgDBTableOptions*/  ;
-
-CREATE TABLE /*$wgDBprefix*/text (
-  old_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  old_text mediumblob ,
-  old_flags tinyblob ) /*$wgDBTableOptions*/  ;
-
-CREATE TABLE /*$wgDBprefix*/archive (
-  ar_namespace INTEGER  default '0',
-  ar_title varchar(255)   default '',
-  ar_text mediumblob ,
-  ar_comment tinyblob ,
-  ar_user INTEGER  default '0',
-  ar_user_text varchar(255)  ,
-  ar_timestamp BLOB  default '',
-  ar_minor_edit tinyint  default '0',
-  ar_flags tinyblob ,
-  ar_rev_id int,
-  ar_text_id int,
-  ar_deleted tinyint  default '0',
-  ar_len int,
-  ar_page_id int,
-  ar_parent_id INTEGER default NULL) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/pagelinks (
-  pl_from INTEGER  default '0',
-  pl_namespace INTEGER  default '0',
-  pl_title varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/templatelinks (
-  tl_from INTEGER  default '0',
-  tl_namespace INTEGER  default '0',
-  tl_title varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/imagelinks (
-  il_from INTEGER  default '0',
-  il_to varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/categorylinks (
-  cl_from INTEGER  default '0',
-  cl_to varchar(255)   default '',
-  cl_sortkey varchar(70)   default '',
-  cl_timestamp timestamp ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/category (
-  cat_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  cat_title varchar(255)  ,
-  cat_pages INTEGER signed  default 0,
-  cat_subcats INTEGER signed  default 0,
-  cat_files INTEGER signed  default 0,
-  cat_hidden tinyint  default 0) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/externallinks (
-  el_from INTEGER  default '0',
-  el_to blob ,
-  el_index blob ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/langlinks (
-  ll_from INTEGER  default '0',
-  ll_lang varBLOB  default '',
-  ll_title varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/site_stats (
-  ss_row_id INTEGER ,
-  ss_total_views bigint default '0',
-  ss_total_edits bigint default '0',
-  ss_good_articles bigint default '0',
-  ss_total_pages bigint default '-1',
-  ss_users bigint default '-1',
-  ss_admins INTEGER default '-1',
-  ss_images INTEGER default '0') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/hitcounter (
-  hc_id INTEGER 
-)  ;
-
-CREATE TABLE /*$wgDBprefix*/ipblocks (
-  ipb_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  ipb_address tinyblob ,
-  ipb_user INTEGER  default '0',
-  ipb_by INTEGER  default '0',
-  ipb_by_text varchar(255)   default '',
-  ipb_reason tinyblob ,
-  ipb_timestamp BLOB  default '',
-  ipb_auto bool  default 0,
-  ipb_anon_only bool  default 0,
-  ipb_create_account bool  default 1,
-  ipb_enable_autoblock bool  default '1',
-  ipb_expiry varBLOB  default '',
-  ipb_range_start tinyblob ,
-  ipb_range_end tinyblob ,
-  ipb_deleted bool  default 0,
-  ipb_block_email bool  default 0) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/image (
-  img_name varchar(255)   default '',
-  img_size INTEGER  default '0',
-  img_width INTEGER  default '0',
-  img_height INTEGER  default '0',
-  img_metadata mediumblob ,
-  img_bits INTEGER  default '0',
-  img_media_type TEXT default NULL,
-  img_major_mime TEXT  default "unknown",
-  img_minor_mime varBLOB  default "unknown",
-  img_description tinyblob ,
-  img_user INTEGER  default '0',
-  img_user_text varchar(255)  ,
-  img_timestamp varBLOB  default '',
-  img_sha1 varBLOB  default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/oldimage (
-  oi_name varchar(255)   default '',
-  oi_archive_name varchar(255)   default '',
-  oi_size INTEGER  default 0,
-  oi_width INTEGER  default 0,
-  oi_height INTEGER  default 0,
-  oi_bits INTEGER  default 0,
-  oi_description tinyblob ,
-  oi_user INTEGER  default '0',
-  oi_user_text varchar(255)  ,
-  oi_timestamp BLOB  default '',
-  oi_metadata mediumblob ,
-  oi_media_type TEXT default NULL,
-  oi_major_mime TEXT  default "unknown",
-  oi_minor_mime varBLOB  default "unknown",
-  oi_deleted tinyint  default '0',
-  oi_sha1 varBLOB  default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/filearchive (
-  fa_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  fa_name varchar(255)   default '',
-  fa_archive_name varchar(255)  default '',
-  fa_storage_group varBLOB,
-  fa_storage_key varBLOB default '',
-  fa_deleted_user int,
-  fa_deleted_timestamp BLOB default '',
-  fa_deleted_reason text,
-  fa_size INTEGER default '0',
-  fa_width INTEGER default '0',
-  fa_height INTEGER default '0',
-  fa_metadata mediumblob,
-  fa_bits INTEGER default '0',
-  fa_media_type TEXT default NULL,
-  fa_major_mime TEXT default "unknown",
-  fa_minor_mime varBLOB default "unknown",
-  fa_description tinyblob,
-  fa_user INTEGER default '0',
-  fa_user_text varchar(255) ,
-  fa_timestamp BLOB default '',
-  fa_deleted tinyint  default '0') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/recentchanges (
-  rc_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  rc_timestamp varBLOB  default '',
-  rc_cur_time varBLOB  default '',
-  rc_user INTEGER  default '0',
-  rc_user_text varchar(255)  ,
-  rc_namespace INTEGER  default '0',
-  rc_title varchar(255)   default '',
-  rc_comment varchar(255)   default '',
-  rc_minor tinyint  default '0',
-  rc_bot tinyint  default '0',
-  rc_new tinyint  default '0',
-  rc_cur_id INTEGER  default '0',
-  rc_this_oldid INTEGER  default '0',
-  rc_last_oldid INTEGER  default '0',
-  rc_type tinyint  default '0',
-  rc_moved_to_ns tinyint  default '0',
-  rc_moved_to_title varchar(255)   default '',
-  rc_patrolled tinyint  default '0',
-  rc_ip varBLOB  default '',
-  rc_old_len int,
-  rc_new_len int,
-  rc_deleted tinyint  default '0',
-  rc_logid INTEGER  default '0',
-  rc_log_type varBLOB NULL default NULL,
-  rc_log_action varBLOB NULL default NULL,
-  rc_params blob NULL) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/watchlist (
-  wl_user INTEGER ,
-  wl_namespace INTEGER  default '0',
-  wl_title varchar(255)   default '',
-  wl_notificationtimestamp varBLOB) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/math (
-  math_inputhash varBLOB ,
-  math_outputhash varBLOB ,
-  math_html_conservativeness tinyint ,
-  math_html text,
-  math_mathml text) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/searchindex (
-  si_page INTEGER ,
-  si_title varchar(255)  default '',
-  si_text mediumtext ) ;
-
-CREATE TABLE /*$wgDBprefix*/interwiki (
-  iw_prefix varchar(32) ,
-  iw_url blob ,
-  iw_local bool ,
-  iw_trans tinyint  default 0) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/querycache (
-  qc_type varBLOB ,
-  qc_value INTEGER  default '0',
-  qc_namespace INTEGER  default '0',
-  qc_title varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/objectcache (
-  keyname varBLOB  default '',
-  value mediumblob,
-  exptime datetime) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/transcache (
-  tc_url varBLOB ,
-  tc_contents text,
-  tc_time INTEGER ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/logging (
-  log_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  log_type varBLOB  default '',
-  log_action varBLOB  default '',
-  log_timestamp BLOB  default '19700101000000',
-  log_user INTEGER  default 0,
-  log_namespace INTEGER  default 0,
-  log_title varchar(255)   default '',
-  log_comment varchar(255)  default '',
-  log_params blob ,
-  log_deleted tinyint  default '0') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/trackbacks (
-  tb_id INTEGER PRIMARY KEY AUTOINCREMENT,
-  tb_page INTEGER REFERENCES /*$wgDBprefix*/page(page_id) ON DELETE CASCADE,
-  tb_title varchar(255) ,
-  tb_url blob ,
-  tb_ex text,
-  tb_name varchar(255)) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/job (
-  job_id INTEGER  PRIMARY KEY AUTOINCREMENT,
-  job_cmd varBLOB  default '',
-  job_namespace INTEGER ,
-  job_title varchar(255)  ,
-  job_params blob ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/querycache_info (
-  qci_type varBLOB  default '',
-  qci_timestamp BLOB  default '19700101000000') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/redirect (
-  rd_from INTEGER  default '0',
-  rd_namespace INTEGER  default '0',
-  rd_title varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/querycachetwo (
-  qcc_type varBLOB ,
-  qcc_value INTEGER  default '0',
-  qcc_namespace INTEGER  default '0',
-  qcc_title varchar(255)   default '',
-  qcc_namespacetwo INTEGER  default '0',
-  qcc_titletwo varchar(255)   default '') /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/page_restrictions (
-  pr_page INTEGER ,
-  pr_type varBLOB ,
-  pr_level varBLOB ,
-  pr_cascade tinyint ,
-  pr_user INTEGER NULL,
-  pr_expiry varBLOB NULL,
-  pr_id INTEGER  PRIMARY KEY AUTOINCREMENT) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/protected_titles (
-  pt_namespace INTEGER ,
-  pt_title varchar(255)  ,
-  pt_user INTEGER ,
-  pt_reason tinyblob,
-  pt_timestamp BLOB ,
-  pt_expiry varBLOB  default '',
-  pt_create_perm varBLOB ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/page_props (
-  pp_page INTEGER ,
-  pp_propname varBLOB ,
-  pp_value blob ) /*$wgDBTableOptions*/;
-
-CREATE TABLE /*$wgDBprefix*/updatelog (
-  ul_key varchar(255) ) /*$wgDBTableOptions*/;
-
-
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.15.sql b/tests/phpunit/includes/db/sqlite/tables-1.15.sql
deleted file mode 100644
index 901bac52..00000000
--- a/tests/phpunit/includes/db/sqlite/tables-1.15.sql
+++ /dev/null
@@ -1,454 +0,0 @@
--- This is a copy of MediaWiki 1.15 schema shared by MySQL and SQLite.
--- It is used for updater testing. Comments are stripped to decrease
--- file size, as we don't need to maintain it.
-
-CREATE TABLE /*_*/user (
-  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  user_name varchar(255) binary NOT NULL default '',
-  user_real_name varchar(255) binary NOT NULL default '',
-  user_password tinyblob NOT NULL,
-  user_newpassword tinyblob NOT NULL,
-  user_newpass_time binary(14),
-  user_email tinytext NOT NULL,
-  user_options blob NOT NULL,
-  user_touched binary(14) NOT NULL default '',
-  user_token binary(32) NOT NULL default '',
-  user_email_authenticated binary(14),
-  user_email_token binary(32),
-  user_email_token_expires binary(14),
-  user_registration binary(14),
-  user_editcount int
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
-CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
-CREATE TABLE /*_*/user_groups (
-  ug_user int unsigned NOT NULL default 0,
-  ug_group varbinary(16) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
-CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
-CREATE TABLE /*_*/user_newtalk (
-  user_id int NOT NULL default 0,
-  user_ip varbinary(40) NOT NULL default '',
-  user_last_timestamp binary(14) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
-CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
-CREATE TABLE /*_*/page (
-  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  page_namespace int NOT NULL,
-  page_title varchar(255) binary NOT NULL,
-  page_restrictions tinyblob NOT NULL,
-  page_counter bigint unsigned NOT NULL default 0,
-  page_is_redirect tinyint unsigned NOT NULL default 0,
-  page_is_new tinyint unsigned NOT NULL default 0,
-  page_random real unsigned NOT NULL,
-  page_touched binary(14) NOT NULL default '',
-  page_latest int unsigned NOT NULL,
-  page_len int unsigned NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
-CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
-CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
-CREATE TABLE /*_*/revision (
-  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rev_page int unsigned NOT NULL,
-  rev_text_id int unsigned NOT NULL,
-  rev_comment tinyblob NOT NULL,
-  rev_user int unsigned NOT NULL default 0,
-  rev_user_text varchar(255) binary NOT NULL default '',
-  rev_timestamp binary(14) NOT NULL default '',
-  rev_minor_edit tinyint unsigned NOT NULL default 0,
-  rev_deleted tinyint unsigned NOT NULL default 0,
-  rev_len int unsigned,
-  rev_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
-CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
-CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
-CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
-CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
-CREATE TABLE /*_*/text (
-  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  old_text mediumblob NOT NULL,
-  old_flags tinyblob NOT NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
-CREATE TABLE /*_*/archive (
-  ar_namespace int NOT NULL default 0,
-  ar_title varchar(255) binary NOT NULL default '',
-  ar_text mediumblob NOT NULL,
-  ar_comment tinyblob NOT NULL,
-  ar_user int unsigned NOT NULL default 0,
-  ar_user_text varchar(255) binary NOT NULL,
-  ar_timestamp binary(14) NOT NULL default '',
-  ar_minor_edit tinyint NOT NULL default 0,
-  ar_flags tinyblob NOT NULL,
-  ar_rev_id int unsigned,
-  ar_text_id int unsigned,
-  ar_deleted tinyint unsigned NOT NULL default 0,
-  ar_len int unsigned,
-  ar_page_id int unsigned,
-  ar_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
-CREATE TABLE /*_*/pagelinks (
-  pl_from int unsigned NOT NULL default 0,
-  pl_namespace int NOT NULL default 0,
-  pl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
-CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
-CREATE TABLE /*_*/templatelinks (
-  tl_from int unsigned NOT NULL default 0,
-  tl_namespace int NOT NULL default 0,
-  tl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
-CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
-CREATE TABLE /*_*/imagelinks (
-  il_from int unsigned NOT NULL default 0,
-  il_to varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
-CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
-CREATE TABLE /*_*/categorylinks (
-  cl_from int unsigned NOT NULL default 0,
-  cl_to varchar(255) binary NOT NULL default '',
-  cl_sortkey varchar(70) binary NOT NULL default '',
-  cl_timestamp timestamp NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
-CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_sortkey,cl_from);
-CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
-CREATE TABLE /*_*/category (
-  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  cat_title varchar(255) binary NOT NULL,
-  cat_pages int signed NOT NULL default 0,
-  cat_subcats int signed NOT NULL default 0,
-  cat_files int signed NOT NULL default 0,
-  cat_hidden tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
-CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
-CREATE TABLE /*_*/externallinks (
-  el_from int unsigned NOT NULL default 0,
-  el_to blob NOT NULL,
-  el_index blob NOT NULL
-) /*$wgDBTableOptions*/;
-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 /*_*/langlinks (
-  ll_from int unsigned NOT NULL default 0,
-  
-  ll_lang varbinary(20) NOT NULL default '',
-  ll_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
-CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
-CREATE TABLE /*_*/site_stats (
-  ss_row_id int unsigned NOT NULL,
-  ss_total_views bigint unsigned default 0,
-  ss_total_edits bigint unsigned default 0,
-  ss_good_articles bigint unsigned default 0,
-  ss_total_pages bigint default '-1',
-  ss_users bigint default '-1',
-  ss_active_users bigint default '-1',
-  ss_admins int default '-1',
-  ss_images int default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
-CREATE TABLE /*_*/hitcounter (
-  hc_id int unsigned NOT NULL
-) ENGINE=HEAP MAX_ROWS=25000;
-CREATE TABLE /*_*/ipblocks (
-  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  ipb_address tinyblob NOT NULL,
-  ipb_user int unsigned NOT NULL default 0,
-  ipb_by int unsigned NOT NULL default 0,
-  ipb_by_text varchar(255) binary NOT NULL default '',
-  ipb_reason tinyblob NOT NULL,
-  ipb_timestamp binary(14) NOT NULL default '',
-  ipb_auto bool NOT NULL default 0,
-  ipb_anon_only bool NOT NULL default 0,
-  ipb_create_account bool NOT NULL default 1,
-  ipb_enable_autoblock bool NOT NULL default '1',
-  ipb_expiry varbinary(14) NOT NULL default '',
-  ipb_range_start tinyblob NOT NULL,
-  ipb_range_end tinyblob NOT NULL,
-  ipb_deleted bool NOT NULL default 0,
-  ipb_block_email bool NOT NULL default 0,
-  ipb_allow_usertalk bool NOT NULL default 0
-) /*$wgDBTableOptions*/;
-  
-CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
-CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
-CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
-CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
-CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
-CREATE TABLE /*_*/image (
-  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
-  img_size int unsigned NOT NULL default 0,
-  img_width int NOT NULL default 0,
-  img_height int NOT NULL default 0,
-  img_metadata mediumblob NOT NULL,
-  img_bits int NOT NULL default 0,
-  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  img_minor_mime varbinary(32) NOT NULL default "unknown",
-  img_description tinyblob NOT NULL,
-  img_user int unsigned NOT NULL default 0,
-  img_user_text varchar(255) binary NOT NULL,
-  img_timestamp varbinary(14) NOT NULL default '',
-  img_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
-CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
-CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
-CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
-CREATE TABLE /*_*/oldimage (
-  oi_name varchar(255) binary NOT NULL default '',
-  oi_archive_name varchar(255) binary NOT NULL default '',
-  oi_size int unsigned NOT NULL default 0,
-  oi_width int NOT NULL default 0,
-  oi_height int NOT NULL default 0,
-  oi_bits int NOT NULL default 0,
-  oi_description tinyblob NOT NULL,
-  oi_user int unsigned NOT NULL default 0,
-  oi_user_text varchar(255) binary NOT NULL,
-  oi_timestamp binary(14) NOT NULL default '',
-  oi_metadata mediumblob NOT NULL,
-  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  oi_minor_mime varbinary(32) NOT NULL default "unknown",
-  oi_deleted tinyint unsigned NOT NULL default 0,
-  oi_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
-CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
-CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
-CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
-CREATE TABLE /*_*/filearchive (
-  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  fa_name varchar(255) binary NOT NULL default '',
-  fa_archive_name varchar(255) binary default '',
-  fa_storage_group varbinary(16),
-  fa_storage_key varbinary(64) default '',
-  fa_deleted_user int,
-  fa_deleted_timestamp binary(14) default '',
-  fa_deleted_reason text,
-  fa_size int unsigned default 0,
-  fa_width int default 0,
-  fa_height int default 0,
-  fa_metadata mediumblob,
-  fa_bits int default 0,
-  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
-  fa_minor_mime varbinary(32) default "unknown",
-  fa_description tinyblob,
-  fa_user int unsigned default 0,
-  fa_user_text varchar(255) binary,
-  fa_timestamp binary(14) default '',
-  fa_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
-CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
-CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
-CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
-CREATE TABLE /*_*/recentchanges (
-  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rc_timestamp varbinary(14) NOT NULL default '',
-  rc_cur_time varbinary(14) NOT NULL default '',
-  rc_user int unsigned NOT NULL default 0,
-  rc_user_text varchar(255) binary NOT NULL,
-  rc_namespace int NOT NULL default 0,
-  rc_title varchar(255) binary NOT NULL default '',
-  rc_comment varchar(255) binary NOT NULL default '',
-  rc_minor tinyint unsigned NOT NULL default 0,
-  rc_bot tinyint unsigned NOT NULL default 0,
-  rc_new tinyint unsigned NOT NULL default 0,
-  rc_cur_id int unsigned NOT NULL default 0,
-  rc_this_oldid int unsigned NOT NULL default 0,
-  rc_last_oldid int unsigned NOT NULL default 0,
-  rc_type tinyint unsigned NOT NULL default 0,
-  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
-  rc_moved_to_title varchar(255) binary NOT NULL default '',
-  rc_patrolled tinyint unsigned NOT NULL default 0,
-  rc_ip varbinary(40) NOT NULL default '',
-  rc_old_len int,
-  rc_new_len int,
-  rc_deleted tinyint unsigned NOT NULL default 0,
-  rc_logid int unsigned NOT NULL default 0,
-  rc_log_type varbinary(255) NULL default NULL,
-  rc_log_action varbinary(255) NULL default NULL,
-  rc_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
-CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
-CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
-CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
-CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
-CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
-CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
-CREATE TABLE /*_*/watchlist (
-  wl_user int unsigned NOT NULL,
-  wl_namespace int NOT NULL default 0,
-  wl_title varchar(255) binary NOT NULL default '',
-  wl_notificationtimestamp varbinary(14)
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
-CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
-CREATE TABLE /*_*/math (
-  math_inputhash varbinary(16) NOT NULL,
-  math_outputhash varbinary(16) NOT NULL,
-  math_html_conservativeness tinyint NOT NULL,
-  math_html text,
-  math_mathml text
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
-CREATE TABLE /*_*/searchindex (
-  si_page int unsigned NOT NULL,
-  si_title varchar(255) NOT NULL default '',
-  si_text mediumtext NOT NULL
-) ENGINE=MyISAM;
-CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
-CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
-CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
-CREATE TABLE /*_*/interwiki (
-  iw_prefix varchar(32) NOT NULL,
-  iw_url blob NOT NULL,
-  iw_local bool NOT NULL,
-  iw_trans tinyint NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
-CREATE TABLE /*_*/querycache (
-  qc_type varbinary(32) NOT NULL,
-  qc_value int unsigned NOT NULL default 0,
-  qc_namespace int NOT NULL default 0,
-  qc_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
-CREATE TABLE /*_*/objectcache (
-  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
-  value mediumblob,
-  exptime datetime
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
-CREATE TABLE /*_*/transcache (
-  tc_url varbinary(255) NOT NULL,
-  tc_contents text,
-  tc_time int NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
-CREATE TABLE /*_*/logging (
-  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  log_type varbinary(10) NOT NULL default '',
-  log_action varbinary(10) NOT NULL default '',
-  log_timestamp binary(14) NOT NULL default '19700101000000',
-  log_user int unsigned NOT NULL default 0,
-  log_namespace int NOT NULL default 0,
-  log_title varchar(255) binary NOT NULL default '',
-  log_comment varchar(255) NOT NULL default '',
-  log_params blob NOT NULL,
-  log_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
-CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
-CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
-CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
-CREATE TABLE /*_*/trackbacks (
-  tb_id int PRIMARY KEY AUTO_INCREMENT,
-  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
-  tb_title varchar(255) NOT NULL,
-  tb_url blob NOT NULL,
-  tb_ex text,
-  tb_name varchar(255)
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
-CREATE TABLE /*_*/job (
-  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  job_cmd varbinary(60) NOT NULL default '',
-  job_namespace int NOT NULL,
-  job_title varchar(255) binary NOT NULL,
-  job_params blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title);
-CREATE TABLE /*_*/querycache_info (
-  qci_type varbinary(32) NOT NULL default '',
-  qci_timestamp binary(14) NOT NULL default '19700101000000'
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
-CREATE TABLE /*_*/redirect (
-  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
-  rd_namespace int NOT NULL default 0,
-  rd_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
-CREATE TABLE /*_*/querycachetwo (
-  qcc_type varbinary(32) NOT NULL,
-  qcc_value int unsigned NOT NULL default 0,
-  qcc_namespace int NOT NULL default 0,
-  qcc_title varchar(255) binary NOT NULL default '',
-  qcc_namespacetwo int NOT NULL default 0,
-  qcc_titletwo varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
-CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
-CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
-CREATE TABLE /*_*/page_restrictions (
-  pr_page int NOT NULL,
-  pr_type varbinary(60) NOT NULL,
-  pr_level varbinary(60) NOT NULL,
-  pr_cascade tinyint NOT NULL,
-  pr_user int NULL,
-  pr_expiry varbinary(14) NULL,
-  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
-CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
-CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
-CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
-CREATE TABLE /*_*/protected_titles (
-  pt_namespace int NOT NULL,
-  pt_title varchar(255) binary NOT NULL,
-  pt_user int unsigned NOT NULL,
-  pt_reason tinyblob,
-  pt_timestamp binary(14) NOT NULL,
-  pt_expiry varbinary(14) NOT NULL default '',
-  pt_create_perm varbinary(60) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
-CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
-CREATE TABLE /*_*/page_props (
-  pp_page int NOT NULL,
-  pp_propname varbinary(60) NOT NULL,
-  pp_value blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
-CREATE TABLE /*_*/updatelog (
-  ul_key varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
-CREATE TABLE /*_*/change_tag (
-  ct_rc_id int NULL,
-  ct_log_id int NULL,
-  ct_rev_id int NULL,
-  ct_tag varchar(255) NOT NULL,
-  ct_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
-CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
-CREATE TABLE /*_*/tag_summary (
-  ts_rc_id int NULL,
-  ts_log_id int NULL,
-  ts_rev_id int NULL,
-  ts_tags blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
-CREATE TABLE /*_*/valid_tag (
-  vt_tag varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.16.sql b/tests/phpunit/includes/db/sqlite/tables-1.16.sql
deleted file mode 100644
index 6e56add2..00000000
--- a/tests/phpunit/includes/db/sqlite/tables-1.16.sql
+++ /dev/null
@@ -1,483 +0,0 @@
--- This is a copy of MediaWiki 1.16 schema shared by MySQL and SQLite.
--- It is used for updater testing. Comments are stripped to decrease
--- file size, as we don't need to maintain it.
-
-CREATE TABLE /*_*/user (
-  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  user_name varchar(255) binary NOT NULL default '',
-  user_real_name varchar(255) binary NOT NULL default '',
-  user_password tinyblob NOT NULL,
-  user_newpassword tinyblob NOT NULL,
-  user_newpass_time binary(14),
-  user_email tinytext NOT NULL,
-  user_options blob NOT NULL,
-  user_touched binary(14) NOT NULL default '',
-  user_token binary(32) NOT NULL default '',
-  user_email_authenticated binary(14),
-  user_email_token binary(32),
-  user_email_token_expires binary(14),
-  user_registration binary(14),
-  user_editcount int
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
-CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
-CREATE TABLE /*_*/user_groups (
-  ug_user int unsigned NOT NULL default 0,
-  ug_group varbinary(16) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
-CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
-CREATE TABLE /*_*/user_newtalk (
-  user_id int NOT NULL default 0,
-  user_ip varbinary(40) NOT NULL default '',
-  user_last_timestamp binary(14) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
-CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
-CREATE TABLE /*_*/user_properties (
-  up_user int NOT NULL,
-  up_property varbinary(32) NOT NULL,
-  up_value blob
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
-CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
-CREATE TABLE /*_*/page (
-  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  page_namespace int NOT NULL,
-  page_title varchar(255) binary NOT NULL,
-  page_restrictions tinyblob NOT NULL,
-  page_counter bigint unsigned NOT NULL default 0,
-  page_is_redirect tinyint unsigned NOT NULL default 0,
-  page_is_new tinyint unsigned NOT NULL default 0,
-  page_random real unsigned NOT NULL,
-  page_touched binary(14) NOT NULL default '',
-  page_latest int unsigned NOT NULL,
-  page_len int unsigned NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
-CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
-CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
-CREATE TABLE /*_*/revision (
-  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rev_page int unsigned NOT NULL,
-  rev_text_id int unsigned NOT NULL,
-  rev_comment tinyblob NOT NULL,
-  rev_user int unsigned NOT NULL default 0,
-  rev_user_text varchar(255) binary NOT NULL default '',
-  rev_timestamp binary(14) NOT NULL default '',
-  rev_minor_edit tinyint unsigned NOT NULL default 0,
-  rev_deleted tinyint unsigned NOT NULL default 0,
-  rev_len int unsigned,
-  rev_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
-CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
-CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
-CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
-CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
-CREATE TABLE /*_*/text (
-  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  old_text mediumblob NOT NULL,
-  old_flags tinyblob NOT NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
-CREATE TABLE /*_*/archive (
-  ar_namespace int NOT NULL default 0,
-  ar_title varchar(255) binary NOT NULL default '',
-  ar_text mediumblob NOT NULL,
-  ar_comment tinyblob NOT NULL,
-  ar_user int unsigned NOT NULL default 0,
-  ar_user_text varchar(255) binary NOT NULL,
-  ar_timestamp binary(14) NOT NULL default '',
-  ar_minor_edit tinyint NOT NULL default 0,
-  ar_flags tinyblob NOT NULL,
-  ar_rev_id int unsigned,
-  ar_text_id int unsigned,
-  ar_deleted tinyint unsigned NOT NULL default 0,
-  ar_len int unsigned,
-  ar_page_id int unsigned,
-  ar_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
-CREATE TABLE /*_*/pagelinks (
-  pl_from int unsigned NOT NULL default 0,
-  pl_namespace int NOT NULL default 0,
-  pl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
-CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
-CREATE TABLE /*_*/templatelinks (
-  tl_from int unsigned NOT NULL default 0,
-  tl_namespace int NOT NULL default 0,
-  tl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
-CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
-CREATE TABLE /*_*/imagelinks (
-  il_from int unsigned NOT NULL default 0,
-  il_to varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
-CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
-CREATE TABLE /*_*/categorylinks (
-  cl_from int unsigned NOT NULL default 0,
-  cl_to varchar(255) binary NOT NULL default '',
-  cl_sortkey varchar(70) binary NOT NULL default '',
-  cl_timestamp timestamp NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
-CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_sortkey,cl_from);
-CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
-CREATE TABLE /*_*/category (
-  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  cat_title varchar(255) binary NOT NULL,
-  cat_pages int signed NOT NULL default 0,
-  cat_subcats int signed NOT NULL default 0,
-  cat_files int signed NOT NULL default 0,
-  cat_hidden tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
-CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
-CREATE TABLE /*_*/externallinks (
-  el_from int unsigned NOT NULL default 0,
-  el_to blob NOT NULL,
-  el_index blob NOT NULL
-) /*$wgDBTableOptions*/;
-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 '',
-  ll_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
-CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
-CREATE TABLE /*_*/site_stats (
-  ss_row_id int unsigned NOT NULL,
-  ss_total_views bigint unsigned default 0,
-  ss_total_edits bigint unsigned default 0,
-  ss_good_articles bigint unsigned default 0,
-  ss_total_pages bigint default '-1',
-  ss_users bigint default '-1',
-  ss_active_users bigint default '-1',
-  ss_admins int default '-1',
-  ss_images int default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
-CREATE TABLE /*_*/hitcounter (
-  hc_id int unsigned NOT NULL
-) ENGINE=HEAP MAX_ROWS=25000;
-CREATE TABLE /*_*/ipblocks (
-  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  ipb_address tinyblob NOT NULL,
-  ipb_user int unsigned NOT NULL default 0,
-  ipb_by int unsigned NOT NULL default 0,
-  ipb_by_text varchar(255) binary NOT NULL default '',
-  ipb_reason tinyblob NOT NULL,
-  ipb_timestamp binary(14) NOT NULL default '',
-  ipb_auto bool NOT NULL default 0,
-  ipb_anon_only bool NOT NULL default 0,
-  ipb_create_account bool NOT NULL default 1,
-  ipb_enable_autoblock bool NOT NULL default '1',
-  ipb_expiry varbinary(14) NOT NULL default '',
-  ipb_range_start tinyblob NOT NULL,
-  ipb_range_end tinyblob NOT NULL,
-  ipb_deleted bool NOT NULL default 0,
-  ipb_block_email bool NOT NULL default 0,
-  ipb_allow_usertalk bool NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
-CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
-CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
-CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
-CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
-CREATE TABLE /*_*/image (
-  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
-  img_size int unsigned NOT NULL default 0,
-  img_width int NOT NULL default 0,
-  img_height int NOT NULL default 0,
-  img_metadata mediumblob NOT NULL,
-  img_bits int NOT NULL default 0,
-  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  img_minor_mime varbinary(100) NOT NULL default "unknown",
-  img_description tinyblob NOT NULL,
-  img_user int unsigned NOT NULL default 0,
-  img_user_text varchar(255) binary NOT NULL,
-  img_timestamp varbinary(14) NOT NULL default '',
-  img_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
-CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
-CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
-CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
-CREATE TABLE /*_*/oldimage (
-  oi_name varchar(255) binary NOT NULL default '',
-  oi_archive_name varchar(255) binary NOT NULL default '',
-  oi_size int unsigned NOT NULL default 0,
-  oi_width int NOT NULL default 0,
-  oi_height int NOT NULL default 0,
-  oi_bits int NOT NULL default 0,
-  oi_description tinyblob NOT NULL,
-  oi_user int unsigned NOT NULL default 0,
-  oi_user_text varchar(255) binary NOT NULL,
-  oi_timestamp binary(14) NOT NULL default '',
-  oi_metadata mediumblob NOT NULL,
-  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  oi_minor_mime varbinary(100) NOT NULL default "unknown",
-  oi_deleted tinyint unsigned NOT NULL default 0,
-  oi_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
-CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
-CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
-CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
-CREATE TABLE /*_*/filearchive (
-  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  fa_name varchar(255) binary NOT NULL default '',
-  fa_archive_name varchar(255) binary default '',
-  fa_storage_group varbinary(16),
-  fa_storage_key varbinary(64) default '',
-  fa_deleted_user int,
-  fa_deleted_timestamp binary(14) default '',
-  fa_deleted_reason text,
-  fa_size int unsigned default 0,
-  fa_width int default 0,
-  fa_height int default 0,
-  fa_metadata mediumblob,
-  fa_bits int default 0,
-  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
-  fa_minor_mime varbinary(100) default "unknown",
-  fa_description tinyblob,
-  fa_user int unsigned default 0,
-  fa_user_text varchar(255) binary,
-  fa_timestamp binary(14) default '',
-  fa_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
-CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
-CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
-CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
-CREATE TABLE /*_*/recentchanges (
-  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rc_timestamp varbinary(14) NOT NULL default '',
-  rc_cur_time varbinary(14) NOT NULL default '',
-  rc_user int unsigned NOT NULL default 0,
-  rc_user_text varchar(255) binary NOT NULL,
-  rc_namespace int NOT NULL default 0,
-  rc_title varchar(255) binary NOT NULL default '',
-  rc_comment varchar(255) binary NOT NULL default '',
-  rc_minor tinyint unsigned NOT NULL default 0,
-  rc_bot tinyint unsigned NOT NULL default 0,
-  rc_new tinyint unsigned NOT NULL default 0,
-  rc_cur_id int unsigned NOT NULL default 0,
-  rc_this_oldid int unsigned NOT NULL default 0,
-  rc_last_oldid int unsigned NOT NULL default 0,
-  rc_type tinyint unsigned NOT NULL default 0,
-  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
-  rc_moved_to_title varchar(255) binary NOT NULL default '',
-  rc_patrolled tinyint unsigned NOT NULL default 0,
-  rc_ip varbinary(40) NOT NULL default '',
-  rc_old_len int,
-  rc_new_len int,
-  rc_deleted tinyint unsigned NOT NULL default 0,
-  rc_logid int unsigned NOT NULL default 0,
-  rc_log_type varbinary(255) NULL default NULL,
-  rc_log_action varbinary(255) NULL default NULL,
-  rc_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
-CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
-CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
-CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
-CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
-CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
-CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
-CREATE TABLE /*_*/watchlist (
-  wl_user int unsigned NOT NULL,
-  wl_namespace int NOT NULL default 0,
-  wl_title varchar(255) binary NOT NULL default '',
-  wl_notificationtimestamp varbinary(14)
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
-CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
-CREATE TABLE /*_*/math (
-  math_inputhash varbinary(16) NOT NULL,
-  math_outputhash varbinary(16) NOT NULL,
-  math_html_conservativeness tinyint NOT NULL,
-  math_html text,
-  math_mathml text
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
-CREATE TABLE /*_*/searchindex (
-  si_page int unsigned NOT NULL,
-  si_title varchar(255) NOT NULL default '',
-  si_text mediumtext NOT NULL
-) ENGINE=MyISAM;
-CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
-CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
-CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
-CREATE TABLE /*_*/interwiki (
-  iw_prefix varchar(32) NOT NULL,
-  iw_url blob NOT NULL,
-  iw_local bool NOT NULL,
-  iw_trans tinyint NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
-CREATE TABLE /*_*/querycache (
-  qc_type varbinary(32) NOT NULL,
-  qc_value int unsigned NOT NULL default 0,
-  qc_namespace int NOT NULL default 0,
-  qc_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
-CREATE TABLE /*_*/objectcache (
-  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
-  value mediumblob,
-  exptime datetime
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
-CREATE TABLE /*_*/transcache (
-  tc_url varbinary(255) NOT NULL,
-  tc_contents text,
-  tc_time binary(14) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
-CREATE TABLE /*_*/logging (
-  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  log_type varbinary(32) NOT NULL default '',
-  log_action varbinary(32) NOT NULL default '',
-  log_timestamp binary(14) NOT NULL default '19700101000000',
-  log_user int unsigned NOT NULL default 0,
-  log_user_text varchar(255) binary NOT NULL default '',
-  log_namespace int NOT NULL default 0,
-  log_title varchar(255) binary NOT NULL default '',
-  log_page int unsigned NULL,
-  log_comment varchar(255) NOT NULL default '',
-  log_params blob NOT NULL,
-  log_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
-CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
-CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
-CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
-CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
-CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
-CREATE TABLE /*_*/log_search (
-  ls_field varbinary(32) NOT NULL,
-  ls_value varchar(255) NOT NULL,
-  ls_log_id int unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
-CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
-CREATE TABLE /*_*/trackbacks (
-  tb_id int PRIMARY KEY AUTO_INCREMENT,
-  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
-  tb_title varchar(255) NOT NULL,
-  tb_url blob NOT NULL,
-  tb_ex text,
-  tb_name varchar(255)
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
-CREATE TABLE /*_*/job (
-  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  job_cmd varbinary(60) NOT NULL default '',
-  job_namespace int NOT NULL,
-  job_title varchar(255) binary NOT NULL,
-  job_params blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
-CREATE TABLE /*_*/querycache_info (
-  qci_type varbinary(32) NOT NULL default '',
-  qci_timestamp binary(14) NOT NULL default '19700101000000'
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
-CREATE TABLE /*_*/redirect (
-  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
-  rd_namespace int NOT NULL default 0,
-  rd_title varchar(255) binary NOT NULL default '',
-  rd_interwiki varchar(32) default NULL,
-  rd_fragment varchar(255) binary default NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
-CREATE TABLE /*_*/querycachetwo (
-  qcc_type varbinary(32) NOT NULL,
-  qcc_value int unsigned NOT NULL default 0,
-  qcc_namespace int NOT NULL default 0,
-  qcc_title varchar(255) binary NOT NULL default '',
-  qcc_namespacetwo int NOT NULL default 0,
-  qcc_titletwo varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
-CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
-CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
-CREATE TABLE /*_*/page_restrictions (
-  pr_page int NOT NULL,
-  pr_type varbinary(60) NOT NULL,
-  pr_level varbinary(60) NOT NULL,
-  pr_cascade tinyint NOT NULL,
-  pr_user int NULL,
-  pr_expiry varbinary(14) NULL,
-  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
-CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
-CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
-CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
-CREATE TABLE /*_*/protected_titles (
-  pt_namespace int NOT NULL,
-  pt_title varchar(255) binary NOT NULL,
-  pt_user int unsigned NOT NULL,
-  pt_reason tinyblob,
-  pt_timestamp binary(14) NOT NULL,
-  pt_expiry varbinary(14) NOT NULL default '',
-  pt_create_perm varbinary(60) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
-CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
-CREATE TABLE /*_*/page_props (
-  pp_page int NOT NULL,
-  pp_propname varbinary(60) NOT NULL,
-  pp_value blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
-CREATE TABLE /*_*/updatelog (
-  ul_key varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
-CREATE TABLE /*_*/change_tag (
-  ct_rc_id int NULL,
-  ct_log_id int NULL,
-  ct_rev_id int NULL,
-  ct_tag varchar(255) NOT NULL,
-  ct_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
-CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
-CREATE TABLE /*_*/tag_summary (
-  ts_rc_id int NULL,
-  ts_log_id int NULL,
-  ts_rev_id int NULL,
-  ts_tags blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
-CREATE TABLE /*_*/valid_tag (
-  vt_tag varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
-CREATE TABLE /*_*/l10n_cache (
-  lc_lang varbinary(32) NOT NULL,
-  lc_key varchar(255) NOT NULL,
-  lc_value mediumblob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
diff --git a/tests/phpunit/includes/db/sqlite/tables-1.17.sql b/tests/phpunit/includes/db/sqlite/tables-1.17.sql
deleted file mode 100644
index 69ae3764..00000000
--- a/tests/phpunit/includes/db/sqlite/tables-1.17.sql
+++ /dev/null
@@ -1,516 +0,0 @@
--- This is a copy of MediaWiki 1.17 schema shared by MySQL and SQLite.
--- It is used for updater testing. Comments are stripped to decrease
--- file size, as we don't need to maintain it.
-
-CREATE TABLE /*_*/user (
-  user_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  user_name varchar(255) binary NOT NULL default '',
-  user_real_name varchar(255) binary NOT NULL default '',
-  user_password tinyblob NOT NULL,
-  user_newpassword tinyblob NOT NULL,
-  user_newpass_time binary(14),
-  user_email tinytext NOT NULL,
-  user_options blob NOT NULL,
-  user_touched binary(14) NOT NULL default '',
-  user_token binary(32) NOT NULL default '',
-  user_email_authenticated binary(14),
-  user_email_token binary(32),
-  user_email_token_expires binary(14),
-  user_registration binary(14),
-  user_editcount int
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/user_name ON /*_*/user (user_name);
-CREATE INDEX /*i*/user_email_token ON /*_*/user (user_email_token);
-CREATE TABLE /*_*/user_groups (
-  ug_user int unsigned NOT NULL default 0,
-  ug_group varbinary(16) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ug_user_group ON /*_*/user_groups (ug_user,ug_group);
-CREATE INDEX /*i*/ug_group ON /*_*/user_groups (ug_group);
-CREATE TABLE /*_*/user_newtalk (
-  user_id int NOT NULL default 0,
-  user_ip varbinary(40) NOT NULL default '',
-  user_last_timestamp binary(14) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/un_user_id ON /*_*/user_newtalk (user_id);
-CREATE INDEX /*i*/un_user_ip ON /*_*/user_newtalk (user_ip);
-CREATE TABLE /*_*/user_properties (
-  up_user int NOT NULL,
-  up_property varbinary(32) NOT NULL,
-  up_value blob
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/user_properties_user_property ON /*_*/user_properties (up_user,up_property);
-CREATE INDEX /*i*/user_properties_property ON /*_*/user_properties (up_property);
-CREATE TABLE /*_*/page (
-  page_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  page_namespace int NOT NULL,
-  page_title varchar(255) binary NOT NULL,
-  page_restrictions tinyblob NOT NULL,
-  page_counter bigint unsigned NOT NULL default 0,
-  page_is_redirect tinyint unsigned NOT NULL default 0,
-  page_is_new tinyint unsigned NOT NULL default 0,
-  page_random real unsigned NOT NULL,
-  page_touched binary(14) NOT NULL default '',
-  page_latest int unsigned NOT NULL,
-  page_len int unsigned NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/name_title ON /*_*/page (page_namespace,page_title);
-CREATE INDEX /*i*/page_random ON /*_*/page (page_random);
-CREATE INDEX /*i*/page_len ON /*_*/page (page_len);
-CREATE TABLE /*_*/revision (
-  rev_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rev_page int unsigned NOT NULL,
-  rev_text_id int unsigned NOT NULL,
-  rev_comment tinyblob NOT NULL,
-  rev_user int unsigned NOT NULL default 0,
-  rev_user_text varchar(255) binary NOT NULL default '',
-  rev_timestamp binary(14) NOT NULL default '',
-  rev_minor_edit tinyint unsigned NOT NULL default 0,
-  rev_deleted tinyint unsigned NOT NULL default 0,
-  rev_len int unsigned,
-  rev_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=1024;
-CREATE UNIQUE INDEX /*i*/rev_page_id ON /*_*/revision (rev_page, rev_id);
-CREATE INDEX /*i*/rev_timestamp ON /*_*/revision (rev_timestamp);
-CREATE INDEX /*i*/page_timestamp ON /*_*/revision (rev_page,rev_timestamp);
-CREATE INDEX /*i*/user_timestamp ON /*_*/revision (rev_user,rev_timestamp);
-CREATE INDEX /*i*/usertext_timestamp ON /*_*/revision (rev_user_text,rev_timestamp);
-CREATE TABLE /*_*/text (
-  old_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  old_text mediumblob NOT NULL,
-  old_flags tinyblob NOT NULL
-) /*$wgDBTableOptions*/ MAX_ROWS=10000000 AVG_ROW_LENGTH=10240;
-CREATE TABLE /*_*/archive (
-  ar_namespace int NOT NULL default 0,
-  ar_title varchar(255) binary NOT NULL default '',
-  ar_text mediumblob NOT NULL,
-  ar_comment tinyblob NOT NULL,
-  ar_user int unsigned NOT NULL default 0,
-  ar_user_text varchar(255) binary NOT NULL,
-  ar_timestamp binary(14) NOT NULL default '',
-  ar_minor_edit tinyint NOT NULL default 0,
-  ar_flags tinyblob NOT NULL,
-  ar_rev_id int unsigned,
-  ar_text_id int unsigned,
-  ar_deleted tinyint unsigned NOT NULL default 0,
-  ar_len int unsigned,
-  ar_page_id int unsigned,
-  ar_parent_id int unsigned default NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/name_title_timestamp ON /*_*/archive (ar_namespace,ar_title,ar_timestamp);
-CREATE INDEX /*i*/ar_usertext_timestamp ON /*_*/archive (ar_user_text,ar_timestamp);
-CREATE INDEX /*i*/ar_revid ON /*_*/archive (ar_rev_id);
-CREATE TABLE /*_*/pagelinks (
-  pl_from int unsigned NOT NULL default 0,
-  pl_namespace int NOT NULL default 0,
-  pl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pl_from ON /*_*/pagelinks (pl_from,pl_namespace,pl_title);
-CREATE UNIQUE INDEX /*i*/pl_namespace ON /*_*/pagelinks (pl_namespace,pl_title,pl_from);
-CREATE TABLE /*_*/templatelinks (
-  tl_from int unsigned NOT NULL default 0,
-  tl_namespace int NOT NULL default 0,
-  tl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tl_from ON /*_*/templatelinks (tl_from,tl_namespace,tl_title);
-CREATE UNIQUE INDEX /*i*/tl_namespace ON /*_*/templatelinks (tl_namespace,tl_title,tl_from);
-CREATE TABLE /*_*/imagelinks (
-  il_from int unsigned NOT NULL default 0,
-  il_to varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/il_from ON /*_*/imagelinks (il_from,il_to);
-CREATE UNIQUE INDEX /*i*/il_to ON /*_*/imagelinks (il_to,il_from);
-CREATE TABLE /*_*/categorylinks (
-  cl_from int unsigned NOT NULL default 0,
-  cl_to varchar(255) binary NOT NULL default '',
-  cl_sortkey varbinary(230) NOT NULL default '',
-  cl_sortkey_prefix varchar(255) binary NOT NULL default '',
-  cl_timestamp timestamp NOT NULL,
-  cl_collation varbinary(32) NOT NULL default '',
-  cl_type ENUM('page', 'subcat', 'file') NOT NULL default 'page'
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cl_from ON /*_*/categorylinks (cl_from,cl_to);
-CREATE INDEX /*i*/cl_sortkey ON /*_*/categorylinks (cl_to,cl_type,cl_sortkey,cl_from);
-CREATE INDEX /*i*/cl_timestamp ON /*_*/categorylinks (cl_to,cl_timestamp);
-CREATE INDEX /*i*/cl_collation ON /*_*/categorylinks (cl_collation);
-CREATE TABLE /*_*/category (
-  cat_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  cat_title varchar(255) binary NOT NULL,
-  cat_pages int signed NOT NULL default 0,
-  cat_subcats int signed NOT NULL default 0,
-  cat_files int signed NOT NULL default 0,
-  cat_hidden tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/cat_title ON /*_*/category (cat_title);
-CREATE INDEX /*i*/cat_pages ON /*_*/category (cat_pages);
-CREATE TABLE /*_*/externallinks (
-  el_from int unsigned NOT NULL default 0,
-  el_to blob NOT NULL,
-  el_index blob NOT NULL
-) /*$wgDBTableOptions*/;
-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 '',
-  ll_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ll_from ON /*_*/langlinks (ll_from, ll_lang);
-CREATE INDEX /*i*/ll_lang ON /*_*/langlinks (ll_lang, ll_title);
-CREATE TABLE /*_*/iwlinks (
-  iwl_from int unsigned NOT NULL default 0,
-  iwl_prefix varbinary(20) NOT NULL default '',
-  iwl_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/iwl_from ON /*_*/iwlinks (iwl_from, iwl_prefix, iwl_title);
-CREATE UNIQUE INDEX /*i*/iwl_prefix_title_from ON /*_*/iwlinks (iwl_prefix, iwl_title, iwl_from);
-CREATE TABLE /*_*/site_stats (
-  ss_row_id int unsigned NOT NULL,
-  ss_total_views bigint unsigned default 0,
-  ss_total_edits bigint unsigned default 0,
-  ss_good_articles bigint unsigned default 0,
-  ss_total_pages bigint default '-1',
-  ss_users bigint default '-1',
-  ss_active_users bigint default '-1',
-  ss_admins int default '-1',
-  ss_images int default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ss_row_id ON /*_*/site_stats (ss_row_id);
-CREATE TABLE /*_*/hitcounter (
-  hc_id int unsigned NOT NULL
-) ENGINE=HEAP MAX_ROWS=25000;
-CREATE TABLE /*_*/ipblocks (
-  ipb_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  ipb_address tinyblob NOT NULL,
-  ipb_user int unsigned NOT NULL default 0,
-  ipb_by int unsigned NOT NULL default 0,
-  ipb_by_text varchar(255) binary NOT NULL default '',
-  ipb_reason tinyblob NOT NULL,
-  ipb_timestamp binary(14) NOT NULL default '',
-  ipb_auto bool NOT NULL default 0,
-  ipb_anon_only bool NOT NULL default 0,
-  ipb_create_account bool NOT NULL default 1,
-  ipb_enable_autoblock bool NOT NULL default '1',
-  ipb_expiry varbinary(14) NOT NULL default '',
-  ipb_range_start tinyblob NOT NULL,
-  ipb_range_end tinyblob NOT NULL,
-  ipb_deleted bool NOT NULL default 0,
-  ipb_block_email bool NOT NULL default 0,
-  ipb_allow_usertalk bool NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ipb_address ON /*_*/ipblocks (ipb_address(255), ipb_user, ipb_auto, ipb_anon_only);
-CREATE INDEX /*i*/ipb_user ON /*_*/ipblocks (ipb_user);
-CREATE INDEX /*i*/ipb_range ON /*_*/ipblocks (ipb_range_start(8), ipb_range_end(8));
-CREATE INDEX /*i*/ipb_timestamp ON /*_*/ipblocks (ipb_timestamp);
-CREATE INDEX /*i*/ipb_expiry ON /*_*/ipblocks (ipb_expiry);
-CREATE TABLE /*_*/image (
-  img_name varchar(255) binary NOT NULL default '' PRIMARY KEY,
-  img_size int unsigned NOT NULL default 0,
-  img_width int NOT NULL default 0,
-  img_height int NOT NULL default 0,
-  img_metadata mediumblob NOT NULL,
-  img_bits int NOT NULL default 0,
-  img_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  img_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  img_minor_mime varbinary(100) NOT NULL default "unknown",
-  img_description tinyblob NOT NULL,
-  img_user int unsigned NOT NULL default 0,
-  img_user_text varchar(255) binary NOT NULL,
-  img_timestamp varbinary(14) NOT NULL default '',
-  img_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/img_usertext_timestamp ON /*_*/image (img_user_text,img_timestamp);
-CREATE INDEX /*i*/img_size ON /*_*/image (img_size);
-CREATE INDEX /*i*/img_timestamp ON /*_*/image (img_timestamp);
-CREATE INDEX /*i*/img_sha1 ON /*_*/image (img_sha1);
-CREATE TABLE /*_*/oldimage (
-  oi_name varchar(255) binary NOT NULL default '',
-  oi_archive_name varchar(255) binary NOT NULL default '',
-  oi_size int unsigned NOT NULL default 0,
-  oi_width int NOT NULL default 0,
-  oi_height int NOT NULL default 0,
-  oi_bits int NOT NULL default 0,
-  oi_description tinyblob NOT NULL,
-  oi_user int unsigned NOT NULL default 0,
-  oi_user_text varchar(255) binary NOT NULL,
-  oi_timestamp binary(14) NOT NULL default '',
-  oi_metadata mediumblob NOT NULL,
-  oi_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  oi_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") NOT NULL default "unknown",
-  oi_minor_mime varbinary(100) NOT NULL default "unknown",
-  oi_deleted tinyint unsigned NOT NULL default 0,
-  oi_sha1 varbinary(32) NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/oi_usertext_timestamp ON /*_*/oldimage (oi_user_text,oi_timestamp);
-CREATE INDEX /*i*/oi_name_timestamp ON /*_*/oldimage (oi_name,oi_timestamp);
-CREATE INDEX /*i*/oi_name_archive_name ON /*_*/oldimage (oi_name,oi_archive_name(14));
-CREATE INDEX /*i*/oi_sha1 ON /*_*/oldimage (oi_sha1);
-CREATE TABLE /*_*/filearchive (
-  fa_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  fa_name varchar(255) binary NOT NULL default '',
-  fa_archive_name varchar(255) binary default '',
-  fa_storage_group varbinary(16),
-  fa_storage_key varbinary(64) default '',
-  fa_deleted_user int,
-  fa_deleted_timestamp binary(14) default '',
-  fa_deleted_reason text,
-  fa_size int unsigned default 0,
-  fa_width int default 0,
-  fa_height int default 0,
-  fa_metadata mediumblob,
-  fa_bits int default 0,
-  fa_media_type ENUM("UNKNOWN", "BITMAP", "DRAWING", "AUDIO", "VIDEO", "MULTIMEDIA", "OFFICE", "TEXT", "EXECUTABLE", "ARCHIVE") default NULL,
-  fa_major_mime ENUM("unknown", "application", "audio", "image", "text", "video", "message", "model", "multipart") default "unknown",
-  fa_minor_mime varbinary(100) default "unknown",
-  fa_description tinyblob,
-  fa_user int unsigned default 0,
-  fa_user_text varchar(255) binary,
-  fa_timestamp binary(14) default '',
-  fa_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/fa_name ON /*_*/filearchive (fa_name, fa_timestamp);
-CREATE INDEX /*i*/fa_storage_group ON /*_*/filearchive (fa_storage_group, fa_storage_key);
-CREATE INDEX /*i*/fa_deleted_timestamp ON /*_*/filearchive (fa_deleted_timestamp);
-CREATE INDEX /*i*/fa_user_timestamp ON /*_*/filearchive (fa_user_text,fa_timestamp);
-CREATE TABLE /*_*/recentchanges (
-  rc_id int NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  rc_timestamp varbinary(14) NOT NULL default '',
-  rc_cur_time varbinary(14) NOT NULL default '',
-  rc_user int unsigned NOT NULL default 0,
-  rc_user_text varchar(255) binary NOT NULL,
-  rc_namespace int NOT NULL default 0,
-  rc_title varchar(255) binary NOT NULL default '',
-  rc_comment varchar(255) binary NOT NULL default '',
-  rc_minor tinyint unsigned NOT NULL default 0,
-  rc_bot tinyint unsigned NOT NULL default 0,
-  rc_new tinyint unsigned NOT NULL default 0,
-  rc_cur_id int unsigned NOT NULL default 0,
-  rc_this_oldid int unsigned NOT NULL default 0,
-  rc_last_oldid int unsigned NOT NULL default 0,
-  rc_type tinyint unsigned NOT NULL default 0,
-  rc_moved_to_ns tinyint unsigned NOT NULL default 0,
-  rc_moved_to_title varchar(255) binary NOT NULL default '',
-  rc_patrolled tinyint unsigned NOT NULL default 0,
-  rc_ip varbinary(40) NOT NULL default '',
-  rc_old_len int,
-  rc_new_len int,
-  rc_deleted tinyint unsigned NOT NULL default 0,
-  rc_logid int unsigned NOT NULL default 0,
-  rc_log_type varbinary(255) NULL default NULL,
-  rc_log_action varbinary(255) NULL default NULL,
-  rc_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rc_timestamp ON /*_*/recentchanges (rc_timestamp);
-CREATE INDEX /*i*/rc_namespace_title ON /*_*/recentchanges (rc_namespace, rc_title);
-CREATE INDEX /*i*/rc_cur_id ON /*_*/recentchanges (rc_cur_id);
-CREATE INDEX /*i*/new_name_timestamp ON /*_*/recentchanges (rc_new,rc_namespace,rc_timestamp);
-CREATE INDEX /*i*/rc_ip ON /*_*/recentchanges (rc_ip);
-CREATE INDEX /*i*/rc_ns_usertext ON /*_*/recentchanges (rc_namespace, rc_user_text);
-CREATE INDEX /*i*/rc_user_text ON /*_*/recentchanges (rc_user_text, rc_timestamp);
-CREATE TABLE /*_*/watchlist (
-  wl_user int unsigned NOT NULL,
-  wl_namespace int NOT NULL default 0,
-  wl_title varchar(255) binary NOT NULL default '',
-  wl_notificationtimestamp varbinary(14)
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/wl_user ON /*_*/watchlist (wl_user, wl_namespace, wl_title);
-CREATE INDEX /*i*/namespace_title ON /*_*/watchlist (wl_namespace, wl_title);
-CREATE TABLE /*_*/math (
-  math_inputhash varbinary(16) NOT NULL,
-  math_outputhash varbinary(16) NOT NULL,
-  math_html_conservativeness tinyint NOT NULL,
-  math_html text,
-  math_mathml text
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/math_inputhash ON /*_*/math (math_inputhash);
-CREATE TABLE /*_*/searchindex (
-  si_page int unsigned NOT NULL,
-  si_title varchar(255) NOT NULL default '',
-  si_text mediumtext NOT NULL
-) ENGINE=MyISAM;
-CREATE UNIQUE INDEX /*i*/si_page ON /*_*/searchindex (si_page);
-CREATE FULLTEXT INDEX /*i*/si_title ON /*_*/searchindex (si_title);
-CREATE FULLTEXT INDEX /*i*/si_text ON /*_*/searchindex (si_text);
-CREATE TABLE /*_*/interwiki (
-  iw_prefix varchar(32) NOT NULL,
-  iw_url blob NOT NULL,
-  iw_api blob NOT NULL,
-  iw_wikiid varchar(64) NOT NULL,
-  iw_local bool NOT NULL,
-  iw_trans tinyint NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/iw_prefix ON /*_*/interwiki (iw_prefix);
-CREATE TABLE /*_*/querycache (
-  qc_type varbinary(32) NOT NULL,
-  qc_value int unsigned NOT NULL default 0,
-  qc_namespace int NOT NULL default 0,
-  qc_title varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qc_type ON /*_*/querycache (qc_type,qc_value);
-CREATE TABLE /*_*/objectcache (
-  keyname varbinary(255) NOT NULL default '' PRIMARY KEY,
-  value mediumblob,
-  exptime datetime
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/exptime ON /*_*/objectcache (exptime);
-CREATE TABLE /*_*/transcache (
-  tc_url varbinary(255) NOT NULL,
-  tc_contents text,
-  tc_time binary(14) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tc_url_idx ON /*_*/transcache (tc_url);
-CREATE TABLE /*_*/logging (
-  log_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  log_type varbinary(32) NOT NULL default '',
-  log_action varbinary(32) NOT NULL default '',
-  log_timestamp binary(14) NOT NULL default '19700101000000',
-  log_user int unsigned NOT NULL default 0,
-  log_user_text varchar(255) binary NOT NULL default '',
-  log_namespace int NOT NULL default 0,
-  log_title varchar(255) binary NOT NULL default '',
-  log_page int unsigned NULL,
-  log_comment varchar(255) NOT NULL default '',
-  log_params blob NOT NULL,
-  log_deleted tinyint unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/type_time ON /*_*/logging (log_type, log_timestamp);
-CREATE INDEX /*i*/user_time ON /*_*/logging (log_user, log_timestamp);
-CREATE INDEX /*i*/page_time ON /*_*/logging (log_namespace, log_title, log_timestamp);
-CREATE INDEX /*i*/times ON /*_*/logging (log_timestamp);
-CREATE INDEX /*i*/log_user_type_time ON /*_*/logging (log_user, log_type, log_timestamp);
-CREATE INDEX /*i*/log_page_id_time ON /*_*/logging (log_page,log_timestamp);
-CREATE TABLE /*_*/log_search (
-  ls_field varbinary(32) NOT NULL,
-  ls_value varchar(255) NOT NULL,
-  ls_log_id int unsigned NOT NULL default 0
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/ls_field_val ON /*_*/log_search (ls_field,ls_value,ls_log_id);
-CREATE INDEX /*i*/ls_log_id ON /*_*/log_search (ls_log_id);
-CREATE TABLE /*_*/trackbacks (
-  tb_id int PRIMARY KEY AUTO_INCREMENT,
-  tb_page int REFERENCES /*_*/page(page_id) ON DELETE CASCADE,
-  tb_title varchar(255) NOT NULL,
-  tb_url blob NOT NULL,
-  tb_ex text,
-  tb_name varchar(255)
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/tb_page ON /*_*/trackbacks (tb_page);
-CREATE TABLE /*_*/job (
-  job_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
-  job_cmd varbinary(60) NOT NULL default '',
-  job_namespace int NOT NULL,
-  job_title varchar(255) binary NOT NULL,
-  job_params blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/job_cmd ON /*_*/job (job_cmd, job_namespace, job_title, job_params(128));
-CREATE TABLE /*_*/querycache_info (
-  qci_type varbinary(32) NOT NULL default '',
-  qci_timestamp binary(14) NOT NULL default '19700101000000'
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/qci_type ON /*_*/querycache_info (qci_type);
-CREATE TABLE /*_*/redirect (
-  rd_from int unsigned NOT NULL default 0 PRIMARY KEY,
-  rd_namespace int NOT NULL default 0,
-  rd_title varchar(255) binary NOT NULL default '',
-  rd_interwiki varchar(32) default NULL,
-  rd_fragment varchar(255) binary default NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/rd_ns_title ON /*_*/redirect (rd_namespace,rd_title,rd_from);
-CREATE TABLE /*_*/querycachetwo (
-  qcc_type varbinary(32) NOT NULL,
-  qcc_value int unsigned NOT NULL default 0,
-  qcc_namespace int NOT NULL default 0,
-  qcc_title varchar(255) binary NOT NULL default '',
-  qcc_namespacetwo int NOT NULL default 0,
-  qcc_titletwo varchar(255) binary NOT NULL default ''
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/qcc_type ON /*_*/querycachetwo (qcc_type,qcc_value);
-CREATE INDEX /*i*/qcc_title ON /*_*/querycachetwo (qcc_type,qcc_namespace,qcc_title);
-CREATE INDEX /*i*/qcc_titletwo ON /*_*/querycachetwo (qcc_type,qcc_namespacetwo,qcc_titletwo);
-CREATE TABLE /*_*/page_restrictions (
-  pr_page int NOT NULL,
-  pr_type varbinary(60) NOT NULL,
-  pr_level varbinary(60) NOT NULL,
-  pr_cascade tinyint NOT NULL,
-  pr_user int NULL,
-  pr_expiry varbinary(14) NULL,
-  pr_id int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pr_pagetype ON /*_*/page_restrictions (pr_page,pr_type);
-CREATE INDEX /*i*/pr_typelevel ON /*_*/page_restrictions (pr_type,pr_level);
-CREATE INDEX /*i*/pr_level ON /*_*/page_restrictions (pr_level);
-CREATE INDEX /*i*/pr_cascade ON /*_*/page_restrictions (pr_cascade);
-CREATE TABLE /*_*/protected_titles (
-  pt_namespace int NOT NULL,
-  pt_title varchar(255) binary NOT NULL,
-  pt_user int unsigned NOT NULL,
-  pt_reason tinyblob,
-  pt_timestamp binary(14) NOT NULL,
-  pt_expiry varbinary(14) NOT NULL default '',
-  pt_create_perm varbinary(60) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pt_namespace_title ON /*_*/protected_titles (pt_namespace,pt_title);
-CREATE INDEX /*i*/pt_timestamp ON /*_*/protected_titles (pt_timestamp);
-CREATE TABLE /*_*/page_props (
-  pp_page int NOT NULL,
-  pp_propname varbinary(60) NOT NULL,
-  pp_value blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/pp_page_propname ON /*_*/page_props (pp_page,pp_propname);
-CREATE TABLE /*_*/updatelog (
-  ul_key varchar(255) NOT NULL PRIMARY KEY,
-  ul_value blob
-) /*$wgDBTableOptions*/;
-CREATE TABLE /*_*/change_tag (
-  ct_rc_id int NULL,
-  ct_log_id int NULL,
-  ct_rev_id int NULL,
-  ct_tag varchar(255) NOT NULL,
-  ct_params blob NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/change_tag_rc_tag ON /*_*/change_tag (ct_rc_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_log_tag ON /*_*/change_tag (ct_log_id,ct_tag);
-CREATE UNIQUE INDEX /*i*/change_tag_rev_tag ON /*_*/change_tag (ct_rev_id,ct_tag);
-CREATE INDEX /*i*/change_tag_tag_id ON /*_*/change_tag (ct_tag,ct_rc_id,ct_rev_id,ct_log_id);
-CREATE TABLE /*_*/tag_summary (
-  ts_rc_id int NULL,
-  ts_log_id int NULL,
-  ts_rev_id int NULL,
-  ts_tags blob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/tag_summary_rc_id ON /*_*/tag_summary (ts_rc_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_log_id ON /*_*/tag_summary (ts_log_id);
-CREATE UNIQUE INDEX /*i*/tag_summary_rev_id ON /*_*/tag_summary (ts_rev_id);
-CREATE TABLE /*_*/valid_tag (
-  vt_tag varchar(255) NOT NULL PRIMARY KEY
-) /*$wgDBTableOptions*/;
-CREATE TABLE /*_*/l10n_cache (
-  lc_lang varbinary(32) NOT NULL,
-  lc_key varchar(255) NOT NULL,
-  lc_value mediumblob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE INDEX /*i*/lc_lang_key ON /*_*/l10n_cache (lc_lang, lc_key);
-CREATE TABLE /*_*/msg_resource (
-  mr_resource varbinary(255) NOT NULL,
-  mr_lang varbinary(32) NOT NULL,
-  mr_blob mediumblob NOT NULL,
-  mr_timestamp binary(14) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/mr_resource_lang ON /*_*/msg_resource (mr_resource, mr_lang);
-CREATE TABLE /*_*/msg_resource_links (
-  mrl_resource varbinary(255) NOT NULL,
-  mrl_message varbinary(255) NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/mrl_message_resource ON /*_*/msg_resource_links (mrl_message, mrl_resource);
-CREATE TABLE /*_*/module_deps (
-  md_module varbinary(255) NOT NULL,
-  md_skin varbinary(32) NOT NULL,
-  md_deps mediumblob NOT NULL
-) /*$wgDBTableOptions*/;
-CREATE UNIQUE INDEX /*i*/md_module_skin ON /*_*/module_deps (md_module, md_skin);
diff --git a/tests/phpunit/includes/debug/MWDebugTest.php b/tests/phpunit/includes/debug/MWDebugTest.php
new file mode 100644
index 00000000..5a4e66d4
--- /dev/null
+++ b/tests/phpunit/includes/debug/MWDebugTest.php
@@ -0,0 +1,63 @@
+<?php
+
+class MWDebugTest extends MediaWikiTestCase {
+
+
+	function setUp() {
+		// Make sure MWDebug class is enabled
+		static $MWDebugEnabled = false;
+		if( !$MWDebugEnabled ) {
+			MWDebug::init();
+			$MWDebugEnabled = true;
+		}
+		/** Clear log before each test */
+		MWDebug::clearLog();
+	}
+
+	function testAddLog() {
+		MWDebug::log( 'logging a string' );
+		$this->assertEquals( array( array(
+			'msg' => 'logging a string',
+			'type' => 'log',
+			'caller' => __METHOD__ ,
+			) ),
+			MWDebug::getLog()
+		);
+	}
+
+	function testAddWarning() {
+		MWDebug::warning( 'Warning message' );
+		$this->assertEquals( array( array(
+			'msg' => 'Warning message',
+			'type' => 'warn',
+			'caller' => 'MWDebug::warning',
+			) ),
+			MWDebug::getLog()
+		);
+	}
+
+	function testAvoidDuplicateDeprecations() {
+		MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+		MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+		// assertCount() not available on WMF integration server
+		$this->assertEquals( 1,
+			count( MWDebug::getLog() ),
+			"Only one deprecated warning per function should be kept"
+		);
+	}
+
+	function testAvoidNonConsecutivesDuplicateDeprecations() {
+		MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+		MWDebug::warning( 'some warning' );
+		MWDebug::log( 'we could have logged something too' );
+		// Another deprecation
+		MWDebug::deprecated( 'wfOldFunction', '1.0', 'component' );
+
+		// assertCount() not available on WMF integration server
+		$this->assertEquals( 3,
+			count( MWDebug::getLog() ),
+			"Only one deprecated warning per function should be kept"
+		);
+	}
+}
diff --git a/tests/phpunit/includes/filerepo/FileBackendTest.php b/tests/phpunit/includes/filerepo/FileBackendTest.php
new file mode 100644
index 00000000..da44797a
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileBackendTest.php
@@ -0,0 +1,1358 @@
+<?php
+
+/**
+ * @group FileRepo
+ * @group FileBackend
+ */
+class FileBackendTest extends MediaWikiTestCase {
+	private $backend, $multiBackend;
+	private $filesToPrune = array();
+	private $dirsToPrune = array();
+	private static $backendToUse;
+
+	function setUp() {
+		global $wgFileBackends;
+		parent::setUp();
+		$tmpPrefix = wfTempDir() . '/filebackend-unittest-' . time() . '-' . mt_rand();
+		if ( $this->getCliArg( 'use-filebackend=' ) ) {
+			if ( self::$backendToUse ) {
+				$this->singleBackend = self::$backendToUse;
+			} else {
+				$name = $this->getCliArg( 'use-filebackend=' );
+				$useConfig = array();
+				foreach ( $wgFileBackends as $conf ) {
+					if ( $conf['name'] == $name ) {
+						$useConfig = $conf;
+					}
+				}
+				$useConfig['name'] = 'localtesting'; // swap name
+				$class = $conf['class'];
+				self::$backendToUse = new $class( $useConfig );
+				$this->singleBackend = self::$backendToUse;
+			}
+		} else {
+			$this->singleBackend = new FSFileBackend( array(
+				'name'        => 'localtesting',
+				'lockManager' => 'fsLockManager',
+				'containerPaths' => array(
+					'unittest-cont1' => "{$tmpPrefix}-localtesting-cont1",
+					'unittest-cont2' => "{$tmpPrefix}-localtesting-cont2" )
+			) );
+		}
+		$this->multiBackend = new FileBackendMultiWrite( array(
+			'name'        => 'localtesting',
+			'lockManager' => 'fsLockManager',
+			'backends'    => array(
+				array(
+					'name'          => 'localmutlitesting1',
+					'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',
+					'containerPaths' => array(
+						'unittest-cont1' => "{$tmpPrefix}-localtestingmulti2-cont1",
+						'unittest-cont2' => "{$tmpPrefix}-localtestingmulti2-cont2" ),
+					'isMultiMaster' => true
+				)
+			)
+		) );
+		$this->filesToPrune = array();
+	}
+
+	private function baseStorePath() {
+		return 'mwstore://localtesting';
+	}
+
+	private function backendClass() {
+		return get_class( $this->backend );
+	}
+
+	/**
+	 * @dataProvider provider_testIsStoragePath
+	 */
+	public function testIsStoragePath( $path, $isStorePath ) {
+		$this->assertEquals( $isStorePath, FileBackend::isStoragePath( $path ),
+			"FileBackend::isStoragePath on path '$path'" );
+	}
+
+	function provider_testIsStoragePath() {
+		return array(
+			array( 'mwstore://', true ),
+			array( 'mwstore://backend', true ),
+			array( 'mwstore://backend/container', true ),
+			array( 'mwstore://backend/container/', true ),
+			array( 'mwstore://backend/container/path', true ),
+			array( 'mwstore://backend//container/', true ),
+			array( 'mwstore://backend//container//', true ),
+			array( 'mwstore://backend//container//path', true ),
+			array( 'mwstore:///', true ),
+			array( 'mwstore:/', false ),
+			array( 'mwstore:', false ),
+		);
+	}
+
+	/**
+	 * @dataProvider provider_testSplitStoragePath
+	 */
+	public function testSplitStoragePath( $path, $res ) {
+		$this->assertEquals( $res, FileBackend::splitStoragePath( $path ),
+			"FileBackend::splitStoragePath on path '$path'" );
+	}
+
+	function provider_testSplitStoragePath() {
+		return array(
+			array( 'mwstore://backend/container', array( 'backend', 'container', '' ) ),
+			array( 'mwstore://backend/container/', array( 'backend', 'container', '' ) ),
+			array( 'mwstore://backend/container/path', array( 'backend', 'container', 'path' ) ),
+			array( 'mwstore://backend/container//path', array( 'backend', 'container', '/path' ) ),
+			array( 'mwstore://backend//container/path', array( null, null, null ) ),
+			array( 'mwstore://backend//container//path', array( null, null, null ) ),
+			array( 'mwstore://', array( null, null, null ) ),
+			array( 'mwstore://backend', array( null, null, null ) ),
+			array( 'mwstore:///', array( null, null, null ) ),
+			array( 'mwstore:/', array( null, null, null ) ),
+			array( 'mwstore:', array( null, null, null ) )
+		);
+	}
+
+	/**
+	 * @dataProvider provider_normalizeStoragePath
+	 */
+	public function testNormalizeStoragePath( $path, $res ) {
+		$this->assertEquals( $res, FileBackend::normalizeStoragePath( $path ),
+			"FileBackend::normalizeStoragePath on path '$path'" );
+	}
+
+	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://', 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 ), )
+		);
+	}
+
+	/**
+	 * @dataProvider provider_testParentStoragePath
+	 */
+	public function testParentStoragePath( $path, $res ) {
+		$this->assertEquals( $res, FileBackend::parentStoragePath( $path ),
+			"FileBackend::parentStoragePath on path '$path'" );
+	}
+
+	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' ),
+			array( 'mwstore://backend/container/path', 'mwstore://backend/container' ),
+			array( 'mwstore://backend/container', null ),
+			array( 'mwstore://backend/container/path/to/obj/', 'mwstore://backend/container/path/to' ),
+			array( 'mwstore://backend/container/path/to/', 'mwstore://backend/container/path' ),
+			array( 'mwstore://backend/container/path/', 'mwstore://backend/container' ),
+			array( 'mwstore://backend/container/', null ),
+		);
+	}
+
+	/**
+	 * @dataProvider provider_testExtensionFromPath
+	 */
+	public function testExtensionFromPath( $path, $res ) {
+		$this->assertEquals( $res, FileBackend::extensionFromPath( $path ),
+			"FileBackend::extensionFromPath on path '$path'" );
+	}
+
+	function provider_testExtensionFromPath() {
+		return array(
+			array( 'mwstore://backend/container/path.txt', 'txt' ),
+			array( 'mwstore://backend/container/path.svg.png', 'png' ),
+			array( 'mwstore://backend/container/path', '' ),
+			array( 'mwstore://backend/container/path.', '' ),
+		);
+	}
+
+	/**
+	 * @dataProvider provider_testStore
+	 */
+	public function testStore( $op ) {
+		$this->filesToPrune[] = $op['src'];
+
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestStore( $op );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestStore( $op );
+		$this->filesToPrune[] = $op['src']; # avoid file leaking
+		$this->tearDownFiles();
+	}
+
+	function doTestStore( $op ) {
+		$backendName = $this->backendClass();
+
+		$source = $op['src'];
+		$dest = $op['dst'];
+		$this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+		file_put_contents( $source, "Unit test file" );
+
+		if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+			$this->backend->store( $op );
+		}
+
+		$status = $this->backend->doOperation( $op );
+
+		$this->assertEquals( array(), $status->errors,
+			"Store from $source to $dest succeeded without warnings ($backendName)." );
+		$this->assertEquals( array(), $status->errors,
+			"Store from $source to $dest succeeded ($backendName)." );
+		$this->assertEquals( array( 0 => true ), $status->success,
+			"Store from $source to $dest has proper 'success' field in Status ($backendName)." );
+		$this->assertEquals( true, file_exists( $source ),
+			"Source file $source still exists ($backendName)." );
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+			"Destination file $dest exists ($backendName)." );
+
+		$this->assertEquals( filesize( $source ),
+			$this->backend->getFileSize( array( 'src' => $dest ) ),
+			"Destination file $dest has correct size ($backendName)." );
+
+		$props1 = FSFile::getPropsFromPath( $source );
+		$props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+		$this->assertEquals( $props1, $props2,
+			"Source and destination have the same props ($backendName)." );
+	}
+
+	public function provider_testStore() {
+		$cases = array();
+
+		$tmpName = TempFSFile::factory( "unittests_", 'txt' )->getPath();
+		$toPath = $this->baseStorePath() . '/unittest-cont1/fun/obj1.txt';
+		$op = array( 'op' => 'store', 'src' => $tmpName, 'dst' => $toPath );
+		$cases[] = array(
+			$op, // operation
+			$tmpName, // source
+			$toPath, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwrite'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$tmpName, // source
+			$toPath, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwriteSame'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$tmpName, // source
+			$toPath, // dest
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testCopy
+	 */
+	public function testCopy( $op ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestCopy( $op );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestCopy( $op );
+		$this->tearDownFiles();
+	}
+
+	function doTestCopy( $op ) {
+		$backendName = $this->backendClass();
+
+		$source = $op['src'];
+		$dest = $op['dst'];
+		$this->prepare( array( 'dir' => dirname( $source ) ) );
+		$this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+		$status = $this->backend->doOperation(
+			array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+		$this->assertEquals( array(), $status->errors,
+			"Creation of file at $source succeeded ($backendName)." );
+
+		if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+			$this->backend->copy( $op );
+		}
+
+		$status = $this->backend->doOperation( $op );
+
+		$this->assertEquals( array(), $status->errors,
+			"Copy from $source to $dest succeeded without warnings ($backendName)." );
+		$this->assertEquals( true, $status->isOK(),
+			"Copy from $source to $dest succeeded ($backendName)." );
+		$this->assertEquals( array( 0 => true ), $status->success,
+			"Copy from $source to $dest has proper 'success' field in Status ($backendName)." );
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $source ) ),
+			"Source file $source still exists ($backendName)." );
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+			"Destination file $dest exists after copy ($backendName)." );
+
+		$this->assertEquals(
+			$this->backend->getFileSize( array( 'src' => $source ) ),
+			$this->backend->getFileSize( array( 'src' => $dest ) ),
+			"Destination file $dest has correct size ($backendName)." );
+
+		$props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+		$props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+		$this->assertEquals( $props1, $props2,
+			"Source and destination have the same props ($backendName)." );
+	}
+
+	public function provider_testCopy() {
+		$cases = array();
+
+		$source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+		$dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+		$op = array( 'op' => 'copy', 'src' => $source, 'dst' => $dest );
+		$cases[] = array(
+			$op, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwrite'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwriteSame'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testMove
+	 */
+	public function testMove( $op ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestMove( $op );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestMove( $op );
+		$this->tearDownFiles();
+	}
+
+	private function doTestMove( $op ) {
+		$backendName = $this->backendClass();
+
+		$source = $op['src'];
+		$dest = $op['dst'];
+		$this->prepare( array( 'dir' => dirname( $source ) ) );
+		$this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+		$status = $this->backend->doOperation(
+			array( 'op' => 'create', 'content' => 'blahblah', 'dst' => $source ) );
+		$this->assertEquals( array(), $status->errors,
+			"Creation of file at $source succeeded ($backendName)." );
+
+		if ( isset( $op['overwrite'] ) || isset( $op['overwriteSame'] ) ) {
+			$this->backend->copy( $op );
+		}
+
+		$status = $this->backend->doOperation( $op );
+		$this->assertEquals( array(), $status->errors,
+			"Move from $source to $dest succeeded without warnings ($backendName)." );
+		$this->assertEquals( true, $status->isOK(),
+			"Move from $source to $dest succeeded ($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 still exists ($backendName)." );
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+			"Destination file $dest exists after move ($backendName)." );
+
+		$this->assertNotEquals(
+			$this->backend->getFileSize( array( 'src' => $source ) ),
+			$this->backend->getFileSize( array( 'src' => $dest ) ),
+			"Destination file $dest has correct size ($backendName)." );
+
+		$props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+		$props2 = $this->backend->getFileProps( array( 'src' => $dest ) );
+		$this->assertEquals( false, $props1['fileExists'],
+			"Source file does not exist accourding to props ($backendName)." );
+		$this->assertEquals( true, $props2['fileExists'],
+			"Destination file exists accourding to props ($backendName)." );
+	}
+
+	public function provider_testMove() {
+		$cases = array();
+
+		$source = $this->baseStorePath() . '/unittest-cont1/file.txt';
+		$dest = $this->baseStorePath() . '/unittest-cont2/fileMoved.txt';
+
+		$op = array( 'op' => 'move', 'src' => $source, 'dst' => $dest );
+		$cases[] = array(
+			$op, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwrite'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		$op2 = $op;
+		$op2['overwriteSame'] = true;
+		$cases[] = array(
+			$op2, // operation
+			$source, // source
+			$dest, // dest
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testDelete
+	 */
+	public function testDelete( $op, $withSource, $okStatus ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestDelete( $op, $withSource, $okStatus );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestDelete( $op, $withSource, $okStatus );
+		$this->tearDownFiles();
+	}
+
+	private function doTestDelete( $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->assertEquals( array(), $status->errors,
+				"Creation of file at $source succeeded ($backendName)." );
+		}
+
+		$status = $this->backend->doOperation( $op );
+		if ( $okStatus ) {
+			$this->assertEquals( array(), $status->errors,
+				"Deletion of file at $source succeeded without warnings ($backendName)." );
+			$this->assertEquals( true, $status->isOK(),
+				"Deletion of file at $source succeeded ($backendName)." );
+			$this->assertEquals( array( 0 => true ), $status->success,
+				"Deletion of file at $source has proper 'success' field in Status ($backendName)." );
+		} else {
+			$this->assertEquals( false, $status->isOK(),
+				"Deletion of file at $source failed ($backendName)." );
+		}
+
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $source ) ),
+			"Source file $source does not exist after move ($backendName)." );
+
+		$this->assertFalse(
+			$this->backend->getFileSize( array( 'src' => $source ) ),
+			"Source file $source has correct size (false) ($backendName)." );
+
+		$props1 = $this->backend->getFileProps( array( 'src' => $source ) );
+		$this->assertFalse( $props1['fileExists'],
+			"Source file $source does not exist according to props ($backendName)." );
+	}
+
+	public function provider_testDelete() {
+		$cases = array();
+
+		$source = $this->baseStorePath() . '/unittest-cont1/myfacefile.txt';
+
+		$op = array( 'op' => 'delete', 'src' => $source );
+		$cases[] = array(
+			$op, // operation
+			true, // with source
+			true // succeeds
+		);
+
+		$cases[] = array(
+			$op, // operation
+			false, // without source
+			false // fails
+		);
+
+		$op['ignoreMissingSource'] = true;
+		$cases[] = array(
+			$op, // operation
+			false, // without source
+			true // succeeds
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testCreate
+	 */
+	public function testCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestCreate( $op, $alreadyExists, $okStatus, $newSize );
+		$this->tearDownFiles();
+	}
+
+	private function doTestCreate( $op, $alreadyExists, $okStatus, $newSize ) {
+		$backendName = $this->backendClass();
+
+		$dest = $op['dst'];
+		$this->prepare( array( 'dir' => dirname( $dest ) ) );
+
+		$oldText = 'blah...blah...waahwaah';
+		if ( $alreadyExists ) {
+			$status = $this->backend->doOperation(
+				array( 'op' => 'create', 'content' => $oldText, 'dst' => $dest ) );
+			$this->assertEquals( array(), $status->errors,
+				"Creation of file at $dest succeeded ($backendName)." );
+		}
+
+		$status = $this->backend->doOperation( $op );
+		if ( $okStatus ) {
+			$this->assertEquals( array(), $status->errors,
+				"Creation of file at $dest succeeded without warnings ($backendName)." );
+			$this->assertEquals( true, $status->isOK(),
+				"Creation of file at $dest succeeded ($backendName)." );
+			$this->assertEquals( array( 0 => true ), $status->success,
+				"Creation of file at $dest has proper 'success' field in Status ($backendName)." );
+		} else {
+			$this->assertEquals( false, $status->isOK(),
+				"Creation of file at $dest failed ($backendName)." );
+		}
+
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $dest ) ),
+			"Destination file $dest exists after creation ($backendName)." );
+
+		$props1 = $this->backend->getFileProps( array( 'src' => $dest ) );
+		$this->assertEquals( true, $props1['fileExists'],
+			"Destination file $dest exists according to props ($backendName)." );
+		if ( $okStatus ) { // file content is what we saved
+			$this->assertEquals( $newSize, $props1['size'],
+				"Destination file $dest has expected size according to props ($backendName)." );
+			$this->assertEquals( $newSize,
+				$this->backend->getFileSize( array( 'src' => $dest ) ),
+				"Destination file $dest has correct size ($backendName)." );
+		} else { // file content is some other previous text
+			$this->assertEquals( strlen( $oldText ), $props1['size'],
+				"Destination file $dest has original size according to props ($backendName)." );
+			$this->assertEquals( strlen( $oldText ),
+				$this->backend->getFileSize( array( 'src' => $dest ) ),
+				"Destination file $dest has original size according to props ($backendName)." );
+		}
+	}
+
+	/**
+	 * @dataProvider provider_testCreate
+	 */
+	public function provider_testCreate() {
+		$cases = array();
+
+		$dest = $this->baseStorePath() . '/unittest-cont2/myspacefile.txt';
+
+		$op = array( 'op' => 'create', 'content' => 'test test testing', 'dst' => $dest );
+		$cases[] = array(
+			$op, // operation
+			false, // no dest already exists
+			true, // succeeds
+			strlen( $op['content'] )
+		);
+
+		$op2 = $op;
+		$op2['content'] = "\n";
+		$cases[] = array(
+			$op2, // operation
+			false, // no dest already exists
+			true, // succeeds
+			strlen( $op2['content'] )
+		);
+
+		$op2 = $op;
+		$op2['content'] = "fsf\n waf 3kt";
+		$cases[] = array(
+			$op2, // operation
+			true, // dest already exists
+			false, // fails
+			strlen( $op2['content'] )
+		);
+
+		$op2 = $op;
+		$op2['content'] = "egm'g gkpe gpqg eqwgwqg";
+		$op2['overwrite'] = true;
+		$cases[] = array(
+			$op2, // operation
+			true, // dest already exists
+			true, // succeeds
+			strlen( $op2['content'] )
+		);
+
+		$op2 = $op;
+		$op2['content'] = "39qjmg3-qg";
+		$op2['overwriteSame'] = true;
+		$cases[] = array(
+			$op2, // operation
+			true, // dest already exists
+			false, // succeeds
+			strlen( $op2['content'] )
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testConcatenate
+	 */
+	public function testConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
+		$this->filesToPrune[] = $op['dst'];
+
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestConcatenate( $op, $srcs, $srcsContent, $alreadyExists, $okStatus );
+		$this->filesToPrune[] = $op['dst']; # avoid file leaking
+		$this->tearDownFiles();
+	}
+
+	public function doTestConcatenate( $params, $srcs, $srcsContent, $alreadyExists, $okStatus ) {
+		$backendName = $this->backendClass();
+
+		$expContent = '';
+		// Create sources
+		$ops = array();
+		foreach ( $srcs as $i => $source ) {
+			$this->prepare( array( 'dir' => dirname( $source ) ) );
+			$ops[] = array(
+				'op'      => 'create', // operation
+				'dst'     => $source, // source
+				'content' => $srcsContent[$i]
+			);
+			$expContent .= $srcsContent[$i];
+		}
+		$status = $this->backend->doOperations( $ops );
+
+		$this->assertEquals( array(), $status->errors,
+			"Creation of source files succeeded ($backendName)." );
+
+		$dest = $params['dst'];
+		if ( $alreadyExists ) {
+			$ok = file_put_contents( $dest, 'blah...blah...waahwaah' ) !== false;
+			$this->assertEquals( true, $ok,
+				"Creation of file at $dest succeeded ($backendName)." );
+		} else {
+			$ok = file_put_contents( $dest, '' ) !== false;
+			$this->assertEquals( true, $ok,
+				"Creation of 0-byte file at $dest succeeded ($backendName)." );
+		}
+
+		// Combine the files into one
+		$status = $this->backend->concatenate( $params );
+		if ( $okStatus ) {
+			$this->assertEquals( array(), $status->errors,
+				"Creation of concat file at $dest succeeded without warnings ($backendName)." );
+			$this->assertEquals( true, $status->isOK(),
+				"Creation of concat file at $dest succeeded ($backendName)." );
+		} else {
+			$this->assertEquals( false, $status->isOK(),
+				"Creation of concat file at $dest failed ($backendName)." );
+		}
+
+		if ( $okStatus ) {
+			$this->assertEquals( true, is_file( $dest ),
+				"Dest concat file $dest exists after creation ($backendName)." );
+		} else {
+			$this->assertEquals( true, is_file( $dest ),
+				"Dest concat file $dest exists after failed creation ($backendName)." );
+		}
+
+		$contents = file_get_contents( $dest );
+		$this->assertNotEquals( false, $contents, "File at $dest exists ($backendName)." );
+
+		if ( $okStatus ) {
+			$this->assertEquals( $expContent, $contents,
+				"Concat file at $dest has correct contents ($backendName)." );
+		} else {
+			$this->assertNotEquals( $expContent, $contents,
+				"Concat file at $dest has correct contents ($backendName)." );
+		}
+	}
+
+	function provider_testConcatenate() {
+		$cases = array();
+
+		$rand = mt_rand( 0, 2000000000 ) . time();
+		$dest = wfTempDir() . "/randomfile!$rand.txt";
+		$srcs = array(
+			$this->baseStorePath() . '/unittest-cont1/file1.txt',
+			$this->baseStorePath() . '/unittest-cont1/file2.txt',
+			$this->baseStorePath() . '/unittest-cont1/file3.txt',
+			$this->baseStorePath() . '/unittest-cont1/file4.txt',
+			$this->baseStorePath() . '/unittest-cont1/file5.txt',
+			$this->baseStorePath() . '/unittest-cont1/file6.txt',
+			$this->baseStorePath() . '/unittest-cont1/file7.txt',
+			$this->baseStorePath() . '/unittest-cont1/file8.txt',
+			$this->baseStorePath() . '/unittest-cont1/file9.txt',
+			$this->baseStorePath() . '/unittest-cont1/file10.txt'
+		);
+		$content = array(
+			'egfage',
+			'ageageag',
+			'rhokohlr',
+			'shgmslkg',
+			'kenga',
+			'owagmal',
+			'kgmae',
+			'g eak;g',
+			'lkaem;a',
+			'legma'
+		);
+		$params = array( 'srcs' => $srcs, 'dst' => $dest );
+
+		$cases[] = array(
+			$params, // operation
+			$srcs, // sources
+			$content, // content for each source
+			false, // no dest already exists
+			true, // succeeds
+		);
+
+		$cases[] = array(
+			$params, // operation
+			$srcs, // sources
+			$content, // content for each source
+			true, // dest already exists
+			false, // succeeds
+		);
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testGetFileStat
+	 */
+	public function testGetFileStat( $path, $content, $alreadyExists ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileStat( $path, $content, $alreadyExists );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileStat( $path, $content, $alreadyExists );
+		$this->tearDownFiles();
+	}
+
+	private function doTestGetFileStat( $path, $content, $alreadyExists ) {
+		$backendName = $this->backendClass();
+
+		if ( $alreadyExists ) {
+			$this->prepare( array( 'dir' => dirname( $path ) ) );
+			$status = $this->backend->create( array( 'dst' => $path, 'content' => $content ) );
+			$this->assertEquals( array(), $status->errors,
+				"Creation of file at $path succeeded ($backendName)." );
+
+			$size = $this->backend->getFileSize( array( 'src' => $path ) );
+			$time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+			$stat = $this->backend->getFileStat( array( 'src' => $path ) );
+
+			$this->assertEquals( strlen( $content ), $size,
+				"Correct file size of '$path'" );
+			$this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+				"Correct file timestamp of '$path'" );
+
+			$size = $stat['size'];
+			$time = $stat['mtime'];
+			$this->assertEquals( strlen( $content ), $size,
+				"Correct file size of '$path'" );
+			$this->assertTrue( abs( time() - wfTimestamp( TS_UNIX, $time ) ) < 5,
+				"Correct file timestamp of '$path'" );
+		} else {
+			$size = $this->backend->getFileSize( array( 'src' => $path ) );
+			$time = $this->backend->getFileTimestamp( array( 'src' => $path ) );
+			$stat = $this->backend->getFileStat( array( 'src' => $path ) );
+			
+			$this->assertFalse( $size, "Correct file size of '$path'" );
+			$this->assertFalse( $time, "Correct file timestamp of '$path'" );
+			$this->assertFalse( $stat, "Correct file stat of '$path'" );
+		}
+	}
+
+	function provider_testGetFileStat() {
+		$cases = array();
+
+		$base = $this->baseStorePath();
+		$cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents", true );
+		$cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "", true );
+		$cases[] = array( "$base/unittest-cont1/b/some-diff_file.txt", null, false );
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testGetFileContents
+	 */
+	public function testGetFileContents( $source, $content ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileContents( $source, $content );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileContents( $source, $content );
+		$this->tearDownFiles();
+	}
+
+	public 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->assertEquals( array(), $status->errors,
+			"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)." );
+
+		$this->assertEquals( $content, $newContents,
+			"Contents read match data at $source ($backendName)." );
+	}
+
+	function provider_testGetFileContents() {
+		$cases = array();
+
+		$base = $this->baseStorePath();
+		$cases[] = array( "$base/unittest-cont1/b/z/some_file.txt", "some file contents" );
+		$cases[] = array( "$base/unittest-cont1/b/some-other_file.txt", "more file contents" );
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testGetLocalCopy
+	 */
+	public function testGetLocalCopy( $source, $content ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestGetLocalCopy( $source, $content );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestGetLocalCopy( $source, $content );
+		$this->tearDownFiles();
+	}
+
+	public 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->assertEquals( array(), $status->errors,
+			"Creation of file at $source succeeded ($backendName)." );
+
+		$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)." );
+	}
+
+	function provider_testGetLocalCopy() {
+		$cases = array();
+
+		$base = $this->baseStorePath();
+		$cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+		$cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testGetLocalReference
+	 */
+	public function testGetLocalReference( $source, $content ) {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestGetLocalReference( $source, $content );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestGetLocalReference( $source, $content );
+		$this->tearDownFiles();
+	}
+
+	private function doTestGetLocalReference( $source, $content ) {
+		$backendName = $this->backendClass();
+
+		$this->prepare( array( 'dir' => dirname( $source ) ) );
+
+		$status = $this->backend->doOperation(
+			array( 'op' => 'create', 'content' => $content, 'dst' => $source ) );
+		$this->assertEquals( array(), $status->errors,
+			"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)." );
+
+		$contents = file_get_contents( $tmpFile->getPath() );
+		$this->assertNotEquals( false, $contents, "Local copy of $source exists ($backendName)." );
+	}
+
+	function provider_testGetLocalReference() {
+		$cases = array();
+
+		$base = $this->baseStorePath();
+		$cases[] = array( "$base/unittest-cont1/a/z/some_file.txt", "some file contents" );
+		$cases[] = array( "$base/unittest-cont1/a/some-other_file.txt", "more file contents" );
+
+		return $cases;
+	}
+
+	/**
+	 * @dataProvider provider_testPrepareAndClean
+	 */
+	public function testPrepareAndClean( $path, $isOK ) {
+		$this->backend = $this->singleBackend;
+		$this->doTestPrepareAndClean( $path, $isOK );
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->doTestPrepareAndClean( $path, $isOK );
+		$this->tearDownFiles();
+	}
+
+	function provider_testPrepareAndClean() {
+		$base = $this->baseStorePath();
+		return array(
+			array( "$base/unittest-cont1/a/z/some_file1.txt", true ),
+			array( "$base/unittest-cont2/a/z/some_file2.txt", true ),
+			# Specific to FS backend with no basePath field set
+			#array( "$base/unittest-cont3/a/z/some_file3.txt", false ),
+		);
+	}
+
+	function doTestPrepareAndClean( $path, $isOK ) {
+		$backendName = $this->backendClass();
+
+		$status = $this->prepare( array( 'dir' => dirname( $path ) ) );
+		if ( $isOK ) {
+			$this->assertEquals( array(), $status->errors,
+				"Preparing dir $path succeeded without warnings ($backendName)." );
+			$this->assertEquals( true, $status->isOK(),
+				"Preparing dir $path succeeded ($backendName)." );
+		} else {
+			$this->assertEquals( false, $status->isOK(),
+				"Preparing dir $path failed ($backendName)." );
+		}
+
+		$status = $this->backend->clean( array( 'dir' => dirname( $path ) ) );
+		if ( $isOK ) {
+			$this->assertEquals( array(), $status->errors,
+				"Cleaning dir $path succeeded without warnings ($backendName)." );
+			$this->assertEquals( true, $status->isOK(),
+				"Cleaning dir $path succeeded ($backendName)." );
+		} else {
+			$this->assertEquals( false, $status->isOK(),
+				"Cleaning dir $path failed ($backendName)." );
+		}
+	}
+
+	// @TODO: testSecure
+
+	public function testDoOperations() {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestDoOperations();
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestDoOperations();
+		$this->tearDownFiles();
+
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestDoOperationsFailing();
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestDoOperationsFailing();
+		$this->tearDownFiles();
+
+		// @TODO: test some cases where the ops should fail
+	}
+
+	function doTestDoOperations() {
+		$base = $this->baseStorePath();
+
+		$fileA = "$base/unittest-cont1/a/b/fileA.txt";
+		$fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+		$fileB = "$base/unittest-cont1/a/b/fileB.txt";
+		$fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+		$fileC = "$base/unittest-cont1/a/b/fileC.txt";
+		$fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+		$fileD = "$base/unittest-cont1/a/b/fileD.txt";
+
+		$this->prepare( array( 'dir' => dirname( $fileA ) ) );
+		$this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+		$this->prepare( array( 'dir' => dirname( $fileB ) ) );
+		$this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+		$this->prepare( array( 'dir' => dirname( $fileC ) ) );
+		$this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+		$status = $this->backend->doOperations( array(
+			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 ),
+			// Now: A:<A>, B:<B>, C:<A>, D:<empty>
+			array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD, 'overwrite' => 1 ),
+			// Now: A:<A>, B:<B>, C:<empty>, D:<A>
+			array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC ),
+			// Now: A:<A>, B:<empty>, C:<B>, D:<A>
+			array( 'op' => 'move', 'src' => $fileD, 'dst' => $fileA, 'overwriteSame' => 1 ),
+			// Now: A:<A>, B:<empty>, C:<B>, D:<empty>
+			array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileA, 'overwrite' => 1 ),
+			// Now: A:<B>, B:<empty>, C:<empty>, D:<empty>
+			array( 'op' => 'copy', 'src' => $fileA, 'dst' => $fileC ),
+			// Now: A:<B>, B:<empty>, C:<B>, D:<empty>
+			array( 'op' => 'move', 'src' => $fileA, 'dst' => $fileC, 'overwriteSame' => 1 ),
+			// Now: A:<empty>, B:<empty>, C:<B>, D:<empty>
+			array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+			// Does nothing
+			array( 'op' => 'copy', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+			// Does nothing
+			array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwrite' => 1 ),
+			// Does nothing
+			array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileC, 'overwriteSame' => 1 ),
+			// Does nothing
+			array( 'op' => 'null' ),
+			// Does nothing
+		) );
+
+		$this->assertEquals( array(), $status->errors, "Operation batch succeeded" );
+		$this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+		$this->assertEquals( 13, count( $status->success ),
+			"Operation batch has correct success array" );
+
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileA ) ),
+			"File does not exist at $fileA" );
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+			"File does not exist at $fileB" );
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+			"File does not exist at $fileD" );
+
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+			"File exists at $fileC" );
+		$this->assertEquals( $fileBContents,
+			$this->backend->getFileContents( array( 'src' => $fileC ) ),
+			"Correct file contents of $fileC" );
+		$this->assertEquals( strlen( $fileBContents ),
+			$this->backend->getFileSize( array( 'src' => $fileC ) ),
+			"Correct file size of $fileC" );
+		$this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+			$this->backend->getFileSha1Base36( array( 'src' => $fileC ) ),
+			"Correct file SHA-1 of $fileC" );
+	}
+
+	function doTestDoOperationsFailing() {
+		$base = $this->baseStorePath();
+
+		$fileA = "$base/unittest-cont2/a/b/fileA.txt";
+		$fileAContents = '3tqtmoeatmn4wg4qe-mg3qt3 tq';
+		$fileB = "$base/unittest-cont2/a/b/fileB.txt";
+		$fileBContents = 'g-jmq3gpqgt3qtg q3GT ';
+		$fileC = "$base/unittest-cont2/a/b/fileC.txt";
+		$fileCContents = 'eigna[ogmewt 3qt g3qg flew[ag';
+		$fileD = "$base/unittest-cont2/a/b/fileD.txt";
+
+		$this->prepare( array( 'dir' => dirname( $fileA ) ) );
+		$this->backend->create( array( 'dst' => $fileA, 'content' => $fileAContents ) );
+		$this->prepare( array( 'dir' => dirname( $fileB ) ) );
+		$this->backend->create( array( 'dst' => $fileB, 'content' => $fileBContents ) );
+		$this->prepare( array( 'dir' => dirname( $fileC ) ) );
+		$this->backend->create( array( 'dst' => $fileC, 'content' => $fileCContents ) );
+
+		$status = $this->backend->doOperations( array(
+			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 ),
+			// Now: A:<A>, B:<B>, C:<A>, D:<empty>
+			array( 'op' => 'copy', 'src' => $fileB, 'dst' => $fileD, 'overwrite' => 1 ),
+			// Now: A:<A>, B:<B>, C:<A>, D:<B>
+			array( 'op' => 'move', 'src' => $fileC, 'dst' => $fileD ),
+			// Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+			array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileC, 'overwriteSame' => 1 ),
+			// Now: A:<A>, B:<B>, C:<A>, D:<empty> (failed)
+			array( 'op' => 'move', 'src' => $fileB, 'dst' => $fileA, 'overwrite' => 1 ),
+			// Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+			array( 'op' => 'delete', 'src' => $fileD ),
+			// Now: A:<B>, B:<empty>, C:<A>, D:<empty>
+			array( 'op' => 'null' ),
+			// Does nothing
+		), array( 'force' => 1 ) );
+
+		$this->assertNotEquals( array(), $status->errors, "Operation had warnings" );
+		$this->assertEquals( true, $status->isOK(), "Operation batch succeeded" );
+		$this->assertEquals( 8, count( $status->success ),
+			"Operation batch has correct success array" );
+
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileB ) ),
+			"File does not exist at $fileB" );
+		$this->assertEquals( false, $this->backend->fileExists( array( 'src' => $fileD ) ),
+			"File does not exist at $fileD" );
+
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileA ) ),
+			"File does not exist at $fileA" );
+		$this->assertEquals( true, $this->backend->fileExists( array( 'src' => $fileC ) ),
+			"File exists at $fileC" );
+		$this->assertEquals( $fileBContents,
+			$this->backend->getFileContents( array( 'src' => $fileA ) ),
+			"Correct file contents of $fileA" );
+		$this->assertEquals( strlen( $fileBContents ),
+			$this->backend->getFileSize( array( 'src' => $fileA ) ),
+			"Correct file size of $fileA" );
+		$this->assertEquals( wfBaseConvert( sha1( $fileBContents ), 16, 36, 31 ),
+			$this->backend->getFileSha1Base36( array( 'src' => $fileA ) ),
+			"Correct file SHA-1 of $fileA" );
+	}
+
+	public function testGetFileList() {
+		$this->backend = $this->singleBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileList();
+		$this->tearDownFiles();
+
+		$this->backend = $this->multiBackend;
+		$this->tearDownFiles();
+		$this->doTestGetFileList();
+		$this->tearDownFiles();
+	}
+
+	private function doTestGetFileList() {
+		$backendName = $this->backendClass();
+
+		$base = $this->baseStorePath();
+		$files = array(
+			"$base/unittest-cont1/test1.txt",
+			"$base/unittest-cont1/test2.txt",
+			"$base/unittest-cont1/test3.txt",
+			"$base/unittest-cont1/subdir1/test1.txt",
+			"$base/unittest-cont1/subdir1/test2.txt",
+			"$base/unittest-cont1/subdir2/test3.txt",
+			"$base/unittest-cont1/subdir2/test4.txt",
+			"$base/unittest-cont1/subdir2/subdir/test1.txt",
+			"$base/unittest-cont1/subdir2/subdir/test2.txt",
+			"$base/unittest-cont1/subdir2/subdir/test3.txt",
+			"$base/unittest-cont1/subdir2/subdir/test4.txt",
+			"$base/unittest-cont1/subdir2/subdir/test5.txt",
+			"$base/unittest-cont1/subdir2/subdir/sub/test0.txt",
+			"$base/unittest-cont1/subdir2/subdir/sub/120-px-file.txt",
+		);
+
+		// Add the files
+		$ops = array();
+		foreach ( $files as $file ) {
+			$this->prepare( array( 'dir' => dirname( $file ) ) );
+			$ops[] = array( 'op' => 'create', 'content' => 'xxy', 'dst' => $file );
+		}
+		$status = $this->backend->doOperations( $ops );
+		$this->assertEquals( array(), $status->errors,
+			"Creation of files succeeded ($backendName)." );
+		$this->assertEquals( true, $status->isOK(),
+			"Creation of files succeeded with OK status ($backendName)." );
+
+		// Expected listing
+		$expected = array(
+			"test1.txt",
+			"test2.txt",
+			"test3.txt",
+			"subdir1/test1.txt",
+			"subdir1/test2.txt",
+			"subdir2/test3.txt",
+			"subdir2/test4.txt",
+			"subdir2/subdir/test1.txt",
+			"subdir2/subdir/test2.txt",
+			"subdir2/subdir/test3.txt",
+			"subdir2/subdir/test4.txt",
+			"subdir2/subdir/test5.txt",
+			"subdir2/subdir/sub/test0.txt",
+			"subdir2/subdir/sub/120-px-file.txt",
+		);
+		sort( $expected );
+
+		// Actual listing (no trailing slash)
+		$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)." );
+
+		// Actual listing (with trailing slash)
+		$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 = array(
+			"test1.txt",
+			"test2.txt",
+			"test3.txt",
+			"test4.txt",
+			"test5.txt",
+			"sub/test0.txt",
+			"sub/120-px-file.txt",
+		);
+		sort( $expected );
+
+		// Actual listing (no trailing slash)
+		$list = array();
+		$iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/subdir2/subdir" ) );
+		foreach ( $iter as $file ) {
+			$list[] = $file;
+		}
+		sort( $list );
+
+		$this->assertEquals( $expected, $list, "Correct file listing ($backendName)." );
+
+		// Actual listing (with trailing slash)
+		$list = array();
+		$iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/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;
+		}
+		sort( $list );
+
+		$this->assertEquals( $expected, $list, "Correct file listing ($backendName), second iteration." );
+
+		foreach ( $files as $file ) { // clean up
+			$this->backend->doOperation( array( 'op' => 'delete', 'src' => $file ) );
+		}
+
+		$iter = $this->backend->getFileList( array( 'dir' => "$base/unittest-cont1/not/exists" ) );
+		foreach ( $iter as $iter ) {} // no errors
+	}
+
+	// test helper wrapper for backend prepare() function
+	private function prepare( array $params ) {
+		$this->dirsToPrune[] = $params['dir'];
+		return $this->backend->prepare( $params );
+	}
+
+	function tearDownFiles() {
+		foreach ( $this->filesToPrune as $file ) {
+			@unlink( $file );
+		}
+		$containers = array( 'unittest-cont1', 'unittest-cont2', 'unittest-cont3' );
+		foreach ( $containers as $container ) {
+			$this->deleteFiles( $container );
+		}
+		foreach ( $this->dirsToPrune as $dir ) {
+			$this->recursiveClean( $dir );
+		}
+		$this->filesToPrune = $this->dirsToPrune = array();
+	}
+
+	private function deleteFiles( $container ) {
+		$base = $this->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 ) );
+			}
+		}
+	}
+
+	private function recursiveClean( $dir ) {
+		do {
+			if ( !$this->backend->clean( array( 'dir' => $dir ) )->isOK() ) {
+				break;
+			}
+		} while ( $dir = FileBackend::parentStoragePath( $dir ) );
+	}
+
+	function tearDown() {
+		parent::tearDown();
+	}
+}
diff --git a/tests/phpunit/includes/filerepo/FileRepoTest.php b/tests/phpunit/includes/filerepo/FileRepoTest.php
new file mode 100644
index 00000000..0f023138
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/FileRepoTest.php
@@ -0,0 +1,41 @@
+<?php
+
+class FileRepoTest extends MediaWikiTestCase {
+
+	/**
+	 * @expectedException MWException
+	 */
+	function testFileRepoConstructionOptionCanNotBeNull() {
+		$f = new FileRepo();
+	}
+	/**
+	 * @expectedException MWException
+	 */
+	function testFileRepoConstructionOptionCanNotBeAnEmptyArray() {
+		$f = new FileRepo( array() );
+	}
+	/**
+	 * @expectedException MWException
+	 */
+	function testFileRepoConstructionOptionNeedNameKey() {
+		$f = new FileRepo( array(
+			'backend' => 'foobar'
+		) );
+	}
+	/**
+	 * @expectedException MWException
+	 */
+	function testFileRepoConstructionOptionNeedBackendKey() {
+		$f = new FileRepo( array(
+			'name' => 'foobar'
+		) );
+	}
+
+	function testFileRepoConstructionWithRequiredOptions() {
+		$f = new FileRepo( array(
+			'name'    => 'FileRepoTestRepository',
+			'backend' => 'local-backend',
+		));
+		$this->assertInstanceOf( 'FileRepo', $f );
+	}
+}
diff --git a/tests/phpunit/includes/filerepo/StoreBatchTest.php b/tests/phpunit/includes/filerepo/StoreBatchTest.php
new file mode 100644
index 00000000..6abceeb3
--- /dev/null
+++ b/tests/phpunit/includes/filerepo/StoreBatchTest.php
@@ -0,0 +1,122 @@
+<?php
+/**
+ * @group FileRepo
+ */
+class StoreBatchTest extends MediaWikiTestCase {
+
+	public function setUp() {
+		global $wgFileBackends;
+		parent::setUp();
+
+		# Forge a FSRepo object to not have to rely on local wiki settings
+		$tmpPrefix = wfTempDir() . '/storebatch-test-' . time() . '-' . mt_rand();
+		if ( $this->getCliArg( 'use-filebackend=' ) ) {
+			$name = $this->getCliArg( 'use-filebackend=' );
+			$useConfig = array();
+			foreach ( $wgFileBackends as $conf ) {
+				if ( $conf['name'] == $name ) {
+					$useConfig = $conf;
+				}
+			}
+			$useConfig['name'] = 'local-testing'; // swap name
+			$class = $useConfig['class'];
+			$backend = new $class( $useConfig );
+		} else {
+			$backend = new FSFileBackend( array(
+				'name'        => 'local-testing',
+				'lockManager' => 'nullLockManager',
+				'containerPaths' => array(
+					'unittests-public'  => "{$tmpPrefix}-public",
+					'unittests-thumb'   => "{$tmpPrefix}-thumb",
+					'unittests-temp'    => "{$tmpPrefix}-temp",
+					'unittests-deleted' => "{$tmpPrefix}-deleted",
+				)
+			) );
+		}
+		$this->repo = new FileRepo( array(
+			'name'    => 'unittests',
+			'backend' => $backend
+		) );
+
+		$this->date = gmdate( "YmdHis" );
+		$this->createdFiles = array();
+	}
+
+	/**
+	 * 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().
+	 */
+	private function storeit($originalName, $srcPath, $flags) {
+		$hashPath = $this->repo->getHashPath( $originalName );
+		$dstRel = "$hashPath{$this->date}!$originalName";
+		$dstUrlRel = $hashPath . $this->date . '!' . rawurlencode( $originalName );
+
+		$result = $this->repo->store( $srcPath, 'temp', $dstRel, $flags );
+		$result->value = $this->repo->getVirtualUrl( 'temp' ) . '/' . $dstUrlRel;
+		$this->createdFiles[] = $result->value;
+		return $result;
+	}
+
+	/**
+	 * Test storing a file using different flags.
+	 *
+	 * @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.
+	 */
+	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}" );
+		if ( $fromrepo ) {
+			$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 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 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 should fail because we're overwriting different content.
+		if ( $fromrepo ) {
+			$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}" );
+	}
+
+	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/json/ServicesJsonTest.php b/tests/phpunit/includes/json/ServicesJsonTest.php
new file mode 100644
index 00000000..8f2421a2
--- /dev/null
+++ b/tests/phpunit/includes/json/ServicesJsonTest.php
@@ -0,0 +1,93 @@
+<?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/JavaScriptMinifierTest.php b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
index aa05500e..d2bfeedf 100644
--- a/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
+++ b/tests/phpunit/includes/libs/JavaScriptMinifierTest.php
@@ -84,6 +84,13 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
 			// And also 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={}'),
+
+			/* Some structures that might look invalid at first sight */
+			array( "var a = 5.;", "var a=5.;" ),
+			array( "5.0.toString();", "5.0.toString();" ),
+			array( "5..toString();", "5..toString();" ),
+			array( "5...toString();", false ),
+			array( "5.\n.toString();", '5..toString();' ),
 		);
 	}
 
@@ -102,4 +109,40 @@ class JavaScriptMinifierTest extends MediaWikiTestCase {
 
 		$this->assertEquals( $expectedOutput, $minified, "Minified output should be in the form expected." );
 	}
+
+	/**
+	 * @dataProvider provideBug32548
+	 */
+	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.
+		$prefix = 'var longVarName' . str_repeat('_', 973) . '=';
+		$suffix = ',shortVarName=0;';
+
+		$input = $prefix . $num . $suffix;
+		$expected = $prefix . "\n" . $num . $suffix;
+
+		$minified = JavaScriptMinifier::minify( $input );
+
+		$this->assertEquals( $expected, $minified, "Line breaks must not occur in middle of exponent");
+	}
+
+	function provideBug32548() {
+		return array(
+			array(
+				// This one gets interpreted all together by the prior code;
+				// no break at the 'E' happens.
+				'1.23456789E55',				
+			),
+			array(
+				// This one breaks under the bad code; splits between 'E' and '+'
+				'1.23456789E+5',
+			),
+			array(
+				// This one breaks under the bad code; splits between 'E' and '-'
+				'1.23456789E-5',
+			),
+		);
+	}
 }
diff --git a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
index a0d5cd86..f4f52dd8 100644
--- a/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
+++ b/tests/phpunit/includes/media/BitmapMetadataHandlerTest.php
@@ -14,10 +14,15 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
 	 * translation (to en) where XMP should win.
 	 */
 	public function testMultilingualCascade() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
+		if ( !wfDl( 'exif' ) ) {
+			$this->markTestSkipped( "This test needs the exif extension." );
+		}
+		if ( !wfDl( 'xml' ) ) {
+			$this->markTestSkipped( "This test needs the xml extension." );
 		}
+		global $wgShowEXIF;
+		$oldExif = $wgShowEXIF;
+		$wgShowEXIF = true;
 
 		$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
 			'/Xmp-exif-multilingual_test.jpg' );
@@ -32,6 +37,8 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
 			'Did not extract any ImageDescription info?!' );
 
 		$this->assertEquals( $expected, $meta['ImageDescription'] );
+
+		$wgShowEXIF = $oldExif;
 	}
 
 	/**
@@ -49,6 +56,16 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
 			$meta['JPEGFileComment'][0] );
 	}
 
+	/**
+	 * Make sure a bad iptc block doesn't stop the other metadata
+	 * from being extracted.
+	 */
+	public function testBadIPTC() {
+		$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
+			'iptc-invalid-psir.jpg' );
+		$this->assertEquals( 'Created with GIMP', $meta['JPEGFileComment'][0] );
+	}
+
 	public function testIPTCDates() {
 		$meta = BitmapMetadataHandler::Jpeg( $this->filePath .
 			'iptc-timetest.jpg' );
@@ -95,6 +112,9 @@ class BitmapMetadataHandlerTest extends MediaWikiTestCase {
 	}
 
 	public function testPNGXMP() {
+		if ( !wfDl( 'xml' ) ) {
+			$this->markTestSkipped( "This test needs the xml extension." );
+		}
 		$handler = new BitmapMetadataHandler();
 		$result = $handler->png( $this->filePath . 'xmp.png' );
 		$expected = array (
diff --git a/tests/phpunit/includes/media/BitmapScalingTest.php b/tests/phpunit/includes/media/BitmapScalingTest.php
index 5bcd3232..11d9dc47 100644
--- a/tests/phpunit/includes/media/BitmapScalingTest.php
+++ b/tests/phpunit/includes/media/BitmapScalingTest.php
@@ -3,13 +3,16 @@
 class BitmapScalingTest extends MediaWikiTestCase {
 
 	function setUp() {
-		global $wgMaxImageArea;
+		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;
+		global $wgMaxImageArea, $wgCustomConvertCommand;
 		$wgMaxImageArea = $this->oldMaxImageArea;
+		$wgCustomConvertCommand = $this->oldCustomConvertCommand;
 	}
 	/**
 	 * @dataProvider provideNormaliseParams
@@ -105,14 +108,22 @@ class BitmapScalingTest extends MediaWikiTestCase {
 		$file = new FakeDimensionFile( array( 4000, 4000 ) );
 		$handler = new BitmapHandler;
 		$params = array( 'width' => '3700' ); // Still bigger than max size.
-		$this->assertFalse( $handler->normaliseParams( $file, $params ) );
+		$this->assertEquals( 'TransformParameterError', 
+			get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
 	}
 	function testTooBigMustRenderImage() {
 		$file = new FakeDimensionFile( array( 4000, 4000 ) );
 		$file->mustRender = true;
 		$handler = new BitmapHandler;
 		$params = array( 'width' => '5000' ); // Still bigger than max size.
-		$this->assertFalse( $handler->normaliseParams( $file, $params ) );
+		$this->assertEquals( 'TransformParameterError', 
+			get_class( $handler->doTransform( $file, 'dummy path', '', $params ) ) );
+	}
+	
+	function testImageArea() {
+		$file = new FakeDimensionFile( array( 7, 9 ) );
+		$handler = new BitmapHandler;
+		$this->assertEquals( 63, $handler->getImageArea( $file ) );
 	}
 }
 
@@ -120,7 +131,8 @@ class FakeDimensionFile extends File {
 	public $mustRender = false;
 
 	public function __construct( $dimensions ) {
-		parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), null );
+		parent::__construct( Title::makeTitle( NS_FILE, 'Test' ), 
+			new NullRepo( null ) );
 		
 		$this->dimensions = $dimensions;
 	}
@@ -133,4 +145,7 @@ class FakeDimensionFile extends File {
 	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 4282d3c8..b2f6b7ba 100644
--- a/tests/phpunit/includes/media/ExifBitmapTest.php
+++ b/tests/phpunit/includes/media/ExifBitmapTest.php
@@ -1,4 +1,5 @@
 <?php
+
 class ExifBitmapTest extends MediaWikiTestCase {
 
 	public function setUp() {
@@ -17,42 +18,23 @@ class ExifBitmapTest extends MediaWikiTestCase {
 	}
 
 	public function testIsOldBroken() {
-		if ( !wfDl( 'exif' ) ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::OLD_BROKEN_FILE );
 		$this->assertEquals( ExifBitmapHandler::METADATA_COMPATIBLE, $res );
 	}
 	public function testIsBrokenFile() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$res = $this->handler->isMetadataValid( null, ExifBitmapHandler::BROKEN_FILE );
 		$this->assertEquals( ExifBitmapHandler::METADATA_GOOD, $res );
 	}
 	public function testIsInvalid() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$res = $this->handler->isMetadataValid( null, 'Something Invalid Here.' );
 		$this->assertEquals( ExifBitmapHandler::METADATA_BAD, $res );
 	}
 	public function testGoodMetadata() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$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 );
 	}
 	public function testIsOldGood() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$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 );
@@ -60,10 +42,6 @@ class ExifBitmapTest extends MediaWikiTestCase {
 	// Handle metadata from paged tiff handler (gotten via instant commons)
 	// gracefully.
 	public function testPagedTiffHandledGracefully() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$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 );
diff --git a/tests/phpunit/includes/media/ExifRotationTest.php b/tests/phpunit/includes/media/ExifRotationTest.php
index 639091d0..25149a05 100644
--- a/tests/phpunit/includes/media/ExifRotationTest.php
+++ b/tests/phpunit/includes/media/ExifRotationTest.php
@@ -5,15 +5,26 @@
  */
 class ExifRotationTest extends MediaWikiTestCase {
 
+	/** track directories creations. Content will be deleted. */
+	private $createdDirs = array();
+
 	function setUp() {
 		parent::setUp();
-		$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
 		$this->handler = new BitmapHandler();
-		$this->repo = new FSRepo(array(
-			'name' => 'temp',
-			'directory' => wfTempDir() . '/exif-test-' . time() . '-' . mt_rand(),
-			'url' => 'http://localhost/thumbtest'
-		));
+		$filePath = dirname( __FILE__ ) . '/../../data/media';
+
+		$tmpDir = wfTempDir() . '/exif-test-' . time() . '-' . mt_rand();
+		$this->createdDirs[] = $tmpDir;
+
+		$this->repo = new FSRepo( array(
+			'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." );
 		}
@@ -25,10 +36,23 @@ class ExifRotationTest extends MediaWikiTestCase {
 		$this->oldAuto = $wgEnableAutoRotation;
 		$wgEnableAutoRotation = true;
 	}
+
 	public function tearDown() {
 		global $wgShowEXIF, $wgEnableAutoRotation;
 		$wgShowEXIF = $this->show;
 		$wgEnableAutoRotation = $this->oldAuto;
+
+		$this->tearDownFiles();
+	}
+
+	private function tearDownFiles() {
+		foreach( $this->createdDirs as $dir ) {
+			wfRecursiveRemoveDir( $dir );
+		}
+	}
+
+	function __destruct() {
+		$this->tearDownFiles();
 	}
 
 	/**
@@ -39,7 +63,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 		if ( !BitmapHandler::canRotate() ) {
 			$this->markTestSkipped( "This test needs a rasterizer that can auto-rotate." );
 		}
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+		$file = $this->dataFile( $name, $type );
 		$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
 		$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
 	}
@@ -66,13 +90,13 @@ class ExifRotationTest extends MediaWikiTestCase {
 				throw new MWException('bogus test data format ' . $size);
 			}
 
-			$file = $this->localFile( $name, $type );
-			$thumb = $file->transform( $params, File::RENDER_NOW );
+			$file = $this->dataFile( $name, $type );
+			$thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
 
 			$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
 			$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
 
-			$gis = getimagesize( $thumb->getPath() );
+			$gis = getimagesize( $thumb->getLocalCopyPath() );
 			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");
@@ -84,8 +108,9 @@ class ExifRotationTest extends MediaWikiTestCase {
 		}
 	}
 
-	private function localFile( $name, $type ) {
-		return new UnregisteredLocalFile( false, $this->repo, $this->filePath . $name, $type );
+	private function dataFile( $name, $type ) {
+		return new UnregisteredLocalFile( false, $this->repo,
+			"mwstore://localtesting/data/$name", $type );
 	}
 
 	function providerFiles() {
@@ -129,7 +154,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 		global $wgEnableAutoRotation;
 		$wgEnableAutoRotation = false;
 
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $name, $type );
+		$file = $this->dataFile( $name, $type );
 		$this->assertEquals( $info['width'], $file->getWidth(), "$name: width check" );
 		$this->assertEquals( $info['height'], $file->getHeight(), "$name: height check" );
 
@@ -158,13 +183,13 @@ class ExifRotationTest extends MediaWikiTestCase {
 				throw new MWException('bogus test data format ' . $size);
 			}
 
-			$file = $this->localFile( $name, $type );
-			$thumb = $file->transform( $params, File::RENDER_NOW );
+			$file = $this->dataFile( $name, $type );
+			$thumb = $file->transform( $params, File::RENDER_NOW | File::RENDER_FORCE );
 
 			$this->assertEquals( $out[0], $thumb->getWidth(), "$name: thumb reported width check for $size" );
 			$this->assertEquals( $out[1], $thumb->getHeight(), "$name: thumb reported height check for $size" );
 
-			$gis = getimagesize( $thumb->getPath() );
+			$gis = getimagesize( $thumb->getLocalCopyPath() );
 			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");
@@ -242,7 +267,7 @@ class ExifRotationTest extends MediaWikiTestCase {
 			array(
 				270,
 				array( self::TEST_HEIGHT, self::TEST_WIDTH ) 
-			),			
+			),
 		);
 	}
 }
diff --git a/tests/phpunit/includes/media/ExifTest.php b/tests/phpunit/includes/media/ExifTest.php
index 9b490e92..b39c15e4 100644
--- a/tests/phpunit/includes/media/ExifTest.php
+++ b/tests/phpunit/includes/media/ExifTest.php
@@ -4,6 +4,9 @@ class ExifTest extends MediaWikiTestCase {
 	public function setUp() {
 		$this->mediaPath = dirname( __FILE__ ) . '/../../data/media/';
 
+		if ( !wfDl( 'exif' ) ) {
+			$this->markTestSkipped( "This test needs the exif extension." );
+		}
                 global $wgShowEXIF;
                 $this->showExif = $wgShowEXIF;
                 $wgShowEXIF = true;
@@ -14,9 +17,6 @@ class ExifTest extends MediaWikiTestCase {
         }
 
 	public function testGPSExtraction() {
-		if ( !wfDl( 'exif' ) ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 
 		$filename = $this->mediaPath . 'exif-gps.jpg';
 		$seg = JpegMetadataExtractor::segmentSplitter( $filename ); 
@@ -32,9 +32,6 @@ class ExifTest extends MediaWikiTestCase {
 		$this->assertEquals( $expected, $data, '', 0.0000000001 );
 	}
 	public function testUnicodeUserComment() {
-		if ( !wfDl( 'exif' ) ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 
 		$filename = $this->mediaPath . 'exif-user-comment.jpg';
 		$seg = JpegMetadataExtractor::segmentSplitter( $filename ); 
diff --git a/tests/phpunit/includes/media/FormatMetadataTest.php b/tests/phpunit/includes/media/FormatMetadataTest.php
index db36dea3..8a632f52 100644
--- a/tests/phpunit/includes/media/FormatMetadataTest.php
+++ b/tests/phpunit/includes/media/FormatMetadataTest.php
@@ -1,13 +1,31 @@
 <?php
 class FormatMetadataTest extends MediaWikiTestCase {
-	public function testInvalidDate() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
+	public function setUp() {
+		if ( !wfDl( 'exif' ) ) {
+			$this->markTestSkipped( "This test needs the exif extension." );
 		}
-		
-		$file = UnregisteredLocalFile::newFromPath( dirname( __FILE__ ) . 
-			'/../../data/media/broken_exif_date.jpg', 'image/jpeg' );
+		$filePath = dirname( __FILE__ ) .  '/../../data/media';
+		$this->backend = new FSFileBackend( array(
+			'name'           => 'localtesting',
+			'lockManager'    => 'nullLockManager',
+			'containerPaths' => array( 'data' => $filePath )
+		) );
+		$this->repo = new FSRepo( array(
+			'name'    => 'temp',
+			'url'     => 'http://localhost/thumbtest',
+			'backend' => $this->backend
+		) );
+		global $wgShowEXIF;
+		$this->show = $wgShowEXIF;
+		$wgShowEXIF = true;
+	}
+	public function tearDown() {
+		global $wgShowEXIF;
+		$wgShowEXIF = $this->show;
+	}
+
+	public function testInvalidDate() {
+		$file = $this->dataFile( 'broken_exif_date.jpg', 'image/jpeg' );
 		
 		// Throws an error if bug hit
 		$meta = $file->formatMetadata();
@@ -26,4 +44,9 @@ class FormatMetadataTest extends MediaWikiTestCase {
 			$meta['visible'][$dateIndex]['value'],
 			'File with invalid date metadata (bug 29471)' );
 	}
-}
\ No newline at end of file
+
+	private function dataFile( $name, $type ) {
+		return new UnregisteredLocalFile( false, $this->repo,
+			"mwstore://localtesting/data/$name", $type );
+	}
+}
diff --git a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
index 59b30441..47fc368b 100644
--- a/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/GIFMetadataExtractorTest.php
@@ -63,6 +63,7 @@ class GIFMetadataExtractorTest extends MediaWikiTestCase {
                                                                                                     
 <?xpacket end='w'?>
 EOF;
+		$xmpNugget = str_replace( "\r", '', $xmpNugget ); // Windows compat
 
 		return array(
 			array( 'nonanimated.gif', array(
diff --git a/tests/phpunit/includes/media/GIFTest.php b/tests/phpunit/includes/media/GIFTest.php
index 42c25ca5..36658358 100644
--- a/tests/phpunit/includes/media/GIFTest.php
+++ b/tests/phpunit/includes/media/GIFTest.php
@@ -2,12 +2,22 @@
 class GIFHandlerTest extends MediaWikiTestCase {
 
 	public function setUp() {
-		$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+		$this->filePath = dirname( __FILE__ ) .  '/../../data/media';
+		$this->backend = new FSFileBackend( array(
+			'name'           => 'localtesting',
+			'lockManager'    => 'nullLockManager',
+			'containerPaths' => array( 'data' => $this->filePath )
+		) );
+		$this->repo = new FSRepo( array(
+			'name'    => 'temp',
+			'url'     => 'http://localhost/thumbtest',
+			'backend' => $this->backend
+		) );
 		$this->handler = new GIFHandler();
 	}
 
 	public function testInvalidFile() {
-		$res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+		$res = $this->handler->getMetadata( null, $this->filePath . '/README' );
 		$this->assertEquals( GIFHandler::BROKEN_FILE, $res );
 	}
 	/**
@@ -16,8 +26,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataIsAnimated
 	 */
 	public function testIsAnimanted( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/gif' );
+		$file = $this->dataFile( $filename, 'image/gif' );
 		$actual = $this->handler->isAnimatedImage( $file );
 		$this->assertEquals( $expected, $actual );
 	}
@@ -34,8 +43,7 @@ class GIFHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataGetImageArea
 	 */
 	public function testGetImageArea( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/gif' );
+		$file = $this->dataFile( $filename, 'image/gif' );
 		$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
 		$this->assertEquals( $expected, $actual );
 	}
@@ -71,15 +79,20 @@ class GIFHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataGetMetadata
 	 */
 	public function testGetMetadata( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/gif' );
-		$actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+		$file = $this->dataFile( $filename, 'image/gif' );
+		$actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
 		$this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
 	}
+
 	public function dataGetMetadata() {
 		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;}}' ),
 		);
 	}
+
+	private function dataFile( $name, $type ) {
+		return new UnregisteredLocalFile( false, $this->repo,
+			"mwstore://localtesting/data/$name", $type );
+	}
 }
diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
index 61fc9c81..f48382a4 100644
--- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php
@@ -1,5 +1,5 @@
 <?php
-/*
+/**
  * @todo Could use a test of extended XMP segments. Hard to find programs that
  * create example files, and creating my own in vim propbably wouldn't
  * serve as a very good "test". (Adobe photoshop probably creates such files
@@ -59,7 +59,7 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
 	public function testPSIRExtraction() {
 		$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
 		$expected = '50686f746f73686f7020332e30003842494d04040000000000181c02190004746573741c02190003666f6f1c020000020004';
-		$this->assertEquals( $expected, bin2hex( $res['PSIR'] ) );
+		$this->assertEquals( $expected, bin2hex( $res['PSIR'][0] ) );
 	}
 	public function testXMPExtractionAltAppId() {
 		$res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-alt.jpg' );
@@ -70,19 +70,19 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase {
 
 	public function testIPTCHashComparisionNoHash() {
 		$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-psir.jpg' );
-		$res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+		$res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
 
 		$this->assertEquals( 'iptc-no-hash', $res );
 	}
 	public function testIPTCHashComparisionBadHash() {
 		$segments = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-iptc-bad-hash.jpg' );
-		$res = JpegMetadataExtractor::doPSIR( $segments['PSIR'] );
+		$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'] );
+		$res = JpegMetadataExtractor::doPSIR( $segments['PSIR'][0] );
 
 		$this->assertEquals( 'iptc-good-hash', $res );
 	}
diff --git a/tests/phpunit/includes/media/JpegTest.php b/tests/phpunit/includes/media/JpegTest.php
index 713a3410..ddabf5b8 100644
--- a/tests/phpunit/includes/media/JpegTest.php
+++ b/tests/phpunit/includes/media/JpegTest.php
@@ -3,22 +3,24 @@ class JpegTest extends MediaWikiTestCase {
 
 	public function setUp() {
 		$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+		if ( !wfDl( 'exif' ) ) {
+			$this->markTestSkipped( "This test needs the exif extension." );
+		}
+		global $wgShowEXIF;
+		$this->show = $wgShowEXIF;
+		$wgShowEXIF = true;
+	}
+	public function tearDown() {
+		global $wgShowEXIF;
+		$wgShowEXIF = $this->show;
 	}
 
 	public function testInvalidFile() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$jpeg = new JpegHandler;
 		$res = $jpeg->getMetadata( null, $this->filePath . 'README' );
 		$this->assertEquals( ExifBitmapHandler::BROKEN_FILE, $res );
 	}
 	public function testJpegMetadataExtraction() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
-			$this->markTestIncomplete( "This test needs the exif extension." );
-		}
 		$h = new JpegHandler;
 		$res = $h->getMetadata( null, $this->filePath . 'test.jpg' );
 		$expected = 'a:7:{s:16:"ImageDescription";s:9:"Test file";s:11:"XResolution";s:4:"72/1";s:11:"YResolution";s:4:"72/1";s:14:"ResolutionUnit";i:2;s:16:"YCbCrPositioning";i:1;s:15:"JPEGFileComment";a:1:{i:0;s:17:"Created with GIMP";}s:22:"MEDIAWIKI_EXIF_VERSION";i:2;}';
diff --git a/tests/phpunit/includes/media/MediaHandlerTest.php b/tests/phpunit/includes/media/MediaHandlerTest.php
new file mode 100644
index 00000000..99df4f80
--- /dev/null
+++ b/tests/phpunit/includes/media/MediaHandlerTest.php
@@ -0,0 +1,50 @@
+<?php
+
+class MediaHandlerTest extends MediaWikiTestCase {
+	function testFitBoxWidth() {
+		$vals = array(
+			array(
+				'width' => 50,
+				'height' => 50,
+				'tests' => array(
+					50 => 50,
+					17 => 17,
+					18 => 18 ) ),
+			array(
+				'width' => 366,
+				'height' => 300,
+				'tests' => array(
+					50 => 61,
+					17 => 21,
+					18 => 22 ) ),
+			array(
+				'width' => 300,
+				'height' => 366,
+				'tests' => array(
+					50 => 41,
+					17 => 14,
+					18 => 15 ) ),
+			array(
+				'width' => 100,
+				'height' => 400,
+				'tests' => array(
+					50 => 12,
+					17 => 4,
+					18 => 4 ) ) );
+		foreach ( $vals as $row ) {
+			$tests = $row['tests'];
+			$height = $row['height'];
+			$width = $row['width'];
+			foreach ( $tests as $max => $expected ) {
+				$y = round( $expected * $height / $width );
+				$result = MediaHandler::fitBoxWidth( $width, $height, $max );
+				$y2 = round( $result * $height / $width );
+				$this->assertEquals( $expected,
+					$result,
+					"($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" );
+			}
+		}
+	}
+}
+
+
diff --git a/tests/phpunit/includes/media/PNGTest.php b/tests/phpunit/includes/media/PNGTest.php
index b782918c..b6f911fd 100644
--- a/tests/phpunit/includes/media/PNGTest.php
+++ b/tests/phpunit/includes/media/PNGTest.php
@@ -2,12 +2,22 @@
 class PNGHandlerTest extends MediaWikiTestCase {
 
 	public function setUp() {
-		$this->filePath = dirname( __FILE__ ) . '/../../data/media/';
+		$this->filePath = dirname( __FILE__ ) .  '/../../data/media';
+		$this->backend = new FSFileBackend( array(
+			'name'           => 'localtesting',
+			'lockManager'    => 'nullLockManager',
+			'containerPaths' => array( 'data' => $this->filePath )
+		) );
+		$this->repo = new FSRepo( array(
+			'name'    => 'temp',
+			'url'     => 'http://localhost/thumbtest',
+			'backend' => $this->backend
+		) );
 		$this->handler = new PNGHandler();
 	}
 
 	public function testInvalidFile() {
-		$res = $this->handler->getMetadata( null, $this->filePath . 'README' );
+		$res = $this->handler->getMetadata( null, $this->filePath . '/README' );
 		$this->assertEquals( PNGHandler::BROKEN_FILE, $res );
 	}
 	/**
@@ -16,8 +26,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataIsAnimated
 	 */
 	public function testIsAnimanted( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/png' );
+		$file = $this->dataFile( $filename, 'image/png' );
 		$actual = $this->handler->isAnimatedImage( $file );
 		$this->assertEquals( $expected, $actual );
 	}
@@ -34,8 +43,7 @@ class PNGHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataGetImageArea
 	 */
 	public function testGetImageArea( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/png' );
+		$file = $this->dataFile($filename, 'image/png' );
 		$actual = $this->handler->getImageArea( $file, $file->getWidth(), $file->getHeight() );
 		$this->assertEquals( $expected, $actual );
 	}
@@ -73,9 +81,8 @@ class PNGHandlerTest extends MediaWikiTestCase {
 	 * @dataProvider dataGetMetadata
 	 */
 	public function testGetMetadata( $filename, $expected ) {
-		$file = UnregisteredLocalFile::newFromPath( $this->filePath . $filename,
-			'image/png' );
-		$actual = $this->handler->getMetadata( $file, $this->filePath . $filename );
+		$file = $this->dataFile( $filename, 'image/png' );
+		$actual = $this->handler->getMetadata( $file, "$this->filePath/$filename" );
 //		$this->assertEquals( unserialize( $expected ), unserialize( $actual ) );
 		$this->assertEquals( ( $expected ), ( $actual ) );
 	}
@@ -85,4 +92,9 @@ class PNGHandlerTest extends MediaWikiTestCase {
 			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;}}' ), 
 		);
 	}
+
+	private function dataFile( $name, $type ) {
+		return new UnregisteredLocalFile( false, $this->repo,
+			"mwstore://localtesting/data/$name", $type );
+	}
 }
diff --git a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
index c2c81b98..526beae8 100644
--- a/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
+++ b/tests/phpunit/includes/media/SVGMetadataExtractorTest.php
@@ -62,23 +62,33 @@ class SVGMetadataExtractorTest extends MediaWikiTestCase {
 					'height' => 60
 				)
 			),
+			array(
+				"$base/Toll_Texas_1.svg",
+				// This file triggered bug 31719, needs entity expansion in the xmlns checks
+				array(
+					'width' => 385,
+					'height' => 385
+				)
+			)
 		);
 	}
 
 	function providerSvgFilesWithXMLMetadata() {
 		$base = dirname( __FILE__ ) . '/../../data/media';
-		return array(
-			array(
-				"$base/US_states_by_total_state_tax_revenue.svg",
-				array(
-					'height' => 593,
-					'metadata' =>
+		$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"/>
       </ns4:Work>
-    </rdf:RDF>',
+    </rdf:RDF>';
+		$metadata = str_replace( "\r", '', $metadata ); // Windows compat
+		return array(
+			array(
+				"$base/US_states_by_total_state_tax_revenue.svg",
+				array(
+					'height' => 593,
+					'metadata' => $metadata,
 					'width' => 959
 				)
 			),
diff --git a/tests/phpunit/includes/media/TiffTest.php b/tests/phpunit/includes/media/TiffTest.php
index 0a7e8e8c..d4cf503b 100644
--- a/tests/phpunit/includes/media/TiffTest.php
+++ b/tests/phpunit/includes/media/TiffTest.php
@@ -15,16 +15,15 @@ class TiffTest extends MediaWikiTestCase {
 	}
 
 	public function testInvalidFile() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
+		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 );
 	}
+
 	public function testTiffMetadataExtraction() {
-		global $wgShowEXIF;
-		if ( !$wgShowEXIF ) {
+		if ( !wfDl( 'exif' ) ) {
 			$this->markTestIncomplete( "This test needs the exif extension." );
 		}
 		$res = $this->handler->getMetadata( null, $this->filePath . 'test.tiff' );
diff --git a/tests/phpunit/includes/media/XMPTest.php b/tests/phpunit/includes/media/XMPTest.php
index d1ec4767..c952b23c 100644
--- a/tests/phpunit/includes/media/XMPTest.php
+++ b/tests/phpunit/includes/media/XMPTest.php
@@ -1,6 +1,12 @@
 <?php
 class XMPTest extends MediaWikiTestCase {
 
+	function setUp() {
+		if ( !wfDl( 'xml' ) ) {
+			$this->markTestSkipped( 'Requires libxml to do XMP parsing' );
+		}
+	}
+
 	/**
 	 * Put XMP in, compare what comes out...
 	 *
@@ -11,9 +17,6 @@ class XMPTest extends MediaWikiTestCase {
 	 * @dataProvider dataXMPParse
 	 */
 	public function testXMPParse( $xmp, $expected, $info ) {
-		if ( !function_exists( 'xml_parser_create_ns' ) ) {
-			$this->markIncomplete( 'Requires libxml to do XMP parsing' );
-		}
 		if ( !is_string( $xmp ) || !is_array( $expected ) ) {
 			throw new Exception( "Invalid data provided to " . __METHOD__ );
 		}
diff --git a/tests/phpunit/includes/media/XMPValidateTest.php b/tests/phpunit/includes/media/XMPValidateTest.php
new file mode 100644
index 00000000..e2bb8d8d
--- /dev/null
+++ b/tests/phpunit/includes/media/XMPValidateTest.php
@@ -0,0 +1,47 @@
+<?php
+class XMPValidateTest extends MediaWikiTestCase {
+
+	/**
+	 * @dataProvider providerDate
+	 */
+	function testValidateDate( $value, $expected ) {
+		// The method should modify $value.
+		XMPValidate::validateDate( array(), $value, true );
+		$this->assertEquals( $expected, $value );
+	}
+
+	function providerDate() {
+		/* For reference valid date formats are:
+		 * YYYY
+		 * YYYY-MM
+		 * YYYY-MM-DD
+		 * YYYY-MM-DDThh:mmTZD
+		 * YYYY-MM-DDThh:mm:ssTZD
+		 * YYYY-MM-DDThh:mm:ss.sTZD
+		 * (Time zone is optional)
+		 */
+		return array(
+			array( '1992', '1992' ),
+			array( '1992-04', '1992:04' ),
+			array( '1992-02-01', '1992:02:01' ),
+			array( '2011-09-29', '2011:09:29' ),
+			array( '1982-12-15T20:12', '1982:12:15 20:12' ),
+			array( '1982-12-15T20:12Z', '1982:12:15 20:12' ),
+			array( '1982-12-15T20:12+02:30', '1982:12:15 22:42' ),
+			array( '1982-12-15T01:12-02:30', '1982:12:14 22:42' ),
+			array( '1982-12-15T20:12:11', '1982:12:15 20:12:11' ),
+			array( '1982-12-15T20:12:11Z', '1982:12:15 20:12:11' ),
+			array( '1982-12-15T20:12:11+01:10', '1982:12:15 21:22:11' ),
+			array( '2045-12-15T20:12:11', '2045:12:15 20:12:11' ),
+			array( '1867-06-01T15:00:00', '1867:06:01 15:00:00' ),
+			/* some invalid ones */
+			array( '2001--12', null ),
+			array( '2001-5-12', null ),
+			array( '2001-5-12TZ', null ),
+			array( '2001-05-12T15', null ),
+			array( '2001-12T15:13', null ),
+		);
+
+	}
+
+}
diff --git a/tests/phpunit/includes/parser/MagicVariableTest.php b/tests/phpunit/includes/parser/MagicVariableTest.php
index a47653e3..31645313 100644
--- a/tests/phpunit/includes/parser/MagicVariableTest.php
+++ b/tests/phpunit/includes/parser/MagicVariableTest.php
@@ -6,8 +6,8 @@
  * As of february 2011, it only tests some revisions and date related
  * magic variables.
  *
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
  * @file
  */
 
@@ -38,6 +38,12 @@ class MagicVariableTest extends MediaWikiTestCase {
 
 		# initialize parser output
 		$this->testParser->clearState();
+
+		# Needs a title to do magic word stuff
+		$title = Title::newFromText( 'Tests' );
+		$title->mRedirect = false; # Else it needs a db connection just to check if it's a redirect (when deciding the page language)
+
+		$this->testParser->setTitle( $title );
 	}
 
 	/** destroy parser (TODO: is it really neded?)*/
diff --git a/tests/phpunit/includes/parser/MediaWikiParserTest.php b/tests/phpunit/includes/parser/MediaWikiParserTest.php
index 18510d9a..816c017a 100644
--- a/tests/phpunit/includes/parser/MediaWikiParserTest.php
+++ b/tests/phpunit/includes/parser/MediaWikiParserTest.php
@@ -1,10 +1,8 @@
 <?php
-
-require_once( dirname( __FILE__ ) . '/ParserHelpers.php' );
 require_once( dirname( __FILE__ ) . '/NewParserTest.php' );
 
 /**
- * The UnitTest must be either a class that inherits from PHPUnit_Framework_TestCase 
+ * The UnitTest must be either a class that inherits from MediaWikiTestCase
  * or a class that provides a public static suite() method which returns 
  * an PHPUnit_Framework_Test object
  * 
@@ -20,9 +18,13 @@ class MediaWikiParserTest {
 
 		foreach ( $wgParserTestFiles as $filename ) {
 			$testsName = basename( $filename, '.txt' );
-			$className = /*ucfirst( basename( dirname( $filename ) ) ) .*/ ucfirst( basename( $filename, '.txt' ) );
+			/* 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 = \"" . addslashes( $filename ) . "\"; } " );
+			eval( "/** @group Database\n@group Parser\n*/ class $className extends NewParserTest { protected \$file = '" . strtr( $filename, array( "'" => "\\'", '\\' => '\\\\' ) ) . "'; } " );
 
 			$parserTester = new $className( $testsName );
 			$suite->addTestSuite( new ReflectionClass ( $parserTester ) );
diff --git a/tests/phpunit/includes/parser/NewParserTest.php b/tests/phpunit/includes/parser/NewParserTest.php
index f4d5f757..d1221ca8 100644
--- a/tests/phpunit/includes/parser/NewParserTest.php
+++ b/tests/phpunit/includes/parser/NewParserTest.php
@@ -8,13 +8,12 @@
  * @group Stub
  */
 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
 	 * When running tests from several files, all tests will see all articles.
 	 */
-	
-	public $uploadDir;
+	static protected $backendToUse;
+
 	public $keepUploads = false;
 	public $runDisabled = false;
 	public $regex = '';
@@ -31,16 +30,12 @@ class NewParserTest extends MediaWikiTestCase {
 	public $memoryLimit = 50;
 
 	protected $file = false;
-	
-	/*function __construct($a = null,$b = array(),$c = null ) {
-		parent::__construct($a,$b,$c);
-	}*/
-	
+
 	function setUp() {
 		global $wgContLang, $wgNamespaceProtection, $wgNamespaceAliases;
 		global $wgHooks, $IP;
 		$wgContLang = Language::factory( 'en' );
-		
+
 		//Setup CLI arguments
 		if ( $this->getCliArg( 'regex=' ) ) {
 			$this->regex = $this->getCliArg( 'regex=' );
@@ -48,11 +43,11 @@ class NewParserTest extends MediaWikiTestCase {
 			# Matches anything
 			$this->regex = '';
 		}
-		
+
 		$this->keepUploads = $this->getCliArg( 'keep-uploads' );
-				
+
 		$tmpGlobals = array();
-		
+
 		$tmpGlobals['wgScript'] = '/index.php';
 		$tmpGlobals['wgScriptPath'] = '/';
 		$tmpGlobals['wgArticlePath'] = '/wiki/$1';
@@ -60,15 +55,14 @@ class NewParserTest extends MediaWikiTestCase {
 		$tmpGlobals['wgStylePath'] = '/skins';
 		$tmpGlobals['wgThumbnailScriptPath'] = false;
 		$tmpGlobals['wgLocalFileRepo'] = array(
-			'class' => 'LocalRepo',
-			'name' => 'local',
-			'directory' => wfTempDir() . '/test-repo',
-			'url' => 'http://example.com/images',
-			'deletedDir' => wfTempDir() . '/test-repo/delete',
-			'hashLevels' => 2,
+			'class'           => 'LocalRepo',
+			'name'            => 'local',
+			'url'             => 'http://example.com/images',
+			'hashLevels'      => 2,
 			'transformVia404' => false,
+			'backend'         => 'local-backend'
 		);
-		
+		$tmpGlobals['wgForeignFileRepos'] = array();
 		$tmpGlobals['wgEnableParserCache'] = false;
 		$tmpGlobals['wgHooks'] = $wgHooks;
 		$tmpGlobals['wgDeferredUpdateList'] = array();
@@ -79,16 +73,16 @@ class NewParserTest extends MediaWikiTestCase {
 		// $tmpGlobals['wgContLang'] = new StubContLang;
 		$tmpGlobals['wgUser'] = new User;
 		$context = new RequestContext();
-		$tmpGlobals['wgLang'] = $context->getLang();
+		$tmpGlobals['wgLang'] = $context->getLanguage();
 		$tmpGlobals['wgOut'] = $context->getOutput();
 		$tmpGlobals['wgParser'] = new StubObject( 'wgParser', $GLOBALS['wgParserConf']['class'], array( $GLOBALS['wgParserConf'] ) );
-		$tmpGlobals['wgRequest'] = new WebRequest;
+		$tmpGlobals['wgRequest'] = $context->getRequest();
 
 		if ( $GLOBALS['wgStyleDirectory'] === false ) {
 			$tmpGlobals['wgStyleDirectory'] = "$IP/skins";
 		}
-		
-		
+
+
 		foreach ( $tmpGlobals as $var => $val ) {
 			if ( array_key_exists( $var, $GLOBALS ) ) {
 				$this->savedInitialGlobals[$var] = $GLOBALS[$var];
@@ -96,31 +90,38 @@ class NewParserTest extends MediaWikiTestCase {
 
 			$GLOBALS[$var] = $val;
 		}
-		
+
 		$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;
 		}
-		
+
 		global $wgNamespaceProtection, $wgNamespaceAliases;
-		
+
 		$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();
 	}
-	
+
 	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(
@@ -158,17 +159,14 @@ class NewParserTest extends MediaWikiTestCase {
 			 * @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' ) );
+			), __METHOD__, array( 'IGNORE' )
+		);
 
 
 		# Update certain things in site_stats
-		$this->db->insert( 'site_stats', 
+		$this->db->insert( 'site_stats',
 			array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ),
-			__METHOD__,
-			/**
-			 * @todo Fixme! Same as above!
-			 */
-			array( 'IGNORE' )
+			__METHOD__
 		);
 
 		# Reinitialise the LocalisationCache to match the database state
@@ -177,50 +175,66 @@ class NewParserTest extends MediaWikiTestCase {
 		# Clear the message cache
 		MessageCache::singleton()->clear();
 
-		$this->uploadDir = $this->setupUploadDir();
-
 		$user = User::newFromId( 0 );
 		LinkCache::singleton()->clear(); # Avoids the odd failure at creating the nullRevision
-		
+
+		# Upload DB table entries for files.
+		# 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.
 		$image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) );
-		$image->recordUpload2( '', '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
-			), $this->db->timestamp( '20010115123500' ), $user );
+		if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
+			$image->recordUpload2(
+				'', // archive name
+				'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 ), 
+				$this->db->timestamp( '20010115123500' ), $user
+			);
+		}
 
 		# This image will be blacklisted in [[MediaWiki:Bad image list]]
 		$image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) );
-		$image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array(
-			'size'        => 12345,
-			'width'       => 320,
-			'height'      => 240,
-			'bits'        => 24,
-			'media_type'  => MEDIATYPE_BITMAP,
-			'mime'        => 'image/jpeg',
-			'metadata'    => serialize( array() ),
-			'sha1'        => wfBaseConvert( '', 16, 36, 31 ),
-			'fileExists'  => true
-			), $this->db->timestamp( '20010115123500' ), $user );
-
+		if ( !$this->db->selectField( 'image', '1', array( 'img_name' => $image->getName() ) ) ) {
+			$image->recordUpload2(
+				'', // archive name
+				'zomgnotcensored', 
+				'Borderline image', 
+				array(
+					'size'        => 12345,
+					'width'       => 320,
+					'height'      => 240,
+					'bits'        => 24,
+					'media_type'  => MEDIATYPE_BITMAP,
+					'mime'        => 'image/jpeg',
+					'metadata'    => serialize( array() ),
+					'sha1'        => wfBaseConvert( '', 16, 36, 31 ),
+					'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 = '' ) {
+		global $wgFileBackends;
 		# Find out values for some special options.
 		$lang =
 			self::getOptionValue( 'language', $opts, 'en' );
@@ -231,19 +245,48 @@ class NewParserTest extends MediaWikiTestCase {
 		$linkHolderBatchSize =
 			self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 );
 
+		$uploadDir = $this->getUploadDir();
+		if ( $this->getCliArg( 'use-filebackend=' ) ) {
+			if ( self::$backendToUse ) {
+				$backend = self::$backendToUse;
+			} else {
+				$name = $this->getCliArg( 'use-filebackend=' );
+				$useConfig = array();
+				foreach ( $wgFileBackends as $conf ) {
+					if ( $conf['name'] == $name ) {
+						$useConfig = $conf;
+					}
+				}
+				$useConfig['name'] = 'local-backend'; // swap name
+				$class = $conf['class'];
+				self::$backendToUse = new $class( $useConfig );
+				$backend = self::$backendToUse;
+			}
+		} else {
+			$backend = new FSFileBackend( array(
+				'name'        => 'local-backend',
+				'lockManager' => 'nullLockManager',
+				'containerPaths' => array(
+					'local-public' => "$uploadDir",
+					'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',
-				'directory' => $this->uploadDir,
-				'url' => 'http://example.com/images',
-				'hashLevels' => 2,
+				'class'           => 'LocalRepo',
+				'name'            => 'local',
+				'url'             => 'http://example.com/images',
+				'hashLevels'      => 2,
 				'transformVia404' => false,
+				'backend'         => $backend
 			),
 			'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ),
 			'wgStylePath' => '/skins',
@@ -262,7 +305,7 @@ class NewParserTest extends MediaWikiTestCase {
 			'wgThumbnailScriptPath' => false,
 			'wgUseImageResize' => false,
 			'wgUseTeX' => isset( $opts['math'] ),
-			'wgMathDirectory' => $this->uploadDir . '/math',
+			'wgMathDirectory' => $uploadDir . '/math',
 			'wgLocaltimezone' => 'UTC',
 			'wgAllowExternalImages' => true,
 			'wgUseTidy' => false,
@@ -283,6 +326,7 @@ class NewParserTest extends MediaWikiTestCase {
 			'wgExternalLinkTarget' => false,
 			'wgAlwaysUseTidy' => false,
 			'wgHtml5' => true,
+			'wgCleanupPresentationalAttributes' => true,
 			'wgWellFormedXml' => true,
 			'wgAllowMicrodataAttributes' => true,
 			'wgAdaptiveMessageCache' => true,
@@ -312,39 +356,41 @@ class NewParserTest extends MediaWikiTestCase {
 		$langObj = Language::factory( $lang );
 		$GLOBALS['wgContLang'] = $langObj;
 		$context = new RequestContext();
-		$GLOBALS['wgLang'] = $context->getLang();
+		$GLOBALS['wgLang'] = $context->getLanguage();
 
 		$GLOBALS['wgMemc'] = new EmptyBagOStuff;
 		$GLOBALS['wgOut'] = $context->getOutput();
+		$GLOBALS['wgUser'] = $context->getUser();
 
 		global $wgHooks;
 
 		$wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup';
-		$wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup';
 		$wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp';
 
 		MagicWord::clearCache();
+		RepoGroup::destroySingleton();
+		FileBackendGroup::destroySingleton();
+
+		# Create dummy files in storage
+		$this->setupUploads();
 
 		# 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::singleton()->destroyInstance();
-		
-		global $wgUser;
-		$wgUser = new User();
+
+		return $context;
 	}
 
 	/**
-	 * Create a dummy uploads directory which will contain a couple
-	 * of files in order to pass existence tests.
+	 * Get an FS upload directory (only applies to FSFileBackend)
 	 *
 	 * @return String: the directory
 	 */
-	protected function setupUploadDir() {
-		global $IP;
-
+	protected function getUploadDir() {
 		if ( $this->keepUploads ) {
 			$dir = wfTempDir() . '/mwParser-images';
 
@@ -361,70 +407,67 @@ class NewParserTest extends MediaWikiTestCase {
 			return $dir;
 		}
 
-		wfMkdirParents( $dir . '/3/3a' );
-		copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
-		wfMkdirParents( $dir . '/0/09' );
-		copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
-
 		return $dir;
 	}
-	
+
+	/**
+	 * Create a dummy uploads directory which will contain a couple
+	 * of files in order to pass existence tests.
+	 *
+	 * @return String: the directory
+	 */
+	protected function setupUploads() {
+		global $IP;
+
+		$base = $this->getBaseDir();
+		$backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
+		$backend->prepare( array( 'dir' => "$base/local-public/3/3a" ) );
+		$backend->store( array(
+			'src' => "$IP/skins/monobook/headbg.jpg", 'dst' => "$base/local-public/3/3a/Foobar.jpg"
+		) );
+		$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"
+		) );
+	}
+
 	/**
 	 * Restore default values and perform any necessary clean-up
 	 * after each test runs.
 	 */
 	protected function teardownGlobals() {
-		RepoGroup::destroySingleton();
-		LinkCache::singleton()->clear();
+		$this->teardownUploads();
 
 		foreach ( $this->savedGlobals as $var => $val ) {
 			$GLOBALS[$var] = $val;
 		}
-		
-		$this->teardownUploadDir( $this->uploadDir );
+
+		RepoGroup::destroySingleton();
+		LinkCache::singleton()->clear();
 	}
 
 	/**
 	 * Remove the dummy uploads directory
 	 */
-	private function teardownUploadDir( $dir ) {
+	private function teardownUploads() {
 		if ( $this->keepUploads ) {
 			return;
 		}
 
+		$base = $this->getBaseDir();
 		// delete the files first, then the dirs.
 		self::deleteFiles(
 			array (
-				"$dir/3/3a/Foobar.jpg",
-				"$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg",
-				"$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg",
-				"$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg",
-				"$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg",
-
-				"$dir/0/09/Bad.jpg",
+				"$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",
 
-				"$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
-			)
-		);
+				"$base/local-public/0/09/Bad.jpg",
+				"$base/local-thumb/0/09/Bad.jpg",
 
-		self::deleteDirs(
-			array (
-				"$dir/3/3a",
-				"$dir/3",
-				"$dir/thumb/6/65",
-				"$dir/thumb/6",
-				"$dir/thumb/3/3a/Foobar.jpg",
-				"$dir/thumb/3/3a",
-				"$dir/thumb/3",
-
-				"$dir/0/09/",
-				"$dir/0/",
-				"$dir/thumb",
-				"$dir/math/f/a/5",
-				"$dir/math/f/a",
-				"$dir/math/f",
-				"$dir/math",
-				"$dir",
+				"$base/local-public/math/f/a/5/fa50b8b616463173474302ca3e63586b.png",
 			)
 		);
 	}
@@ -434,25 +477,24 @@ class NewParserTest extends MediaWikiTestCase {
 	 * @param $files Array: full paths to files to delete.
 	 */
 	private static function deleteFiles( $files ) {
+		$backend = RepoGroup::singleton()->getLocalRepo()->getBackend();
 		foreach ( $files as $file ) {
-			if ( file_exists( $file ) ) {
-				unlink( $file );
+			$backend->delete( array( 'src' => $file ), array( 'force' => 1 ) );
+		}
+		foreach ( $files as $file ) {
+			$tmp = $file;
+			while ( $tmp = FileBackend::parentStoragePath( $tmp ) ) {
+				if ( !$backend->clean( array( 'dir' => $tmp ) )->isOK() ) {
+					break;
+				}
 			}
 		}
 	}
 
-	/**
-	 * Delete the specified directories, if they exist. Must be empty.
-	 * @param $dirs Array: full paths to directories to delete.
-	 */
-	private static function deleteDirs( $dirs ) {
-		foreach ( $dirs as $dir ) {
-			if ( is_dir( $dir ) ) {
-				rmdir( $dir );
-			}
-		}
+	protected function getBaseDir() {
+		return 'mwstore://local-backend';
 	}
-	
+
 	public function parserTestProvider() {
 		if ( $this->file === false ) {
 			global $wgParserTestFiles;
@@ -460,25 +502,29 @@ class NewParserTest extends MediaWikiTestCase {
 		}
 		return new TestFileIterator( $this->file, $this );
 	}
-	
+
 	/**
 	 * Set the file from whose tests will be run by this instance
 	 */
 	public function setParserTestFile( $filename ) {
 		$this->file = $filename;
 	}
-	
+
 	/** @dataProvider parserTestProvider */
 	public function testParserTest( $desc, $input, $result, $opts, $config ) {
-		if ( !preg_match( '/' . $this->regex . '/', $desc ) ) return; //$this->markTestSkipped( 'Filtered out by the user' );
+		if ( $this->regex != '' && !preg_match( '/' . $this->regex . '/', $desc ) ) {
+			$this->assertTrue( true ); // XXX: don't flood output with "test made no assertions"
+			//$this->markTestSkipped( 'Filtered out by the user' );
+			return;
+		}
 
 		wfDebug( "Running parser test: $desc\n" );
 
 		$opts = $this->parseOptions( $opts );
-		$this->setupGlobals( $opts, $config );
+		$context = $this->setupGlobals( $opts, $config );
 
-		$user = new User();
-		$options = ParserOptions::newFromUser( $user );
+		$user = $context->getUser();
+		$options = ParserOptions::newFromContext( $context );
 
 		if ( isset( $opts['title'] ) ) {
 			$titleText = $opts['title'];
@@ -490,7 +536,7 @@ class NewParserTest extends MediaWikiTestCase {
 		$local = isset( $opts['local'] );
 		$preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null;
 		$parser = $this->getParser( $preprocessor );
-		
+
 		$title = Title::newFromText( $titleText );
 
 		if ( isset( $opts['pst'] ) ) {
@@ -505,8 +551,7 @@ class NewParserTest extends MediaWikiTestCase {
 			$replace = $opts['replace'][1];
 			$out = $parser->replaceSection( $input, $section, $replace );
 		} elseif ( isset( $opts['comment'] ) ) {
-			$linker = $user->getSkin();
-			$out = $linker->formatComment( $input, $title, $local );
+			$out = Linker::formatComment( $input, $title, $local );
 		} elseif ( isset( $opts['preload'] ) ) {
 			$out = $parser->getpreloadText( $input, $title, $options );
 		} else {
@@ -524,10 +569,9 @@ class NewParserTest extends MediaWikiTestCase {
 			if ( isset( $opts['ill'] ) ) {
 				$out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) );
 			} elseif ( isset( $opts['cat'] ) ) {
-				global $wgOut;
-
-				$wgOut->addCategoryLinks( $output->getCategories() );
-				$cats = $wgOut->getCategoryLinks();
+				$outputPage = $context->getOutput();
+				$outputPage->addCategoryLinks( $output->getCategories() );
+				$cats = $outputPage->getCategoryLinks();
 
 				if ( isset( $cats['normal'] ) ) {
 					$out = $this->tidy( implode( ' ', $cats['normal'] ) );
@@ -541,38 +585,41 @@ class NewParserTest extends MediaWikiTestCase {
 		}
 
 		$this->teardownGlobals();
-		
+
 		$this->assertEquals( $result, $out, $desc );
 	}
-	
+
 	/**
 	 * Run a fuzz test series
 	 * Draw input from a set of test files
+	 *
+	 * @todo @fixme Needs some work to not eat memory until the world explodes
+	 *
+	 * @group ParserFuzz
 	 */
 	function testFuzzTests() {
-		
-		$this->markTestIncomplete( "Somebody is serializing PDO objects, that's a no-no" );
-		
 		global $wgParserTestFiles;
-		
+
 		$files = $wgParserTestFiles;
-		
+
 		if( $this->getCliArg( 'file=' ) ) {
 			$files = array( $this->getCliArg( 'file=' ) );
 		}
-		
+
 		$dict = $this->getFuzzInput( $files );
 		$dictSize = strlen( $dict );
 		$logMaxLength = log( $this->maxFuzzTestLength );
 
+		ini_set( 'memory_limit', $this->memoryLimit * 1048576 );
+
 		$user = new User;
 		$opts = ParserOptions::newFromUser( $user );
 		$title = Title::makeTitle( NS_MAIN, 'Parser_test' );
 
 		$id = 1;
-		
+
 		while ( true ) {
-			
+
 			// Generate test input
 			mt_srand( ++$this->fuzzSeed );
 			$totalLength = mt_rand( 1, $this->maxFuzzTestLength );
@@ -594,7 +641,7 @@ class NewParserTest extends MediaWikiTestCase {
 				$this->assertTrue( true, "Test $id, fuzz seed {$this->fuzzSeed}" );
 			} 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()}" );
 			}
 
@@ -611,18 +658,18 @@ class NewParserTest extends MediaWikiTestCase {
 					foreach ( $memStats as $name => $usage ) {
 						$ret .= "$name: $usage\n";
 					}
-					
+
 					throw new MWException( $ret );
 				}
 			}
-			
+
 			$id++;
-			
+
 		}
 	}
 
 	//Various getter functions
-	
+
 	/**
 	 * Get an input dictionary from a set of parser test files
 	 */
@@ -640,7 +687,7 @@ class NewParserTest extends MediaWikiTestCase {
 
 		return $dict;
 	}
-	
+
 	/**
 	 * Get a memory usage breakdown
 	 */
@@ -675,7 +722,7 @@ class NewParserTest extends MediaWikiTestCase {
 
 		return $memStats;
 	}
-	
+
 	/**
 	 * Get a Parser object
 	 */
@@ -693,23 +740,20 @@ class NewParserTest extends MediaWikiTestCase {
 	//Various action functions
 
 	public function addArticle( $name, $text, $line ) {
-		self::$articles[$name] = $text;
-	}	
-	
+		self::$articles[$name] = array( $text, $line );
+	}
+
 	public function publishTestArticles() {
 		if ( empty( self::$articles ) ) {
 			return;
 		}
 
-		foreach ( self::$articles as $name => $text ) {
-			$title = Title::newFromText( $name );
-
-			if ( $title->getArticleID( Title::GAID_FOR_UPDATE ) == 0 ) {
-				ParserTest::addArticle( $name, $text );
-			}
+		foreach ( self::$articles as $name => $info ) {
+			list( $text, $line ) = $info;
+			ParserTest::addArticle( $name, $text, $line, 'ignoreduplicate' );
 		}
 	}
-	
+
 	/**
 	 * Steal a callback function from the primary parser, save it for
 	 * application to our scary parser. If the hook is not installed,
@@ -730,8 +774,8 @@ class NewParserTest extends MediaWikiTestCase {
 		return isset( $wgParser->mFunctionHooks[$name] );
 	}
 	//Various "cleanup" functions
-	
-	/*
+
+	/**
 	 * Run the "tidy" command on text if the $wgUseTidy
 	 * global is true
 	 *
@@ -747,7 +791,7 @@ class NewParserTest extends MediaWikiTestCase {
 
 		return $text;
 	}
-	
+
 	/**
 	 * Remove last character if it is a newline
 	 */
@@ -760,12 +804,8 @@ class NewParserTest extends MediaWikiTestCase {
 		}
 	}
 
-	public function showRunFile( $file ) {
-		/* NOP */
-	}
-
 	//Test options parser functions
-	
+
 	protected function parseOptions( $instring ) {
 		$opts = array();
 		// foo
@@ -820,7 +860,7 @@ class NewParserTest extends MediaWikiTestCase {
 		}
 		return $opts;
 	}
-	
+
 	protected function cleanupOption( $opt ) {
 		if ( substr( $opt, 0, 1 ) == '"' ) {
 			return substr( $opt, 1, -1 );
@@ -831,7 +871,7 @@ class NewParserTest extends MediaWikiTestCase {
 		}
 		return $opt;
 	}
-	
+
 	/**
 	 * Use a regex to find out the value of an option
 	 * @param $key String: name of option val to retrieve
diff --git a/tests/phpunit/includes/parser/ParserHelpers.php b/tests/phpunit/includes/parser/ParserHelpers.php
deleted file mode 100644
index 4a6ce7c4..00000000
--- a/tests/phpunit/includes/parser/ParserHelpers.php
+++ /dev/null
@@ -1,136 +0,0 @@
-<?php
-
-class PHPUnitParserTest extends ParserTest {
-	function showTesting( $desc ) {
-		/* Do nothing since we don't want to show info during PHPUnit testing. */
-	}
-
-	public function showSuccess( $desc ) {
-		PHPUnit_Framework_Assert::assertTrue( true, $desc );
-		return true;
-	}
-
-	public function showFailure( $desc, $expected, $got ) {
-		PHPUnit_Framework_Assert::assertEquals( $expected, $got, $desc );
-		return false;
-	}
-	
-	public function setupRecorder( $options ) {
-		$this->recorder = new PHPUnitTestRecorder( $this );
-	}
-}
-
-class ParserUnitTest extends MediaWikiTestCase {
-	private $test = "";
-
-	public function __construct( $suite, $test = null ) {
-		parent::__construct();
-		$this->test = $test;
-		$this->suite = $suite;
-	}
-
-	function count() { return 1; }
-
-	public function run( PHPUnit_Framework_TestResult $result = null ) {
-		PHPUnit_Framework_Assert::resetCount();
-		if ( $result === NULL ) {
-			$result = new PHPUnit_Framework_TestResult;
-		}
-
-		$this->suite->publishTestArticles(); // Add articles needed by the tests.
-		$backend = new ParserTestSuiteBackend;
-		$result->startTest( $this );
-
-		// Support the transition to PHPUnit 3.5 where PHPUnit_Util_Timer is replaced with PHP_Timer
-		if ( class_exists( 'PHP_Timer' ) ) {
-			PHP_Timer::start();
-		} else {
-			PHPUnit_Util_Timer::start();
-		}
-
-		$r = false;
-		try {
-			# Run the test.
-			# On failure, the subclassed backend will throw an exception with
-			# the details.
-			$pt = new PHPUnitParserTest;
-			$r =  $pt->runTest( $this->test['test'], $this->test['input'],
-				$this->test['result'], $this->test['options'], $this->test['config']
-			);
-		}
-		catch ( PHPUnit_Framework_AssertionFailedError $e ) {
-
-			// PHPUnit_Util_Timer -> PHP_Timer support (see above)
-			if ( class_exists( 'PHP_Timer' ) ) {
-				$result->addFailure( $this, $e, PHP_Timer::stop() );
-			} else {
-				$result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() );
-			}
-		}
-		catch ( Exception $e ) {
-			// PHPUnit_Util_Timer -> PHP_Timer support (see above)
-			if ( class_exists( 'PHP_Timer' ) ) {
-				$result->addFailure( $this, $e, PHP_Timer::stop() );
-			} else {
-				$result->addFailure( $this, $e, PHPUnit_Util_Timer::stop() );
-			}
-		}
-
-		// PHPUnit_Util_Timer -> PHP_Timer support (see above)
-		if ( class_exists( 'PHP_Timer' ) ) {
-			$result->endTest( $this, PHP_Timer::stop() );
-		} else {
-			$result->endTest( $this, PHPUnit_Util_Timer::stop() );
-		}
-
-		$backend->recorder->record( $this->test['test'], $r );
-		$this->addToAssertionCount( PHPUnit_Framework_Assert::getCount() );
-
-		return $result;
-	}
-
-	public function toString() {
-		return $this->test['test'];
-	}
-
-}
-
-class ParserTestSuiteBackend extends PHPUnit_FrameWork_TestSuite {
-	public $recorder;
-	public $term;
-	static $usePHPUnit = false;
-
-	function __construct() {
-		parent::__construct();
-		$this->setupRecorder(null);
-		self::$usePHPUnit = method_exists('PHPUnit_Framework_Assert', 'assertEquals');
-	}
-
-	function showTesting( $desc ) {
-	}
-
-	function showRunFile( $path ) {
-	}
-
-	function showTestResult( $desc, $result, $out ) {
-		if ( $result === $out ) {
-			return self::showSuccess( $desc, $result, $out );
-		} else {
-			return self::showFailure( $desc, $result, $out );
-	}
-	}
-
-	public function setupRecorder( $options ) {
-		$this->recorder = new PHPUnitTestRecorder( $this );
-	}
-}
-
-class PHPUnitTestRecorder extends TestRecorder {
-	function record( $test, $result ) {
-		$this->total++;
-		$this->success += $result;
-
-	}
-
-	function reportPercentage( $success, $total ) { }
-}
diff --git a/tests/phpunit/includes/parser/ParserPreloadTest.php b/tests/phpunit/includes/parser/ParserPreloadTest.php
new file mode 100644
index 00000000..0e8ef530
--- /dev/null
+++ b/tests/phpunit/includes/parser/ParserPreloadTest.php
@@ -0,0 +1,67 @@
+<?php
+/**
+ * Basic tests for Parser::getPreloadText
+ * @author Antoine Musso
+ */
+class ParserPreloadTest extends MediaWikiTestCase {
+	private $testParser;
+	private $testParserOptions;
+	private $title;
+
+	function setUp() {
+		$this->testParserOptions = new ParserOptions();
+
+		$this->testParser = new Parser();
+		$this->testParser->Options( $this->testParserOptions );
+		$this->testParser->clearState();
+
+		$this->title = Title::newFromText( 'Preload Test' );
+	}
+
+	function tearDown() {
+		unset( $this->testParser );
+		unset( $this->title );
+	}
+
+	/**
+	 * @covers Parser::getPreloadText
+	 */
+	function testPreloadSimpleText() {
+		$this->assertPreloaded( 'simple', 'simple' );
+	}
+
+	/**
+	 * @covers Parser::getPreloadText
+	 */
+	function testPreloadedPreIsUnstripped() {
+		$this->assertPreloaded(
+			'<pre>monospaced</pre>',
+			'<pre>monospaced</pre>',
+			'<pre> in preloaded text must be unstripped (bug 27467)'
+		);
+	}
+
+	/**
+	 * @covers Parser::getPreloadText
+	 */
+	function testPreloadedNowikiIsUnstripped() {
+		$this->assertPreloaded(
+			'<nowiki>[[Dummy title]]</nowiki>',
+			'<nowiki>[[Dummy title]]</nowiki>',
+			'<nowiki> in preloaded text must be unstripped (bug 27467)'
+		);
+	}
+
+	function assertPreloaded( $expected, $text, $msg='') {
+		$this->assertEquals(
+			$expected,
+			$this->testParser->getPreloadText(
+				$text,
+				$this->title,
+				$this->testParserOptions
+			),
+			$msg
+		);
+	}
+
+}
diff --git a/tests/phpunit/includes/parser/PreprocessorTest.php b/tests/phpunit/includes/parser/PreprocessorTest.php
index 7a5948d4..9d3499a0 100644
--- a/tests/phpunit/includes/parser/PreprocessorTest.php
+++ b/tests/phpunit/includes/parser/PreprocessorTest.php
@@ -49,43 +49,45 @@ class PreprocessorTest extends MediaWikiTestCase {
 			array( "== Foo ==\n== Bar == \n", "<root><h level=\"2\" i=\"1\">== Foo ==</h>\n<h level=\"2\" i=\"2\">== Bar == </h>\n</root>" ),
 			array( "===========", "<root><h level=\"5\" i=\"1\">===========</h></root>" ),
 			array( "Foo\n=\n==\n=\n", "<root>Foo\n=\n==\n=\n</root>" ),
-			array( "{{Foo}}", "<root><template lineStart=\"1\"><title>Foo</title></template></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 lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template></root>" ),
-			array( "{{Foo|bar}}a", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part></template>a</root>" ),
-			array( "{{Foo|bar|baz}}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><title>Foo</title><part><name>1</name>=<value>bar</value></part></template></root>" ),
-			array( "{{Foo|=bar}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name></name>=<value>bar</value></part></template></root>" ),
-			array( "{{Foo|bar=baz}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name>bar</name>=<value>baz</value></part></template></root>" ),
-			array( "{{Foo|1=bar|baz}}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><title>1</title></tplarg></root>" ),
-			array( "{{{1|}}}", "<root><tplarg lineStart=\"1\"><title>1</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
-			array( "{{{Foo}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title></tplarg></root>" ),
-			array( "{{{Foo|}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
-			array( "{{{Foo|bar|baz}}}", "<root><tplarg lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></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><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|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( "{{{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>" ),
+			array( "{{{Foo|}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value></value></part></tplarg></root>" ),
+			array( "{{{Foo|bar|baz}}}", "<root><tplarg><title>Foo</title><part><name index=\"1\" /><value>bar</value></part><part><name index=\"2\" /><value>baz</value></part></tplarg></root>" ),
 			array( "{<!-- -->{Foo}}", "<root>{<comment>&lt;!-- --&gt;</comment>{Foo}}</root>" ),
 			array( "{{{{Foobar}}}}", "<root>{<tplarg><title>Foobar</title></tplarg>}</root>" ),
-			array( "{{{ {{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title> <template><title>Foo</title></template> </title></tplarg></root>" ),
-			array( "{{ {{{Foo}}} }}", "<root><template lineStart=\"1\"><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ),
-			array( "{{{{{Foo}}}}}", "<root><template lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
-			array( "{{{{{Foo}} }}}", "<root><tplarg lineStart=\"1\"><title><template><title>Foo</title></template> </title></tplarg></root>" ),
-			array( "{{{{{{Foo}}}}}}", "<root><tplarg lineStart=\"1\"><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ),
+			array( "{{{ {{Foo}} }}}", "<root><tplarg><title> <template><title>Foo</title></template> </title></tplarg></root>" ),
+			array( "{{ {{{Foo}}} }}", "<root><template><title> <tplarg><title>Foo</title></tplarg> </title></template></root>" ),
+			array( "{{{{{Foo}}}}}", "<root><template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
+			array( "{{{{{Foo}} }}}", "<root><tplarg><title><template><title>Foo</title></template> </title></tplarg></root>" ),
+			array( "{{{{{{Foo}}}}}}", "<root><tplarg><title><tplarg><title>Foo</title></tplarg></title></tplarg></root>" ),
 			array( "{{{{{{Foo}}}}}", "<root>{<template><title><tplarg><title>Foo</title></tplarg></title></template></root>" ),
 			array( "[[[Foo]]", "<root>[[[Foo]]</root>" ),
-			array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template lineStart=\"1\"><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not
+			array( "{{Foo|[[[[bar]]|baz]]}}", "<root><template><title>Foo</title><part><name index=\"1\" /><value>[[[[bar]]|baz]]</value></part></template></root>" ), // This test is important, since it means the difference between having the [[ rule stacked or not
 			array( "{{Foo|[[[[bar]|baz]]}}", "<root>{{Foo|[[[[bar]|baz]]}}</root>" ),
 			array( "{{Foo|Foo [[[[bar]|baz]]}}", "<root>{{Foo|Foo [[[[bar]|baz]]}}</root>" ),
 			array( "Foo <display map>Bar</display map             >Baz", "<root>Foo <ext><name>display map</name><attr></attr><inner>Bar</inner><close>&lt;/display map             &gt;</close></ext>Baz</root>" ),
 			array( "Foo <display map foo>Bar</display map             >Baz", "<root>Foo <ext><name>display map</name><attr> foo</attr><inner>Bar</inner><close>&lt;/display map             &gt;</close></ext>Baz</root>" ),
 			array( "Foo <gallery bar=\"baz\" />", "<root>Foo <ext><name>gallery</name><attr> bar=&quot;baz&quot; </attr></ext></root>" ),
+			array( "Foo <gallery bar=\"1\" baz=2 />", "<root>Foo <ext><name>gallery</name><attr> bar=&quot;1&quot; baz=2 </attr></ext></root>" ),
 			array( "</foo>Foo<//foo>", "<root><ext><name>/foo</name><attr></attr><inner>Foo</inner><close>&lt;//foo&gt;</close></ext></root>" ), # Worth blacklisting IMHO
-			array( "{{#ifexpr: ({{{1|1}}} = 2) | Foo | Bar }}", "<root><template lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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 lineStart=\"1\"><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( "{{#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>"),
@@ -97,19 +99,54 @@ class PreprocessorTest extends MediaWikiTestCase {
 			array( "{{Foo|bar=[[baz]}}", "<root>{{Foo|bar=[[baz]}}</root>"),
 			array( "{{foo|", "<root>{{foo|</root>"),
 			array( "{{foo|}", "<root>{{foo|}</root>"),
-			array( "{{foo|} }}", "<root><template lineStart=\"1\"><title>foo</title><part><name index=\"1\" /><value>} </value></part></template></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 lineStart=\"1\"><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"),
+			array( "{{Foo|} Bar=}}", "<root><template><title>Foo</title><part><name>} Bar</name>=<value></value></part></template></root>"),
 			/* array( file_get_contents( dirname( __FILE__ ) . '/QuoteQuran.txt' ), file_get_contents( dirname( __FILE__ ) . '/QuoteQuranExpanded.txt' ) ), */
 		);
 	}
 
+	/**
+	 * Get XML preprocessor tree from the preprocessor (which may not be the
+	 * native XML-based one).
+	 *
+	 * @param string $wikiText
+	 * @return string
+	 */
+	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();
+		} else {
+			return $this->normalizeXml( $dom->__toString() );
+		}
+	}
+
+	/**
+	 * Normalize XML string to the form that a DOMDocument saves out.
+	 *
+	 * @param string $xml
+	 * @return string
+	 */
+	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
 	 */
 	function testPreprocessorOutput( $wikiText, $expectedXml ) {
-		$this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) );
+		$this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
 	}
 
 	/**
@@ -130,11 +167,12 @@ class PreprocessorTest extends MediaWikiTestCase {
 	function testPreprocessorOutputFiles( $filename ) {
 		$folder = dirname( __FILE__ ) . "/../../../parser/preprocess";
 		$wikiText = file_get_contents( "$folder/$filename.txt" );
-		$output = $this->mPreprocessor->preprocessToXml( $wikiText );
+		$output = $this->preprocessToXml( $wikiText );
 
 		$expectedFilename = "$folder/$filename.expected";
 		if ( file_exists( $expectedFilename ) ) {
-			$this->assertStringEqualsFile( $expectedFilename, $output );
+			$expectedXml = $this->normalizeXml( file_get_contents( $expectedFilename ) );
+			$this->assertEquals( $expectedXml, $output );
 		} else {
 			$tempFilename = tempnam( $folder, "$filename." );
 			file_put_contents( $tempFilename, $output );
@@ -189,7 +227,7 @@ class PreprocessorTest extends MediaWikiTestCase {
 	 * @dataProvider provideHeadings
 	 */
 	function testHeadings( $wikiText, $expectedXml ) {
-		$this->assertEquals( $expectedXml, $this->mPreprocessor->preprocessToXml( $wikiText ) );
+		$this->assertEquals( $this->normalizeXml( $expectedXml ), $this->preprocessToXml( $wikiText ) );
 	}
 }
 
diff --git a/tests/phpunit/includes/parser/TagHooks.php b/tests/phpunit/includes/parser/TagHooks.php
deleted file mode 100644
index 713ce846..00000000
--- a/tests/phpunit/includes/parser/TagHooks.php
+++ /dev/null
@@ -1,77 +0,0 @@
-<?php
-
-/**
- * @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" ) );
-	}
-		
-	/**
-	 * @dataProvider provideValidNames
-	 */
-	function testTagHooks( $tag ) {
-		global $wgParserConf;
-		$parser = new Parser( $wgParserConf );
-		
-		$parser->setHook( $tag, array( $this, 'tagCallback' ) );
-		$parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions );
-		$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;
-		$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.');
-	}
-	
-	/**
-	 * @dataProvider provideValidNames
-	 */
-	function testFunctionTagHooks( $tag ) {
-		global $wgParserConf;
-		$parser = new Parser( $wgParserConf );
-		
-		$parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), 0 );
-		$parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions );
-		$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;
-		$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.');
-	}
-	
-	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/TagHooksTest.php b/tests/phpunit/includes/parser/TagHooksTest.php
new file mode 100644
index 00000000..713ce846
--- /dev/null
+++ b/tests/phpunit/includes/parser/TagHooksTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @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" ) );
+	}
+		
+	/**
+	 * @dataProvider provideValidNames
+	 */
+	function testTagHooks( $tag ) {
+		global $wgParserConf;
+		$parser = new Parser( $wgParserConf );
+		
+		$parser->setHook( $tag, array( $this, 'tagCallback' ) );
+		$parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions );
+		$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;
+		$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.');
+	}
+	
+	/**
+	 * @dataProvider provideValidNames
+	 */
+	function testFunctionTagHooks( $tag ) {
+		global $wgParserConf;
+		$parser = new Parser( $wgParserConf );
+		
+		$parser->setFunctionTagHook( $tag, array( $this, 'functionTagCallback' ), 0 );
+		$parserOutput = $parser->parse( "Foo<$tag>Bar</$tag>Baz", Title::newFromText( 'Test' ), new ParserOptions );
+		$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;
+		$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.');
+	}
+	
+	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/search/SearchEngineTest.php b/tests/phpunit/includes/search/SearchEngineTest.php
index 1a0fcd31..957907c7 100644
--- a/tests/phpunit/includes/search/SearchEngineTest.php
+++ b/tests/phpunit/includes/search/SearchEngineTest.php
@@ -12,7 +12,7 @@ class SearchEngineTest extends MediaWikiTestCase {
 		unset( $this->search );
 	}
 
-	/*
+	/**
 	 * Checks for database type & version.
 	 * Will skip current test if DB does not support search.
 	 */
@@ -64,8 +64,10 @@ class SearchEngineTest extends MediaWikiTestCase {
 		$this->assertTrue( is_object( $results ) );
 
 		$matches = array();
-		while ( $row = $results->next() ) {
+		$row = $results->next();
+		while ( $row ) {
 			$matches[] = $row->getTitle()->getPrefixedText();
+			$row = $results->next();
 		}
 		$results->free();
 		# Search is not guaranteed to return results in a certain order;
@@ -83,20 +85,18 @@ class SearchEngineTest extends MediaWikiTestCase {
 	 * @param $n Integer: unused
 	 */
 	function insertPage( $pageName, $text, $ns ) {
-		$dbw = $this->db;
 		$title = Title::newFromText( $pageName );
 
 		$user = User::newFromName( 'WikiSysop' );
 		$comment = 'Search Test';
 
 		// avoid memory leak...?
-		$linkCache = LinkCache::singleton();
-		$linkCache->clear();
+		LinkCache::singleton()->clear();
 
-		$article = new Article( $title );
-		$article->doEdit( $text, $comment, 0, false, $user );
+		$page = WikiPage::factory( $title );
+		$page->doEdit( $text, $comment, 0, false, $user );
 
-		$this->pageList[] = array( $title, $article->getId() );
+		$this->pageList[] = array( $title, $page->getId() );
 
 		return true;
 	}
diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
new file mode 100644
index 00000000..a33c7b68
--- /dev/null
+++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php
@@ -0,0 +1,79 @@
+<?php
+/**
+ * Test class to run the query of most of all our special pages
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+	die( 1 );
+}
+
+global $IP;
+require_once "$IP/includes/QueryPage.php"; // Needed to populate $wgQueryPages
+
+class QueryAllSpecialPagesTest extends MediaWikiTestCase {
+
+	/** List query pages that can not be tested automatically */
+	protected $manualTest = array(
+		'LinkSearchPage'
+	);
+
+	/**
+	 * Pages whose query use the same DB table more than once.
+	 * This is used to skip testing those pages when run against a MySQL backend
+	 * which does not support reopening a temporary table. See upstream bug:
+	 * http://bugs.mysql.com/bug.php?id=10327
+	 */
+	protected $reopensTempTable = array(
+		'BrokenRedirects',
+	);
+
+	/**
+	 * Initialize all query page objects
+	 */
+	function __construct() {
+		parent::__construct();
+
+		global $wgQueryPages;
+		foreach( $wgQueryPages as $page ) {
+			$class = $page[0];
+			if( ! in_array( $class, $this->manualTest ) ) {
+				$this->queryPages[$class] = new $class;
+			}
+		}
+	}
+
+	/**
+	 * Test SQL for each of our QueryPages objects
+	 * @group Database
+	 */
+	function testQuerypageSqlQuery() {
+		global $wgDBtype;
+
+		foreach( $this->queryPages as $page ) {
+
+			// With MySQL, skips special pages reopening a temporary table
+			// See http://bugs.mysql.com/bug.php?id=10327
+			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;
+				}
+
+			$msg = "SQL query for page {$page->getName()} should give a result wrapper object" ;
+
+			$result = $page->reallyDoQuery( 50 );
+			if( $result instanceof ResultWrapper ) {
+				$this->assertTrue( true, $msg );
+			} else {
+				$this->assertFalse( false, $msg );
+			}
+		}
+	}
+}
diff --git a/tests/phpunit/includes/specials/SpecialRecentchanges.php b/tests/phpunit/includes/specials/SpecialRecentchanges.php
deleted file mode 100644
index a98e7c1a..00000000
--- a/tests/phpunit/includes/specials/SpecialRecentchanges.php
+++ /dev/null
@@ -1,134 +0,0 @@
-<?php
-/**
- * Test class for SpecialRecentchanges class
- *
- * Copyright © 2011, Ashar Voultoiz
- *
- * @author Ashar Voultoiz
- */
-class SpecialRecentchangesTest extends MediaWikiTestCase {
-
-	/**
-	 * @var SpecialRecentChanges
-	 */
-	protected $rc;
-
-	function setUp() {
-	}
-
-	/** helper to test SpecialRecentchanges::buildMainQueryConds() */
-	private function assertConditions( $expected, $requestOptions = null, $message = '' ) {
-		global $wgRequest;
-		$savedGlobal = $wgRequest;
-
-		# Initialize a WebRequest object ...
-		$wgRequest = new FauxRequest( $requestOptions );
-		# ... then setup the rc object (which use wgRequest internally)
-		$this->rc = new SpecialRecentChanges();
-		$formOptions = $this->rc->setup( null );
-
-		# Filter out rc_timestamp conditions which depends on the test runtime
-		# This condition is not needed as of march 2, 2011 -- hashar
-		# @todo FIXME: Find a way to generate the correct rc_timestamp
-		$queryConditions = array_filter(
-			$this->rc->buildMainQueryConds( $formOptions ),
-			'SpecialRecentchangesTest::filterOutRcTimestampCondition'
-		);
-
-		$this->assertEquals(
-			$expected,
-			$queryConditions,
-			$message
-		);
-
-		$wgRequest = $savedGlobal;
-	}
-
-	/** return false if condition begin with 'rc_timestamp ' */
-	private static function filterOutRcTimestampCondition( $var ) {
-		return (false === strpos( $var, 'rc_timestamp ' ));
-
-	}
-
-	public function testRcNsFilter() {
-		$this->assertConditions(
-			array( # expected
-				'rc_bot' => 0,
-				#0 => "rc_timestamp >= '20110223000000'",
-				1 => "rc_namespace = '0'",
-			),
-			array(
-				'namespace' => NS_MAIN,
-			),
-			"rc conditions with no options (aka default setting)"
-		);
-	}
-
-	public function testRcNsFilterInversion() {
-		$this->assertConditions(
-			array( # expected
-				#0 => "rc_timestamp >= '20110223000000'",
-				'rc_bot' => 0,
-				1 => sprintf( "rc_namespace != '%s'", NS_MAIN ),
-			),
-			array(
-				'namespace' => NS_MAIN,
-				'invert' => 1,
-			),
-		  "rc conditions with namespace inverted"
-		);
-	}
-
-	/**
-	 * @bug 2429
-	 * @dataProvider provideNamespacesAssociations
-	 */
-	public function testRcNsFilterAssociation( $ns1, $ns2 ) {
-		$this->assertConditions(
-			array( # expected
-				#0 => "rc_timestamp >= '20110223000000'",
-				'rc_bot' => 0,
-				1 => sprintf( "(rc_namespace = '%s' OR rc_namespace = '%s')", $ns1, $ns2 ),
-			),
-			array(
-				'namespace' => $ns1,
-				'associated' => 1,
-			),
-		  "rc conditions with namespace inverted"
-		);
-	}
-
-	/**
-	 * @bug 2429
-	 * @dataProvider provideNamespacesAssociations
-	 */
-	public function testRcNsFilterAssociationWithInversion( $ns1, $ns2 ) {
-		$this->assertConditions(
-			array( # expected
-				#0 => "rc_timestamp >= '20110223000000'",
-				'rc_bot' => 0,
-				1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ),
-			),
-			array(
-				'namespace'  => $ns1,
-				'associated' => 1,
-				'invert'     => 1,
-			),
-		  "rc conditions with namespace inverted"
-		);
-	}
-
-	/**
-	 * Provides associated namespaces to test recent changes
-	 * namespaces association filtering.
-	 */
-	public function provideNamespacesAssociations() {
-		return array( # (NS => Associated_NS)
-			array( NS_MAIN, NS_TALK),
-			array( NS_TALK, NS_MAIN),
-		);
-	}
-
-}
-
-
diff --git a/tests/phpunit/includes/specials/SpecialRecentchangesTest.php b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
new file mode 100644
index 00000000..2e4f4b09
--- /dev/null
+++ b/tests/phpunit/includes/specials/SpecialRecentchangesTest.php
@@ -0,0 +1,132 @@
+<?php
+/**
+ * Test class for SpecialRecentchanges class
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+class SpecialRecentchangesTest extends MediaWikiTestCase {
+
+	/**
+	 * @var SpecialRecentChanges
+	 */
+	protected $rc;
+
+	function setUp() {
+	}
+
+	/** helper to test SpecialRecentchanges::buildMainQueryConds() */
+	private function assertConditions( $expected, $requestOptions = null, $message = '' ) {
+		$context = new RequestContext;
+		$context->setRequest( new FauxRequest( $requestOptions ) );
+
+		# setup the rc object
+		$this->rc = new SpecialRecentChanges();
+		$this->rc->setContext( $context );
+		$formOptions = $this->rc->setup( null );
+
+		# Filter out rc_timestamp conditions which depends on the test runtime
+		# This condition is not needed as of march 2, 2011 -- hashar
+		# @todo FIXME: Find a way to generate the correct rc_timestamp
+		$queryConditions = array_filter(
+			$this->rc->buildMainQueryConds( $formOptions ),
+			'SpecialRecentchangesTest::filterOutRcTimestampCondition'
+		);
+
+		$this->assertEquals(
+			$expected,
+			$queryConditions,
+			$message
+		);
+	}
+
+	/** return false if condition begin with 'rc_timestamp ' */
+	private static function filterOutRcTimestampCondition( $var ) {
+		return (false === strpos( $var, 'rc_timestamp ' ));
+
+	}
+
+	public function testRcNsFilter() {
+		$this->assertConditions(
+			array( # expected
+				'rc_bot' => 0,
+				#0 => "rc_timestamp >= '20110223000000'",
+				1 => "rc_namespace = '0'",
+			),
+			array(
+				'namespace' => NS_MAIN,
+			),
+			"rc conditions with no options (aka default setting)"
+		);
+	}
+
+	public function testRcNsFilterInversion() {
+		$this->assertConditions(
+			array( # expected
+				#0 => "rc_timestamp >= '20110223000000'",
+				'rc_bot' => 0,
+				1 => sprintf( "rc_namespace != '%s'", NS_MAIN ),
+			),
+			array(
+				'namespace' => NS_MAIN,
+				'invert' => 1,
+			),
+		  "rc conditions with namespace inverted"
+		);
+	}
+
+	/**
+	 * @bug 2429
+	 * @dataProvider provideNamespacesAssociations
+	 */
+	public function testRcNsFilterAssociation( $ns1, $ns2 ) {
+		$this->assertConditions(
+			array( # expected
+				#0 => "rc_timestamp >= '20110223000000'",
+				'rc_bot' => 0,
+				1 => sprintf( "(rc_namespace = '%s' OR rc_namespace = '%s')", $ns1, $ns2 ),
+			),
+			array(
+				'namespace' => $ns1,
+				'associated' => 1,
+			),
+		  "rc conditions with namespace inverted"
+		);
+	}
+
+	/**
+	 * @bug 2429
+	 * @dataProvider provideNamespacesAssociations
+	 */
+	public function testRcNsFilterAssociationWithInversion( $ns1, $ns2 ) {
+		$this->assertConditions(
+			array( # expected
+				#0 => "rc_timestamp >= '20110223000000'",
+				'rc_bot' => 0,
+				1 => sprintf( "(rc_namespace != '%s' AND rc_namespace != '%s')", $ns1, $ns2 ),
+			),
+			array(
+				'namespace'  => $ns1,
+				'associated' => 1,
+				'invert'     => 1,
+			),
+		  "rc conditions with namespace inverted"
+		);
+	}
+
+	/**
+	 * Provides associated namespaces to test recent changes
+	 * namespaces association filtering.
+	 */
+	public function provideNamespacesAssociations() {
+		return array( # (NS => Associated_NS)
+			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
new file mode 100644
index 00000000..ea9d5533
--- /dev/null
+++ b/tests/phpunit/includes/specials/SpecialSearchTest.php
@@ -0,0 +1,108 @@
+<?php
+/**
+ * Test class for SpecialSearch class
+ * Copyright © 2012, Antoine Musso
+ *
+ * @author Antoine Musso
+ * @group Database
+ */
+
+class SpecialSearchTest extends MediaWikiTestCase {
+	private $search;
+
+	function setUp() { }
+	function tearDown() { }
+
+	/**
+	 * @covers SpecialSearch::load
+	 * @dataProvider provideSearchOptionsTests
+	 * @param $requested Array Request parameters. For example array( 'ns5' => true, 'ns6' => true). NULL to use default options.
+	 * @param $userOptions Array User options to test with. For example array('searchNs5' => 1 );. NULL to use default options.
+	 * @param $expectedProfile An expected search profile name
+	 * @param $expectedNs Array Expected namespaces
+	 */
+	function testProfileAndNamespaceLoading(
+		$requested, $userOptions, $expectedProfile, $expectedNS,
+		$message = 'Profile name and namespaces mismatches!'
+	) {
+		$context = new RequestContext;
+		$context->setUser(
+			$this->newUserWithSearchNS( $userOptions )
+		);
+		/*
+		$context->setRequest( new FauxRequest( array(
+			'ns5'=>true,
+			'ns6'=>true,
+		) ));
+		 */
+		$context->setRequest( new FauxRequest( $requested ));
+		$search = new SpecialSearch();
+		$search->setContext( $context );
+		$search->load();
+
+		/**
+		 * Verify profile name and namespace in the same assertion to make
+		 * sure we will be able to fully compare the above code. PHPUnit stop
+		 * after an assertion fail.
+		 */
+		$this->assertEquals(
+			array( /** Expected: */
+				'ProfileName' => $expectedProfile,
+				'Namespaces'  => $expectedNS,
+			)
+			, array( /** Actual: */
+				'ProfileName' => $search->getProfile(),
+				'Namespaces'  => $search->getNamespaces(),
+			)
+			, $message
+		);
+
+	}
+
+	function provideSearchOptionsTests() {
+		$defaultNS = SearchEngine::defaultNamespaces();
+		$EMPTY_REQUEST = array();
+		$NO_USER_PREF  = null;
+
+		return array(
+			/**
+			 * Parameters:
+			 * 	<Web Request>, <User options>
+			 * Followed by expected values:
+			 * 	<ProfileName>, <NSList>
+			 * Then an optional message.
+			 */
+			array(
+				$EMPTY_REQUEST, $NO_USER_PREF,
+				'default', $defaultNS,
+				'Bug 33270: No request nor user preferences should give default profile'
+			),
+			array(
+				array( 'ns5' => 1 ), $NO_USER_PREF,
+				'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'
+			),
+		);
+	}
+
+	/**
+	 * Helper to create a new User object with given options
+	 * User remains anonymous though
+	 */
+	function newUserWithSearchNS( $opt = null ) {
+		$u = User::newFromId(0);
+		if( $opt === null ) {
+			return $u;
+		}
+		foreach($opt as $name => $value) {
+			$u->setOption( $name, $value );
+		}
+		return $u;
+	}
+}
+
diff --git a/tests/phpunit/includes/upload/UploadFromUrlTest.php b/tests/phpunit/includes/upload/UploadFromUrlTest.php
index 4722d408..d56cce31 100644
--- a/tests/phpunit/includes/upload/UploadFromUrlTest.php
+++ b/tests/phpunit/includes/upload/UploadFromUrlTest.php
@@ -20,7 +20,7 @@ class UploadFromUrlTest extends ApiTestCase {
 		}
 	}
 
-	protected function doApiRequest( $params, $unused = null, $appendModule = false ) {
+	protected function doApiRequest( $params, $unused = null, $appendModule = false, $user = null ) {
 		$sessionId = session_id();
 		session_write_close();
 
@@ -36,7 +36,10 @@ class UploadFromUrlTest extends ApiTestCase {
 	 * Ensure that the job queue is empty before continuing
 	 */
 	public function testClearQueue() {
-		while ( $job = Job::pop() ) { }
+		$job = Job::pop();
+		while ( $job ) {
+			$job = Job::pop();
+		}
 		$this->assertFalse( $job );
 	}
 
@@ -73,7 +76,7 @@ class UploadFromUrlTest extends ApiTestCase {
 	 * @depends testClearQueue
 	 */
 	public function testSetupUrlDownload( $data ) {
-		$token = $this->user->editToken();
+		$token = $this->user->getEditToken();
 		$exception = false;
 
 		try {
@@ -147,7 +150,7 @@ class UploadFromUrlTest extends ApiTestCase {
 	 * @depends testClearQueue
 	 */
 	public function testAsyncUpload( $data ) {
-		$token = $this->user->editToken();
+		$token = $this->user->getEditToken();
 
 		$this->user->addGroup( 'users' );
 
@@ -166,7 +169,7 @@ class UploadFromUrlTest extends ApiTestCase {
 	 * @depends testClearQueue
 	 */
 	public function testAsyncUploadWarning( $data ) {
-		$token = $this->user->editToken();
+		$token = $this->user->getEditToken();
 
 		$this->user->addGroup( 'users' );
 
@@ -197,7 +200,7 @@ class UploadFromUrlTest extends ApiTestCase {
 	 * @depends testClearQueue
 	 */
 	public function testSyncDownload( $data ) {
-		$token = $this->user->editToken();
+		$token = $this->user->getEditToken();
 
 		$job = Job::pop();
 		$this->assertFalse( $job, 'Starting with an empty jobqueue' );
@@ -221,7 +224,7 @@ class UploadFromUrlTest extends ApiTestCase {
 	}
 
 	public function testLeaveMessage() {
-		$token = $this->user->user->editToken();
+		$token = $this->user->user->getEditToken();
 
 		$talk = $this->user->user->getTalkPage();
 		if ( $talk->exists() ) {
@@ -274,7 +277,7 @@ class UploadFromUrlTest extends ApiTestCase {
 
 		return;
 
-		/**
+		/*
 		// Broken until using leavemessage with ignorewarnings is supported
 		$job->run();
 
diff --git a/tests/phpunit/includes/upload/UploadStashTest.php b/tests/phpunit/includes/upload/UploadStashTest.php
index e644a259..c9dbb138 100644
--- a/tests/phpunit/includes/upload/UploadStashTest.php
+++ b/tests/phpunit/includes/upload/UploadStashTest.php
@@ -1,5 +1,7 @@
 <?php
-
+/**
+ * @group Database
+ */
 class UploadStashTest extends MediaWikiTestCase {
 	/**
 	 * @var Array of UploadStashTestUser
@@ -8,49 +10,68 @@ class UploadStashTest extends MediaWikiTestCase {
 
 	public function setUp() {
 		parent::setUp();
-		
+
 		// Setup a file for bug 29408
 		$this->bug29408File = dirname( __FILE__ ) . '/bug29408';
-		file_put_contents( $this->bug29408File, "\x00" );		
-		
+		file_put_contents( $this->bug29408File, "\x00" );
+
 		self::$users = array(
 			'sysop' => new ApiTestUser(
 				'Uploadstashtestsysop',
 				'Upload Stash Test Sysop',
-				'upload_stash_test_sysop@sample.com',
+				'upload_stash_test_sysop@example.com',
 				array( 'sysop' )
 			),
 			'uploader' => new ApiTestUser(
 				'Uploadstashtestuser',
 				'Upload Stash Test User',
-				'upload_stash_test_user@sample.com',
+				'upload_stash_test_user@example.com',
 				array()
 			)
 		);
 	}
 
-	/**
-	 * @group Database
-	 */
 	public function testBug29408() {
 		global $wgUser;
 		$wgUser = self::$users['uploader']->user;
-		
+
 		$repo = RepoGroup::singleton()->getLocalRepo();
 		$stash = new UploadStash( $repo );
-		
+
 		// Throws exception caught by PHPUnit on failure
 		$file = $stash->stashFile( $this->bug29408File );
 		// We'll never reach this point if we hit bug 29408
 		$this->assertTrue( true, 'Unrecognized file without extension' );
-		
+
 		$stash->removeFile( $file->getFileKey() );
 	}
-	
+
+	public function testValidRequest() {
+		$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( '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', 'wpSessionKey' => 'foo') );
+		$this->assertTrue( UploadFromStash::isValidRequest($request), 'Check key precedence' );
+	}
+
 	public function tearDown() {
 		parent::tearDown();
-		
-		unlink( $this->bug29408File . "." );
-		
+
+		if( file_exists( $this->bug29408File . "." ) ) {
+			unlink( $this->bug29408File . "." );
+		}
+
+		if( file_exists( $this->bug29408File ) ) {
+			unlink( $this->bug29408File );
+		}
 	}
 }
diff --git a/tests/phpunit/includes/upload/UploadTest.php b/tests/phpunit/includes/upload/UploadTest.php
index 69c29032..4293d23b 100644
--- a/tests/phpunit/includes/upload/UploadTest.php
+++ b/tests/phpunit/includes/upload/UploadTest.php
@@ -20,53 +20,14 @@ class UploadTest extends MediaWikiTestCase {
 		$wgHooks = $this->hooks;
 	}
 
-	/**
-	 * Test various forms of valid and invalid titles that can be supplied.
-	 */
-	public function testTitleValidation() {
-
-
-		/* Test a valid title */
-		$this->assertUploadTitleAndCode( 'ValidTitle.jpg',
-			'ValidTitle.jpg', UploadBase::OK,
-			'upload valid title' );
-
-		/* A title with a slash */
-		$this->assertUploadTitleAndCode( 'A/B.jpg',
-			'B.jpg', UploadBase::OK,
-			'upload title with slash' );
-
-		/* A title with illegal char */
-		$this->assertUploadTitleAndCode( 'A:B.jpg',
-			'A-B.jpg', UploadBase::OK,
-			'upload title with colon' );
-
-		/* Stripping leading File: prefix */
-		$this->assertUploadTitleAndCode( 'File:C.jpg',
-			'C.jpg', UploadBase::OK,
-			'upload title with File prefix' );
 
-		/* Test illegal suggested title (r94601) */
-		$this->assertUploadTitleAndCode( '%281%29.JPG',
-			null, UploadBase::ILLEGAL_FILENAME,
-			'illegal title for upload' );
-
-		/* A title without extension */
-		$this->assertUploadTitleAndCode( 'A',
-			null, UploadBase::FILETYPE_MISSING,
-			'upload title without extension' );
-
-		/* A title with no basename */
-		$this->assertUploadTitleAndCode( '.jpg',
-			null, UploadBase::MIN_LENGTH_PARTNAME,
-			'upload title without basename' );
-
-	}
 	/**
-	 * Helper function for testTitleValidation. First checks the return code
-	 * of UploadBase::getTitle() and then the actual returned titl
+	 * First checks the return code
+	 * of UploadBase::getTitle() and then the actual returned title
+	 * 
+	 * @dataProvider dataTestTitleValidation
 	 */
-	private function assertUploadTitleAndCode( $srcFilename, $dstFilename, $code, $msg ) {
+	public function testTitleValidation( $srcFilename, $dstFilename, $code, $msg ) {
 		/* Check the result code */
 		$this->assertEquals( $code,
 			$this->upload->testTitleValidation( $srcFilename ),
@@ -79,6 +40,41 @@ class UploadTest extends MediaWikiTestCase {
 				"$msg text" );
 		}
 	}
+	
+	/**
+	 * Test various forms of valid and invalid titles that can be supplied.
+	 */
+	public function dataTestTitleValidation() {
+		return array(
+			/* Test a valid title */
+			array( 'ValidTitle.jpg', 'ValidTitle.jpg', UploadBase::OK, 
+				'upload valid title' ),
+			/* A title with a slash */
+			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, 
+				'upload title with colon' ),
+			/* Stripping leading File: prefix */
+			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, 
+				'illegal title for upload' ),
+			/* A title without extension */
+			array( 'A', null, UploadBase::FILETYPE_MISSING, 
+				'upload title without extension' ),
+			/* A title with no basename */
+			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, 
+				'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, 
+				'upload title longer than 240 bytes' ),
+		);
+	}
 
 	/**
 	 * Test the upload verification functions
@@ -104,7 +100,7 @@ class UploadTest extends MediaWikiTestCase {
 	}
 
 	/**
-	 * test uploading a 100 bytes file with wgMaxUploadSize = 100
+	 * test uploading a 100 bytes file with $wgMaxUploadSize = 100
 	 *
 	 * This method should be abstracted so we can test different settings.
 	 */
@@ -134,6 +130,7 @@ class UploadTestHandler extends UploadBase {
 		public function testTitleValidation( $name ) {
 			$this->mTitle = false;
 			$this->mDesiredDestName = $name;
+			$this->mTitleError = UploadBase::OK;
 			$this->getTitle();
 			return $this->mTitleError;
 		}
diff --git a/tests/phpunit/languages/LanguageAmTest.php b/tests/phpunit/languages/LanguageAmTest.php
new file mode 100644
index 00000000..3a648ded
--- /dev/null
+++ b/tests/phpunit/languages/LanguageAmTest.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageAm.php */
+class LanguageAmTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'Am' );
+	}
+	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/LanguageArTest.php b/tests/phpunit/languages/LanguageArTest.php
new file mode 100644
index 00000000..b23e0534
--- /dev/null
+++ b/tests/phpunit/languages/LanguageArTest.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Based on LanguagMlTest
+ * @file
+ */
+
+/** 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 ) );
+	}
+
+	/**
+	 * Mostly to test the raw ascii feature.
+	 * @dataProvider providerSprintfDate
+	 */
+	function testSprintfDate( $format, $date, $expected ) {
+		$this->assertEquals( $expected, $this->lang->sprintfDate( $format, $date ) );
+	}
+
+	function providerSprintfDate() {
+		return array(
+			array(
+				'xg "vs" g',
+				'20120102030410',
+				'يناير vs ٣'
+			),
+			array(
+				'xmY',
+				'20120102030410',
+				'١٤٣٣'
+			),
+			array(
+				'xnxmY',
+				'20120102030410',
+				'1433'
+			),
+			array(
+				'xN xmj xmn xN xmY',
+				'20120102030410',
+				' 7 2  ١٤٣٣'
+			),
+		);
+	}
+	/** @dataProvider providePlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'zero', 'one', 'two', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+	function providePlural() {
+		return array (
+			array( 'zero', 0 ),
+			array( 'one', 1 ),
+			array( 'two', 2 ),
+			array( 'few', 3 ),
+			array( 'few', 9 ),
+			array( 'few', 110 ),
+			array( 'many', 11 ),
+			array( 'many', 15 ),
+			array( 'many', 99 ),
+			array( 'many', 9999 ),
+			array( 'other', 100 ),
+			array( 'other', 102 ),
+			array( 'other', 1000 ),
+			array( 'other', 1.7 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageBeTest.php b/tests/phpunit/languages/LanguageBeTest.php
new file mode 100644
index 00000000..735ccc63
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBeTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageBe.php */
+class LanguageBeTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'Be' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @dataProvider providePlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	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( 'many', 5 ),
+			array( 'many', 15 ),
+			array( 'many', 120 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageBe_taraskTest.php b/tests/phpunit/languages/LanguageBe_taraskTest.php
index e7fdb7ca..765cdb8f 100644
--- a/tests/phpunit/languages/LanguageBe_taraskTest.php
+++ b/tests/phpunit/languages/LanguageBe_taraskTest.php
@@ -27,4 +27,39 @@ class LanguageBeTaraskTest extends MediaWikiTestCase {
 	function testDoesNotCommafyFourDigitsNumber() {
 		$this->assertEquals(      '1234', $this->lang->commafy(    '1234' ) );
 	}
+	/** @dataProvider providePluralFourForms */
+	function testPluralFourForms( $result, $value ) {
+		$forms =  array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralFourForms() {
+		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( '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 ) );
+	}
+	function providePluralTwoForms() {
+		return array (
+			array( 'one', 1 ),
+			array( 'several', 11 ),
+			array( 'several', 91 ),
+			array( 'several', 121 ),
+		);
+	}
+
 }
diff --git a/tests/phpunit/languages/LanguageBhTest.php b/tests/phpunit/languages/LanguageBhTest.php
new file mode 100644
index 00000000..e1e2a13e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBhTest.php
@@ -0,0 +1,34 @@
+<?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/LanguageBsTest.php b/tests/phpunit/languages/LanguageBsTest.php
new file mode 100644
index 00000000..b6631c03
--- /dev/null
+++ b/tests/phpunit/languages/LanguageBsTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+	}
+
+	/** @dataProvider providePlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePlural() {
+		return array (
+			array( 'many', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'few', 4 ),
+			array( 'many', 5 ),
+			array( 'many', 11 ),
+			array( 'many', 20 ),
+			array( 'one', 21 ),
+			array( 'few', 24 ),
+			array( 'many', 25 ),
+			array( 'many', 200 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageCsTest.php b/tests/phpunit/languages/LanguageCsTest.php
new file mode 100644
index 00000000..dda29f9a
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCsTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'few', 3 ),
+			array( 'few', 4 ),
+			array( 'other', 5 ),
+			array( 'other', 11 ),
+			array( 'other', 20 ),
+			array( 'other', 25 ),
+			array( 'other', 200 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageCuTest.php b/tests/phpunit/languages/LanguageCuTest.php
new file mode 100644
index 00000000..f8186d7b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCuTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageCu.php */
+class LanguageCuTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'cu' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'many', 3 ),
+			array( 'many', 4 ),
+			array( 'other', 5 ),
+			array( 'one', 11 ),
+			array( 'other', 20 ),
+			array( 'few', 22 ),
+			array( 'many', 223 ),
+			array( 'other', 200 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageCyTest.php b/tests/phpunit/languages/LanguageCyTest.php
new file mode 100644
index 00000000..e9f9e410
--- /dev/null
+++ b/tests/phpunit/languages/LanguageCyTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'zero', 'one', 'two', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'zero', 0 ),
+			array( 'one', 1 ),
+			array( 'two', 2 ),
+			array( 'few', 3 ),
+			array( 'many', 6 ),
+			array( 'other', 4 ),
+			array( 'other', 5 ),
+			array( 'other', 11 ),
+			array( 'other', 20 ),
+			array( 'other', 22 ),
+			array( 'other', 223 ),
+			array( 'other', 200.00 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageDsbTest.php b/tests/phpunit/languages/LanguageDsbTest.php
new file mode 100644
index 00000000..ab7f9313
--- /dev/null
+++ b/tests/phpunit/languages/LanguageDsbTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providePlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'two', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'one', 101 ),
+			array( 'one', 90001 ),
+			array( 'two', 2 ),
+			array( 'few', 3 ),
+			array( 'few', 203 ),
+			array( 'few', 4 ),
+			array( 'other', 99 ),
+			array( 'other', 555 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageFrTest.php b/tests/phpunit/languages/LanguageFrTest.php
new file mode 100644
index 00000000..8538744e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageFrTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @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/LanguageGaTest.php b/tests/phpunit/languages/LanguageGaTest.php
new file mode 100644
index 00000000..fbd9f11d
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGaTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'two', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		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
new file mode 100644
index 00000000..24574bda
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGdTest.php
@@ -0,0 +1,38 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageGd.php */
+class LanguageGdTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'gd' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @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 ) );
+	}
+	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 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageGvTest.php b/tests/phpunit/languages/LanguageGvTest.php
new file mode 100644
index 00000000..3d298b9b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageGvTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, 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 );
+	}
+
+	/** @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 ) );
+	}
+	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 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageHeTest.php b/tests/phpunit/languages/LanguageHeTest.php
new file mode 100644
index 00000000..9ac0f952
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHeTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageHe.php */
+class LanguageHeTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'he' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @dataProvider providerPluralDual */
+	function testPluralDual( $result, $value ) {
+		$forms = array( 'one', 'many', 'two' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPluralDual() {
+		return array (
+			array( 'many', 0 ), // Zero -> plural
+			array( 'one', 1 ), // Singular
+			array( 'two', 2 ), // Dual
+			array( 'many', 3 ), // Plural
+		);
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'many', 0 ), // Zero -> plural
+			array( 'one', 1 ), // Singular
+			array( 'many', 2 ), // Plural, no dual provided
+			array( 'many', 3 ), // Plural
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageHiTest.php b/tests/phpunit/languages/LanguageHiTest.php
new file mode 100644
index 00000000..ead9e020
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHiTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageHi.php */
+class LanguageHiTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'Hi' );
+	}
+	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/LanguageHrTest.php b/tests/phpunit/languages/LanguageHrTest.php
new file mode 100644
index 00000000..4f1c66bf
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHrTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'many', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'few', 4 ),
+			array( 'many', 5 ),
+			array( 'many', 11 ),
+			array( 'many', 20 ),
+			array( 'one', 21 ),
+			array( 'few', 24 ),
+			array( 'many', 25 ),
+			array( 'many', 200 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageHsbTest.php b/tests/phpunit/languages/LanguageHsbTest.php
new file mode 100644
index 00000000..803c7721
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHsbTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providePlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'two', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'one', 101 ),
+			array( 'one', 90001 ),
+			array( 'two', 2 ),
+			array( 'few', 3 ),
+			array( 'few', 203 ),
+			array( 'few', 4 ),
+			array( 'other', 99 ),
+			array( 'other', 555 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageHyTest.php b/tests/phpunit/languages/LanguageHyTest.php
new file mode 100644
index 00000000..7990bdfc
--- /dev/null
+++ b/tests/phpunit/languages/LanguageHyTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		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
new file mode 100644
index 00000000..ab889464
--- /dev/null
+++ b/tests/phpunit/languages/LanguageKshTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array(  'one', 'other', 'zero' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		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
new file mode 100644
index 00000000..0fd9167e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLnTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @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/LanguageLtTest.php b/tests/phpunit/languages/LanguageLtTest.php
new file mode 100644
index 00000000..0d7c7d3e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLtTest.php
@@ -0,0 +1,53 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageLt.php */
+class LanguageLtTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'Lt' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @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 ) );
+	}
+
+	function provideOneFewOtherCases() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'few', 9 ),
+			array( 'other', 10 ),
+			array( 'other', 11 ),
+			array( 'other', 20 ),
+			array( 'one', 21 ),
+			array( 'few', 32 ),
+			array( 'one', 41 ),
+			array( 'one', 40001 ),
+		);
+	}
+	
+	function provideOneFewCases() {
+		return array (
+			array( 'one', 1 ),
+			array( 'few', 15 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageLvTest.php b/tests/phpunit/languages/LanguageLvTest.php
new file mode 100644
index 00000000..0636da5f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageLvTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ), #this must be zero form as per CLDR
+			array( 'one', 1 ),
+			array( 'other', 11 ),
+			array( 'one', 21 ),
+			array( 'other', 411 ),
+			array( 'other', 12.345 ),
+			array( 'other', 20 ),
+			array( 'one', 31 ),
+			array( 'other', 200 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageMgTest.php b/tests/phpunit/languages/LanguageMgTest.php
new file mode 100644
index 00000000..06b56547
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMgTest.php
@@ -0,0 +1,35 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @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 ),
+			array( 'other', 123.3434 ),
+		);
+	}
+
+}
diff --git a/tests/phpunit/languages/LanguageMkTest.php b/tests/phpunit/languages/LanguageMkTest.php
new file mode 100644
index 00000000..cf5ec3d9
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMkTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'other', 11 ),
+			array( 'one', 21 ),
+			array( 'other', 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
new file mode 100644
index 00000000..8c4b0b2f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMlTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2011, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageMl.php */
+class LanguageMlTest extends MediaWikiTestCase {
+	private $lang;
+
+	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 ) );
+	}
+
+	function providerFormatNum() {
+		return array(
+			array( '12,34,567', '1234567'  ),
+			array( '12,345', '12345' ),
+			array( '1', '1' ),
+			array( '123', '123' ) ,
+			array( '1,234', '1234' ),
+			array( '12,345.56', '12345.56' ),
+			array( '12,34,56,79,81,23,45,678', '12345679812345678' ),
+			array( '.12345', '.12345' ),
+			array( '-12,00,000', '-1200000' ),
+			array( '-98', '-98' ),
+			array( '-98', -98 ),
+			array( '-1,23,45,678',  -12345678 ),
+			array( '', '' ),
+			array( '', null ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageMoTest.php b/tests/phpunit/languages/LanguageMoTest.php
new file mode 100644
index 00000000..533e590f
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMoTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Santhosh Thottingal
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		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( 'other', 120 ),
+			array( 'other', 200 ),
+			array( 'few',   201 ),
+			array( 'few',   219 ),
+			array( 'other', 220 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageMtTest.php b/tests/phpunit/languages/LanguageMtTest.php
new file mode 100644
index 00000000..421bb388
--- /dev/null
+++ b/tests/phpunit/languages/LanguageMtTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPluralAllForms */
+	function testPluralAllForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPluralAllForms() {
+		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( 'other', 120 ),
+			array( 'other', 201 ),
+		);
+	}
+
+	/** @dataProvider providerPluralTwoForms */
+	function testPluralTwoForms( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->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 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageNlTest.php b/tests/phpunit/languages/LanguageNlTest.php
new file mode 100644
index 00000000..cf979cd2
--- /dev/null
+++ b/tests/phpunit/languages/LanguageNlTest.php
@@ -0,0 +1,28 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2011, Santhosh Thottingal
+ * @file
+ */
+
+/** Tests for MediaWiki languages/LanguageNl.php */
+class LanguageNlTest extends MediaWikiTestCase {
+	private $lang;
+
+	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' ) );
+	}
+}
diff --git a/tests/phpunit/languages/LanguageNsoTest.php b/tests/phpunit/languages/LanguageNsoTest.php
new file mode 100644
index 00000000..ea393628
--- /dev/null
+++ b/tests/phpunit/languages/LanguageNsoTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'one',  0 ),
+			array( 'one',  1 ),
+			array( 'many', 2 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguagePlTest.php b/tests/phpunit/languages/LanguagePlTest.php
new file mode 100644
index 00000000..e56d4b77
--- /dev/null
+++ b/tests/phpunit/languages/LanguagePlTest.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPluralFourForms */
+	function testPluralFourForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	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 ),
+		);
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->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 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageRoTest.php b/tests/phpunit/languages/LanguageRoTest.php
new file mode 100644
index 00000000..5270f6fe
--- /dev/null
+++ b/tests/phpunit/languages/LanguageRoTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms =  array( 'one', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		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( 'other', 120 ),
+			array( 'other', 200 ),
+			array( 'few',   201 ),
+			array( 'few',   219 ),
+			array( 'other', 220 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageRuTest.php b/tests/phpunit/languages/LanguageRuTest.php
new file mode 100644
index 00000000..7a1f193b
--- /dev/null
+++ b/tests/phpunit/languages/LanguageRuTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * based on LanguageBe_tarask.php
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageRu.php */
+class LanguageRuTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'ru' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @dataProvider providePluralFourForms */
+	function testPluralFourForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralFourForms() {
+		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( '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 ) );
+	}
+	function providePluralTwoForms() {
+		return array (
+			array( 'one', 1 ),
+			array( 'several', 11 ),
+			array( 'several', 91 ),
+			array( 'several', 121 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSeTest.php b/tests/phpunit/languages/LanguageSeTest.php
new file mode 100644
index 00000000..065ec29e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSeTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPluralThreeForms */
+	function testPluralThreeForms( $result, $value ) {
+		$forms = array( 'one', 'two', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPluralThreeForms() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one',   1 ),
+			array( 'two',   2 ),
+			array( 'other', 3 ),
+		);
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one',   1 ),
+			array( 'other', 2 ),
+			array( 'other', 3 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSgsTest.php b/tests/phpunit/languages/LanguageSgsTest.php
new file mode 100644
index 00000000..931c82f0
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSgsTest.php
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @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 );
+	}
+
+	/** @dataProvider providePluralAllForms */
+	function testPluralAllForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralAllForms() {
+		return array (
+			array( 'many',  0 ),
+			array( 'one',   1 ),
+			array( 'few',   2 ),
+			array( 'other', 3 ),
+			array( 'many',  10 ),
+			array( 'many',  11 ),
+			array( 'many',  12 ),
+			array( 'many',  19 ),
+			array( 'other', 20 ),
+			array( 'many',  100 ),
+			array( 'one',   101 ),
+			array( 'many',  111 ),
+			array( 'many',  112 ),
+		);
+	}
+
+	/** @dataProvider providePluralTwoForms */
+	function testPluralTwoForms( $result, $value ) {
+		$forms =  array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralTwoForms() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one',   1 ),
+			array( 'other', 2 ),
+			array( 'other', 3 ),
+			array( 'other', 10 ),
+			array( 'other', 11 ),
+			array( 'other', 12 ),
+			array( 'other', 19 ),
+			array( 'other', 20 ),
+			array( 'other', 100 ),
+			array( 'one',   101 ),
+			array( 'other', 111 ),
+			array( 'other', 112 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageShTest.php b/tests/phpunit/languages/LanguageShTest.php
new file mode 100644
index 00000000..b8169aed
--- /dev/null
+++ b/tests/phpunit/languages/LanguageShTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'many', 0 ),
+			array( 'one',  1 ),
+			array( 'many', 2 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSkTest.php b/tests/phpunit/languages/LanguageSkTest.php
new file mode 100644
index 00000000..4cfd840e
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSkTest.php
@@ -0,0 +1,40 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * based on LanguageSkTest.php
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'few', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one', 1 ),
+			array( 'few', 2 ),
+			array( 'few', 3 ),
+			array( 'few', 4 ),
+			array( 'other', 5 ),
+			array( 'other', 11 ),
+			array( 'other', 20 ),
+			array( 'other', 25 ),
+			array( 'other', 200 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSlTest.php b/tests/phpunit/languages/LanguageSlTest.php
new file mode 100644
index 00000000..c1f75691
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSlTest.php
@@ -0,0 +1,42 @@
+<?php
+/**
+ * @author Santhosh Thottingal
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * based on LanguageSkTest.php
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'two', 'few', 'other', 'zero' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'zero',  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 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSmaTest.php b/tests/phpunit/languages/LanguageSmaTest.php
new file mode 100644
index 00000000..b7e72e97
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSmaTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPluralThreeForms */
+	function testPluralThreeForms( $result, $value ) {
+		$forms = array( 'one', 'two', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPluralThreeForms() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one',   1 ),
+			array( 'two',   2 ),
+			array( 'other', 3 ),
+		);
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'other', 0 ),
+			array( 'one',   1 ),
+			array( 'other', 2 ),
+			array( 'other', 3 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageSrTest.php b/tests/phpunit/languages/LanguageSrTest.php
new file mode 100644
index 00000000..a50547c6
--- /dev/null
+++ b/tests/phpunit/languages/LanguageSrTest.php
@@ -0,0 +1,199 @@
+<?php
+/**
+ * PHPUnit tests for the Serbian language.
+ * The language can be represented using two scripts:
+ *  - Latin (SR_el)
+ *  - Cyrillic (SR_ec)
+ * Both representations seems to be bijective, hence MediaWiki can convert
+ * from one script to the other.
+ *
+ * @author Antoine Musso <hashar at free dot fr>
+ * @copyright Copyright © 2011, Antoine Musso <hashar at free dot fr>
+ * @file
+ */
+
+require_once dirname( dirname( __FILE__ ) ) . '/bootstrap.php';
+
+/** Tests for MediaWiki languages/LanguageTr.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( ) {
+		$this->assertCyrillic(
+			'шђчћжШЂЧЋЖ',
+			'Cyrillic guessing characters'
+		);
+		$this->assertLatin(
+			'šđč枊ĐČĆŽ',
+			'Latin guessing characters'
+		);
+	}
+
+	function testMixedConversions() {
+		$this->assertCyrillic(
+			'шђчћжШЂЧЋЖ - šđčćž',
+			'Mostly cyrillic characters'
+		);
+		$this->assertLatin(
+			'šđč枊ĐČĆŽ - шђчћж',
+			'Mostly latin characters'
+		);
+	}
+
+	function testSameAmountOfLatinAndCyrillicGetConverted() {
+		$this->assertConverted(
+			'4 latin: šđčć | 4 cyrillic: шђчћ',
+			'sr-ec'
+		);
+		$this->assertConverted(
+			'4 latin: šđčć | 4 cyrillic: шђчћ',
+			'sr-el'
+		);
+	}
+
+	/**
+	 * @author Nikola Smolenski
+	 */
+	function testConversionToCyrillic() {
+		$this->assertEquals( 'абвг',
+			$this->convertToCyrillic( 'abvg' )
+		);
+		$this->assertEquals( 'абвг',
+			$this->convertToCyrillic( 'абвг' )
+		);
+		$this->assertEquals( 'abvgшђжчћ',
+			$this->convertToCyrillic( 'abvgшђжчћ' )
+		);
+		$this->assertEquals( 'абвгшђжчћ',
+			$this->convertToCyrillic( 'абвгšđžčć' )
+		);
+		// Roman numerals are not converted
+		$this->assertEquals( 'а I б II в III г IV шђжчћ',
+			$this->convertToCyrillic( 'a I b II v III g IV šđžčć' )
+		);
+	}
+
+	function testConversionToLatin() {
+		$this->assertEquals( 'abcd',
+			$this->convertToLatin( 'abcd' )
+		);
+		$this->assertEquals( 'abcd',
+			$this->convertToLatin( 'абцд' )
+		);
+		$this->assertEquals( 'abcdšđžčć',
+			$this->convertToLatin( 'abcdшђжчћ' )
+		);
+		$this->assertEquals( 'абцдšđžčć',
+			$this->convertToLatin( 'абцдšđžčć' )
+		);
+	}
+
+	/** @dataProvider providePluralFourForms */
+	function testPluralFourForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralFourForms() {
+		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( '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 ) );
+	}
+	function providePluralTwoForms() {
+		return array (
+			array( 'one', 1 ),
+			array( 'several', 11 ),
+			array( 'several', 91 ),
+			array( 'several', 121 ),
+		);
+	}
+
+	##### HELPERS #####################################################
+	/**
+	 *Wrapper to verify text stay the same after applying conversion
+	 * @param $text string Text to convert
+	 * @param $variant string Language variant 'sr-ec' or 'sr-el'
+	 * @param $msg string Optional message
+	 */
+	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 = '' ) {
+		$this->assertNotEquals(
+			$text,
+			$this->convertTo( $text, $variant ),
+			$msg
+		);
+	}
+
+	/**
+	 * Verifiy the given Cyrillic text is not converted when using
+	 * using the cyrillic variant and converted to Latin when using
+	 * the Latin variant.
+	 */
+	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 = '' ) {
+		$this->assertUnConverted( $text, 'sr-el', $msg );
+		$this->assertConverted( $text, 'sr-ec', $msg );
+	}
+
+
+	/** Wrapper for converter::convertTo() method*/
+	function convertTo( $text, $variant ) {
+		return $this
+			->lang
+			->mConverter
+			->convertTo(
+				$text, $variant
+			);
+	}
+	function convertToCyrillic( $text ) {
+		return $this->convertTo( $text, 'sr-ec' );
+	}
+	function convertToLatin( $text ) {
+		return $this->convertTo( $text, 'sr-el' );
+	}
+}
diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php
index aaad9c31..c83e01ea 100644
--- a/tests/phpunit/languages/LanguageTest.php
+++ b/tests/phpunit/languages/LanguageTest.php
@@ -1,6 +1,10 @@
 <?php
 
 class LanguageTest extends MediaWikiTestCase {
+
+	/**
+	 * @var Language
+	 */
 	private $lang;
 
 	function setUp() {
@@ -19,97 +23,46 @@ class LanguageTest extends MediaWikiTestCase {
 			'convertDoubleWidth() with the full alphabet and digits'
 		);
 	}
-
-	function testFormatTimePeriod() {
-		$this->assertEquals(
-			"9.5s",
-			$this->lang->formatTimePeriod( 9.45 ),
-			'formatTimePeriod() rounding (<10s)'
-		);
-
-		$this->assertEquals(
-			"10s",
-			$this->lang->formatTimePeriod( 9.95 ),
-			'formatTimePeriod() rounding (<10s)'
-		);
-
-		$this->assertEquals(
-			"1m 0s",
-			$this->lang->formatTimePeriod( 59.55 ),
-			'formatTimePeriod() rounding (<60s)'
-		);
-
-		$this->assertEquals(
-			"2m 0s",
-			$this->lang->formatTimePeriod( 119.55 ),
-			'formatTimePeriod() rounding (<1h)'
-		);
-
-		$this->assertEquals(
-			"1h 0m 0s",
-			$this->lang->formatTimePeriod( 3599.55 ),
-			'formatTimePeriod() rounding (<1h)'
-		);
-
-		$this->assertEquals(
-			"2h 0m 0s",
-			$this->lang->formatTimePeriod( 7199.55 ),
-			'formatTimePeriod() rounding (>=1h)'
-		);
-
-		$this->assertEquals(
-			"2h 0m",
-			$this->lang->formatTimePeriod( 7199.55, 'avoidseconds' ),
-			'formatTimePeriod() rounding (>=1h), avoidseconds'
-		);
-
-		$this->assertEquals(
-			"2h 0m",
-			$this->lang->formatTimePeriod( 7199.55, 'avoidminutes' ),
-			'formatTimePeriod() rounding (>=1h), avoidminutes'
-		);
-
-		$this->assertEquals(
-			"48h 0m",
-			$this->lang->formatTimePeriod( 172799.55, 'avoidseconds' ),
-			'formatTimePeriod() rounding (=48h), avoidseconds'
-		);
-
-		$this->assertEquals(
-			"3d 0h",
-			$this->lang->formatTimePeriod( 259199.55, 'avoidminutes' ),
-			'formatTimePeriod() rounding (>48h), avoidminutes'
-		);
-
-		$this->assertEquals(
-			"2d 1h 0m",
-			$this->lang->formatTimePeriod( 176399.55, 'avoidseconds' ),
-			'formatTimePeriod() rounding (>48h), avoidseconds'
-		);
-
-		$this->assertEquals(
-			"2d 1h",
-			$this->lang->formatTimePeriod( 176399.55, 'avoidminutes' ),
-			'formatTimePeriod() rounding (>48h), avoidminutes'
-		);
-
-		$this->assertEquals(
-			"3d 0h 0m",
-			$this->lang->formatTimePeriod( 259199.55, 'avoidseconds' ),
-			'formatTimePeriod() rounding (>48h), avoidminutes'
-		);
-
-		$this->assertEquals(
-			"2d 0h 0m",
-			$this->lang->formatTimePeriod( 172801.55, 'avoidseconds' ),
-			'formatTimePeriod() rounding, (>48h), avoidseconds'
-		);
-
-		$this->assertEquals(
-			"2d 1h 1m 1s",
-			$this->lang->formatTimePeriod( 176460.55 ),
-			'formatTimePeriod() rounding, recursion, (>48h)'
+	
+	/** @dataProvider provideFormattableTimes */
+	function testFormatTimePeriod( $seconds, $format, $expected, $desc ) {
+		$this->assertEquals( $expected, $this->lang->formatTimePeriod( $seconds, $format ), $desc );
+	}
+	
+	function provideFormattableTimes() {
+		return array(
+			array( 9.45, array(), '9.5s', 'formatTimePeriod() rounding (<10s)' ),
+			array( 9.45, array( 'noabbrevs' => true ), '9.5 seconds', 'formatTimePeriod() rounding (<10s)' ),
+			array( 9.95, array(), '10s', 'formatTimePeriod() rounding (<10s)' ),
+			array( 9.95, array( 'noabbrevs' => true ), '10 seconds', 'formatTimePeriod() rounding (<10s)' ),
+			array( 59.55, array(), '1m 0s', 'formatTimePeriod() rounding (<60s)' ),
+			array( 59.55, array( 'noabbrevs' => true ), '1 minute 0 seconds', 'formatTimePeriod() rounding (<60s)' ),
+			array( 119.55, array(), '2m 0s', 'formatTimePeriod() rounding (<1h)' ),
+			array( 119.55, array( 'noabbrevs' => true ), '2 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
+			array( 3599.55, array(), '1h 0m 0s', 'formatTimePeriod() rounding (<1h)' ),
+			array( 3599.55, array( 'noabbrevs' => true ), '1 hour 0 minutes 0 seconds', 'formatTimePeriod() rounding (<1h)' ),
+			array( 7199.55, array(), '2h 0m 0s', 'formatTimePeriod() rounding (>=1h)' ),
+			array( 7199.55, array( 'noabbrevs' => true ), '2 hours 0 minutes 0 seconds', 'formatTimePeriod() rounding (>=1h)' ),
+			array( 7199.55, 'avoidseconds', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
+			array( 7199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidseconds' ),
+			array( 7199.55, 'avoidminutes', '2h 0m', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
+			array( 7199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 hours 0 minutes', 'formatTimePeriod() rounding (>=1h), avoidminutes' ),
+			array( 172799.55, 'avoidseconds', '48h 0m', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
+			array( 172799.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '48 hours 0 minutes', 'formatTimePeriod() rounding (=48h), avoidseconds' ),
+			array( 259199.55, 'avoidminutes', '3d 0h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+			array( 259199.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '3 days 0 hours', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+			array( 176399.55, 'avoidseconds', '2d 1h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+			array( 176399.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 1 hour 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+			array( 176399.55, 'avoidminutes', '2d 1h', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+			array( 176399.55, array( 'avoid' => 'avoidminutes', 'noabbrevs' => true ), '2 days 1 hour', 'formatTimePeriod() rounding (>48h), avoidminutes' ),
+			array( 259199.55, 'avoidseconds', '3d 0h 0m', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+			array( 259199.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '3 days 0 hours 0 minutes', 'formatTimePeriod() rounding (>48h), avoidseconds' ),
+			array( 172801.55, 'avoidseconds', '2d 0h 0m', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
+			array( 172801.55, array( 'avoid' => 'avoidseconds', 'noabbrevs' => true ), '2 days 0 hours 0 minutes', 'formatTimePeriod() rounding, (>48h), avoidseconds' ),
+			array( 176460.55, array(), '2d 1h 1m 1s', 'formatTimePeriod() rounding, recursion, (>48h)' ),
+			array( 176460.55, array( 'noabbrevs' => true ), '2 days 1 hour 1 minute 1 second', 'formatTimePeriod() rounding, recursion, (>48h)' ),
 		);
+		
 	}
 
 	function testTruncate() {
@@ -243,4 +196,462 @@ class LanguageTest extends MediaWikiTestCase {
 			array( 'Be-x-old', 'With extension (two dashes)' ),
 		);
 	}
+
+	/**
+	 * @dataProvider provideSprintfDateSamples
+	 */
+	function testSprintfDate( $format, $ts, $expected, $msg ) {
+		$this->assertEquals(
+			$expected,
+			$this->lang->sprintfDate( $format, $ts ),
+			"sprintfDate('$format', '$ts'): $msg"
+		);
+	}
+	/**
+	 * bug 33454. sprintfDate should always use UTC.
+	 * @dataProvider provideSprintfDateSamples
+	 */
+	function testSprintfDateTZ( $format, $ts, $expected, $msg ) {
+		$oldTZ = date_default_timezone_get();
+		$res = date_default_timezone_set( 'Asia/Seoul' );
+		if ( !$res ) {
+			$this->markTestSkipped( "Error setting Timezone" );
+		}
+
+		$this->assertEquals(
+			$expected,
+			$this->lang->sprintfDate( $format, $ts ),
+			"sprintfDate('$format', '$ts'): $msg"
+		);
+
+		date_default_timezone_set( $oldTZ );		
+	}
+
+	function provideSprintfDateSamples() {
+		return array(
+			array(
+				'xiY',
+				'20111212000000',
+				'1390', // note because we're testing English locale we get Latin-standard digits
+				'Iranian calendar full year'
+			),
+			array(
+				'xiy',
+				'20111212000000',
+				'90',
+				'Iranian calendar short year'
+			),
+			array(
+				'o',
+				'20120101235000',
+				'2011',
+				'ISO 8601 (week) year'
+			),
+			array(
+				'W',
+				'20120101235000',
+				'52',
+				'Week number'
+			),
+			array(
+				'W',
+				'20120102235000',
+				'1',
+				'Week number'
+			),
+			array(
+				'o-\\WW-N',
+				'20091231235000',
+				'2009-W53-4',
+				'leap week'
+			),
+			// What follows is mostly copied from http://www.mediawiki.org/wiki/Help:Extension:ParserFunctions#.23time
+			array(
+				'Y',
+				'20120102090705',
+				'2012',
+				'Full year'
+			),
+			array(
+				'y',
+				'20120102090705',
+				'12',
+				'2 digit year'
+			),
+			array(
+				'L',
+				'20120102090705',
+				'1',
+				'Leap year'
+			),
+			array(
+				'n',
+				'20120102090705',
+				'1',
+				'Month index, not zero pad'
+			),
+			array(
+				'N',
+				'20120102090705',
+				'01',
+				'Month index. Zero pad'
+			),
+			array(
+				'M',
+				'20120102090705',
+				'Jan',
+				'Month abbrev'
+			),
+			array(
+				'F',
+				'20120102090705',
+				'January',
+				'Full month'
+			),
+			array(
+				'xg',
+				'20120102090705',
+				'January',
+				'Genitive month name (same in EN)'
+			),
+			array(
+				'j',
+				'20120102090705',
+				'2',
+				'Day of month (not zero pad)'
+			),
+			array(
+				'd',
+				'20120102090705',
+				'02',
+				'Day of month (zero-pad)'
+			),
+			array(
+				'z',
+				'20120102090705',
+				'1',
+				'Day of year (zero-indexed)'
+			),
+			array(
+				'D',
+				'20120102090705',
+				'Mon',
+				'Day of week (abbrev)'
+			),
+			array(
+				'l',
+				'20120102090705',
+				'Monday',
+				'Full day of week'
+			),
+			array(
+				'N',
+				'20120101090705',
+				'7',
+				'Day of week (Mon=1, Sun=7)'
+			),
+			array(
+				'w',
+				'20120101090705',
+				'0',
+				'Day of week (Sun=0, Sat=6)'
+			),
+			array(
+				'N',
+				'20120102090705',
+				'1',
+				'Day of week'
+			),
+			array(
+				'a',
+				'20120102090705',
+				'am',
+				'am vs pm'
+			),
+			array(
+				'A',
+				'20120102120000',
+				'PM',
+				'AM vs PM'
+			),
+			array(
+				'a',
+				'20120102000000',
+				'am',
+				'AM vs PM'
+			),
+			array(
+				'g',
+				'20120102090705',
+				'9',
+				'12 hour, not Zero'
+			),
+			array(
+				'h',
+				'20120102090705',
+				'09',
+				'12 hour, zero padded'
+			),
+			array(
+				'G',
+				'20120102090705',
+				'9',
+				'24 hour, not zero'
+			),
+			array(
+				'H',
+				'20120102090705',
+				'09',
+				'24 hour, zero'
+			),
+			array(
+				'H',
+				'20120102110705',
+				'11',
+				'24 hour, zero'
+			),
+			array(
+				'i',
+				'20120102090705',
+				'07',
+				'Minutes'
+			),
+			array(
+				's',
+				'20120102090705',
+				'05',
+				'seconds'
+			),
+			array(
+				'U',
+				'20120102090705',
+				'1325495225',
+				'unix time'
+			),
+			array(
+				't',
+				'20120102090705',
+				'31',
+				'Days in current month'
+			),
+			array(
+				'c',
+				'20120102090705',
+				'2012-01-02T09:07:05+00:00',
+				'ISO 8601 timestamp'
+			),
+			array(
+				'r',
+				'20120102090705',
+				'Mon, 02 Jan 2012 09:07:05 +0000',
+				'RFC 5322'
+			),
+			array(
+				'xmj xmF xmn xmY',
+				'20120102090705',
+				'7 Safar 2 1433',
+				'Islamic'
+			),
+			array(
+				'xij xiF xin xiY',
+				'20120102090705',
+				'12 Dey 10 1390',
+				'Iranian'
+			),
+			array(
+				'xjj xjF xjn xjY',
+				'20120102090705',
+				'7 Tevet 4 5772',
+				'Hebrew'
+			),
+			array(
+				'xjt',
+				'20120102090705',
+				'29',
+				'Hebrew number of days in month'
+			),
+			array(
+				'xjx',
+				'20120102090705',
+				'Tevet',
+				'Hebrew genitive month name (No difference in EN)'
+			),
+			array(
+				'xkY',
+				'20120102090705',
+				'2555',
+				'Thai year'
+			),
+			array(
+				'xoY',
+				'20120102090705',
+				'101',
+				'Minguo'
+			),
+			array(
+				'xtY',
+				'20120102090705',
+				'平成24',
+				'nengo'
+			),
+			array(
+				'xrxkYY',
+				'20120102090705',
+				'MMDLV2012',
+				'Roman numerals'
+			),
+			array(
+				'xhxjYY',
+				'20120102090705',
+				'ה\'תשע"ב2012',
+				'Hebrew numberals'
+			),
+			array(
+				'xnY',
+				'20120102090705',
+				'2012',
+				'Raw numerals (doesn\'t mean much in EN)'
+			),
+			array(
+				'[[Y "(yea"\\r)]] \\"xx\\"',
+				'20120102090705',
+				'[[2012 (year)]] "x"',
+				'Various escaping'
+			),
+
+		);
+	}
+
+	/**
+	 * @dataProvider provideFormatSizes
+	 */
+	function testFormatSize( $size, $expected, $msg ) {
+		$this->assertEquals(
+			$expected,
+			$this->lang->formatSize( $size ),
+			"formatSize('$size'): $msg"
+		);
+	}
+
+	function provideFormatSizes() {
+		return array(
+			array(
+				0,
+				"0 B",
+				"Zero bytes"
+			),
+			array(
+				1024,
+				"1 KB",
+				"1 kilobyte"
+			),
+			array(
+				1024 * 1024,
+				"1 MB",
+				"1,024 megabytes"
+			),
+			array(
+				1024 * 1024 * 1024,
+				"1 GB",
+				"1 gigabytes"
+			),
+			array(
+				pow( 1024, 4 ),
+				"1 TB",
+				"1 terabyte"
+			),
+			array(
+				pow( 1024, 5 ),
+				"1 PB",
+				"1 petabyte"
+			),
+			array(
+				pow( 1024, 6 ),
+				"1 EB",
+				"1,024 exabyte"
+			),
+			array(
+				pow( 1024, 7 ),
+				"1 ZB",
+				"1 zetabyte"
+			),
+			array(
+				pow( 1024, 8 ),
+				"1 YB",
+				"1 yottabyte"
+			),
+			// How big!? THIS BIG!
+		);
+	}
+
+	/**
+	 * @dataProvider provideFormatBitrate
+	 */
+	function testFormatBitrate( $bps, $expected, $msg ) {
+		$this->assertEquals(
+			$expected,
+			$this->lang->formatBitrate( $bps ),
+			"formatBitrate('$bps'): $msg"
+		);
+	}
+
+	function provideFormatBitrate() {
+		return array(
+			array(
+				0,
+				"0bps",
+				"0 bits per second"
+			),
+			array(
+				999,
+				"999bps",
+				"999 bits per second"
+			),
+			array(
+				1000,
+				"1kbps",
+				"1 kilobit per second"
+			),
+			array(
+				1000 * 1000,
+				"1Mbps",
+				"1 megabit per second"
+			),
+			array(
+				pow( 10, 9 ),
+				"1Gbps",
+				"1 gigabit per second"
+			),
+			array(
+				pow( 10, 12 ),
+				"1Tbps",
+				"1 terabit per second"
+			),
+			array(
+				pow( 10, 15 ),
+				"1Pbps",
+				"1 petabit per second"
+			),
+			array(
+				pow( 10, 18 ),
+				"1Ebps",
+				"1 exabit per second"
+			),
+			array(
+				pow( 10, 21 ),
+				"1Zbps",
+				"1 zetabit per second"
+			),
+			array(
+				pow( 10, 24 ),
+				"1Ybps",
+				"1 yottabit per second"
+			),
+			array(
+				pow( 10, 27 ),
+				"1,000Ybps",
+				"1,000 yottabits per second"
+			),
+		);
+	}
 }
diff --git a/tests/phpunit/languages/LanguageTiTest.php b/tests/phpunit/languages/LanguageTiTest.php
new file mode 100644
index 00000000..4bfaa009
--- /dev/null
+++ b/tests/phpunit/languages/LanguageTiTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'one',  0 ),
+			array( 'one',  1 ),
+			array( 'many', 2 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageTlTest.php b/tests/phpunit/languages/LanguageTlTest.php
new file mode 100644
index 00000000..a1facd14
--- /dev/null
+++ b/tests/phpunit/languages/LanguageTlTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'one',  0 ),
+			array( 'one',  1 ),
+			array( 'many', 2 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageTrTest.php b/tests/phpunit/languages/LanguageTrTest.php
index d2a5ff36..bda4c9d9 100644
--- a/tests/phpunit/languages/LanguageTrTest.php
+++ b/tests/phpunit/languages/LanguageTrTest.php
@@ -1,7 +1,7 @@
 <?php
 /**
- * @author Ashar Voultoiz
- * @copyright Copyright © 2011, Ashar Voultoiz
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
  * @file
  */
 
@@ -18,7 +18,10 @@ class LanguageTrTest extends MediaWikiTestCase {
 
 	/**
 	 * See @bug 28040
-	 * Credits to #wikipedia-tr users berm, []LuCkY[] and Emperyan
+	 * Credits to irc://irc.freenode.net/wikipedia-tr users:
+	 *  - berm
+	 *  - []LuCkY[]
+	 *  - Emperyan
 	 * @see http://en.wikipedia.org/wiki/Dotted_and_dotless_I
 	 * @dataProvider provideDottedAndDotlessI
 	 */
diff --git a/tests/phpunit/languages/LanguageUkTest.php b/tests/phpunit/languages/LanguageUkTest.php
new file mode 100644
index 00000000..60fafb0d
--- /dev/null
+++ b/tests/phpunit/languages/LanguageUkTest.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * based on LanguageBe_tarask.php
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** Tests for MediaWiki languages/classes/LanguageUk.php */
+class LanguageUkTest extends MediaWikiTestCase {
+	private $lang;
+
+	function setUp() {
+		$this->lang = Language::factory( 'Uk' );
+	}
+	function tearDown() {
+		unset( $this->lang );
+	}
+
+	/** @dataProvider providePluralFourForms */
+	function testPluralFourForms( $result, $value ) {
+		$forms = array( 'one', 'few', 'many', 'other' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providePluralFourForms() {
+		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( '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 ) );
+	}
+	function providePluralTwoForms() {
+		return array (
+			array( 'one', 1 ),
+			array( 'several', 11 ),
+			array( 'several', 91 ),
+			array( 'several', 121 ),
+		);
+	}
+}
diff --git a/tests/phpunit/languages/LanguageWaTest.php b/tests/phpunit/languages/LanguageWaTest.php
new file mode 100644
index 00000000..172f19b9
--- /dev/null
+++ b/tests/phpunit/languages/LanguageWaTest.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @author Amir E. Aharoni
+ * @copyright Copyright © 2012, Amir E. Aharoni
+ * @file
+ */
+
+/** 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 );
+	}
+
+	/** @dataProvider providerPlural */
+	function testPlural( $result, $value ) {
+		$forms = array( 'one', 'many' );
+		$this->assertEquals( $result, $this->lang->convertPlural( $value, $forms ) );
+	}
+
+	function providerPlural() {
+		return array (
+			array( 'one',  0 ),
+			array( 'one',  1 ),
+			array( 'many', 2 ),
+		);
+	}
+}
diff --git a/tests/phpunit/phpunit.php b/tests/phpunit/phpunit.php
index 39cccf80..92eeffa2 100644
--- a/tests/phpunit/phpunit.php
+++ b/tests/phpunit/phpunit.php
@@ -46,8 +46,8 @@ require( RUN_MAINTENANCE_IF_MAIN );
 
 if( !in_array( '--configuration', $_SERVER['argv'] ) ) {
 	//Hack to eliminate the need to use the Makefile (which sucks ATM)
-	$_SERVER['argv'][] = '--configuration';
-	$_SERVER['argv'][] = $IP . '/tests/phpunit/suite.xml';
+	array_splice( $_SERVER['argv'], 1, 0, 
+		array( '--configuration', $IP . '/tests/phpunit/suite.xml' ) );
 }
 
 require_once( 'PHPUnit/Runner/Version.php' );
diff --git a/tests/phpunit/skins/SideBarTest.php b/tests/phpunit/skins/SideBarTest.php
index bf79e760..912d7602 100644
--- a/tests/phpunit/skins/SideBarTest.php
+++ b/tests/phpunit/skins/SideBarTest.php
@@ -37,6 +37,7 @@ class SideBarTest extends MediaWikiLangTestCase {
 		parent::setUp();
 		$this->initMessagesHref();
 		$this->skin = new SkinTemplate();
+		$this->skin->getContext()->setLanguage( Language::factory( 'en' ) );
 	}
 	function tearDown() {
 		parent::tearDown();
@@ -106,7 +107,7 @@ class SideBarTest extends MediaWikiLangTestCase {
 
 	}
 	/**
-	 * bug 33321
+	 * bug 33321 - Make sure there's a | after transforming.
 	 * @group Database
 	 */
 	function testTrickyPipe() {
@@ -168,7 +169,7 @@ class SideBarTest extends MediaWikiLangTestCase {
 	}
 
 	/**
-	 * Test wgNoFollowLinks in sidebar
+	 * Test $wgNoFollowLinks in sidebar
 	 */
 	function testRespectWgnofollowlinks() {
 		global $wgNoFollowLinks;
@@ -177,7 +178,7 @@ class SideBarTest extends MediaWikiLangTestCase {
 
 		$attribs = $this->getAttribs();
 		$this->assertArrayNotHasKey( 'rel', $attribs,
-			'External URL in sidebar do not have rel=nofollow when wgNoFollowLinks = false'
+			'External URL in sidebar do not have rel=nofollow when $wgNoFollowLinks = false'
 		);
 
 		// Restore global
@@ -185,7 +186,7 @@ class SideBarTest extends MediaWikiLangTestCase {
 	}
 
 	/**
-	 * Test wgExternaLinkTarget in sidebar
+	 * Test $wgExternaLinkTarget in sidebar
 	 */
 	function testRespectExternallinktarget() {
 		global $wgExternalLinkTarget;
diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml
index e6649beb..1227a17a 100644
--- a/tests/phpunit/suite.xml
+++ b/tests/phpunit/suite.xml
@@ -2,35 +2,42 @@
 
 <!-- colors don't work on Windows! -->
 <phpunit bootstrap="./bootstrap.php"
-         colors="false"
+         colors="true"
          backupGlobals="false"
          convertErrorsToExceptions="true"
          convertNoticesToExceptions="true"
          convertWarningsToExceptions="true"
          stopOnFailure="false"
+		 timeoutForSmallTests="2"
+		 timeoutForMediumTests="10"
+		 timeoutForLargeTests="60"
          strict="true"
 		 verbose="true">
 	<testsuites>
 		<testsuite name="includes">
-			<directory>./includes</directory>
+			<directory>includes</directory>
 		</testsuite>
 		<testsuite name="languages">
-			<directory>./languages</directory>
+			<directory>languages</directory>
 		</testsuite>
 		<testsuite name="skins">
-			<directory>./skins</directory>
+			<directory>skins</directory>
+		</testsuite>
+		<testsuite name="structure">
+			<file>StructureTest.php</file>
 		</testsuite>
 		<testsuite name="uploadfromurl">
-			<file>./suites/UploadFromUrlTestSuite.php</file>
+			<file>suites/UploadFromUrlTestSuite.php</file>
 		</testsuite>
 		<testsuite name="extensions">
-			<file>./suites/ExtensionsTestSuite.php</file>
+			<file>suites/ExtensionsTestSuite.php</file>
 		</testsuite>
 	</testsuites>
 	<groups>
 		<exclude>
 			<group>Utility</group>
 			<group>Broken</group>
+			<group>ParserFuzz</group>
 			<group>Stub</group>
 		</exclude>
 	</groups>
diff --git a/tests/phpunit/suites/UploadFromUrlTestSuite.php b/tests/phpunit/suites/UploadFromUrlTestSuite.php
index 9b666825..6779ad47 100644
--- a/tests/phpunit/suites/UploadFromUrlTestSuite.php
+++ b/tests/phpunit/suites/UploadFromUrlTestSuite.php
@@ -3,6 +3,8 @@
 require_once( dirname( dirname( __FILE__ ) ) . '/includes/upload/UploadFromUrlTest.php' );
 
 class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
+	public $savedGlobals = array();
+
 	public static function addTables( &$tables ) {
 		$tables[] = 'user_properties';
 		$tables[] = 'filearchive';
@@ -14,34 +16,49 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
 	}
 
 	function setUp() {
-		global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList,
-				  $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache,
-				  $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo,
-				  $parserMemc, $wgThumbnailScriptPath, $wgScriptPath,
-				  $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath;
-
-		$wgScript = '/index.php';
-		$wgScriptPath = '/';
-		$wgArticlePath = '/wiki/$1';
-		$wgStyleSheetPath = '/skins';
-		$wgStylePath = '/skins';
-		$wgThumbnailScriptPath = false;
-		$wgLocalFileRepo = array(
-			'class' => 'LocalRepo',
-			'name' => 'local',
-			'directory' => wfTempDir() . '/test-repo',
-			'url' => 'http://example.com/images',
-			'deletedDir' => wfTempDir() . '/test-repo/delete',
-			'hashLevels' => 2,
+		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,
 			'transformVia404' => false,
+			'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-deleted' => wfTempDir() . '/test-repo/delete',
+				)
+			) ),
 		);
+		foreach ( $tmpGlobals as $var => $val ) {
+			if ( array_key_exists( $var, $GLOBALS ) ) {
+				$this->savedGlobals[$var] = $GLOBALS[$var];
+			}
+			$GLOBALS[$var] = $val;
+		}
+
 		$wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface';
 		$wgNamespaceAliases['Image'] = NS_FILE;
 		$wgNamespaceAliases['Image_talk'] = NS_FILE_TALK;
 
 
 		$wgEnableParserCache = false;
-		$wgDeferredUpdateList = array();
+		DeferredUpdates::clearPendingUpdates();
 		$wgMemc = wfGetMainCache();
 		$messageMemc = wfGetMessageCacheStorage();
 		$parserMemc = wfGetParserCacheStorage();
@@ -49,18 +66,27 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
 		// $wgContLang = new StubContLang;
 		$wgUser = new User;
 		$context = new RequestContext;
-		$wgLang = $context->getLang();
+		$wgLang = $context->getLanguage();
 		$wgOut = $context->getOutput();
 		$wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) );
-		$wgRequest = new WebRequest;
+		$wgRequest = $context->getRequest();
 
 		if ( $wgStyleDirectory === false ) {
 			$wgStyleDirectory   = "$IP/skins";
 		}
 
+		RepoGroup::destroySingleton();
+		FileBackendGroup::destroySingleton();
 	}
 
 	public function tearDown() {
+		foreach ( $this->savedGlobals as $var => $val ) {
+			$GLOBALS[$var] = $val;
+		}
+		// Restore backends
+		RepoGroup::destroySingleton();
+		FileBackendGroup::destroySingleton();
+
 		$this->teardownUploadDir( $this->uploadDir );
 	}
 
@@ -159,10 +185,10 @@ class UploadFromUrlTestSuite extends PHPUnit_Framework_TestSuite {
 			return $dir;
 		}
 
-		wfMkdirParents( $dir . '/3/3a' );
+		wfMkdirParents( $dir . '/3/3a', null, __METHOD__ );
 		copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" );
 
-		wfMkdirParents( $dir . '/0/09' );
+		wfMkdirParents( $dir . '/0/09', null, __METHOD__ );
 		copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" );
 
 		return $dir;
-- 
cgit v1.2.3-54-g00ecf