summaryrefslogtreecommitdiff
path: root/tests/phpunit
diff options
context:
space:
mode:
Diffstat (limited to 'tests/phpunit')
-rw-r--r--tests/phpunit/Makefile91
-rw-r--r--tests/phpunit/MediaWikiLangTestCase.php33
-rw-r--r--tests/phpunit/MediaWikiPHPUnitCommand.php119
-rw-r--r--tests/phpunit/MediaWikiPHPUnitTestListener.php114
-rw-r--r--tests/phpunit/MediaWikiTestCase.php948
-rw-r--r--tests/phpunit/README53
-rw-r--r--tests/phpunit/TODO10
-rw-r--r--tests/phpunit/bootstrap.php15
-rw-r--r--tests/phpunit/data/db/mysql/functions.sql12
-rw-r--r--tests/phpunit/data/db/postgres/functions.sql12
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.13.sql342
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.15.sql454
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.16.sql478
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.17.sql511
-rw-r--r--tests/phpunit/data/db/sqlite/tables-1.18.sql530
-rw-r--r--tests/phpunit/data/less/common/test.common.mixins.less5
-rw-r--r--tests/phpunit/data/less/module/dependency.less3
-rw-r--r--tests/phpunit/data/less/module/styles.css6
-rw-r--r--tests/phpunit/data/less/module/styles.less6
-rw-r--r--tests/phpunit/data/media/1bit-png.pngbin0 -> 167 bytes
-rw-r--r--tests/phpunit/data/media/80x60-2layers.xcfbin0 -> 1162 bytes
-rw-r--r--tests/phpunit/data/media/80x60-Greyscale.xcfbin0 -> 667 bytes
-rw-r--r--tests/phpunit/data/media/80x60-RGB.xcfbin0 -> 677 bytes
-rw-r--r--tests/phpunit/data/media/Animated_PNG_example_bouncing_beach_ball.pngbin0 -> 72209 bytes
-rw-r--r--tests/phpunit/data/media/Gtk-media-play-ltr.svg35
-rw-r--r--tests/phpunit/data/media/Png-native-test.pngbin0 -> 4665 bytes
-rw-r--r--tests/phpunit/data/media/QA_icon.svg77
-rw-r--r--tests/phpunit/data/media/README38
-rw-r--r--tests/phpunit/data/media/Toll_Texas_1.svg150
-rw-r--r--tests/phpunit/data/media/US_states_by_total_state_tax_revenue.svg248
-rw-r--r--tests/phpunit/data/media/Wikimedia-logo.svg14
-rw-r--r--tests/phpunit/data/media/Xmp-exif-multilingual_test.jpgbin0 -> 12544 bytes
-rw-r--r--tests/phpunit/data/media/animated-xmp.gifbin0 -> 3864 bytes
-rw-r--r--tests/phpunit/data/media/animated.gifbin0 -> 497 bytes
-rw-r--r--tests/phpunit/data/media/broken_exif_date.jpgbin0 -> 3233 bytes
-rw-r--r--tests/phpunit/data/media/exif-gps.jpgbin0 -> 665 bytes
-rw-r--r--tests/phpunit/data/media/exif-user-comment.jpgbin0 -> 484 bytes
-rw-r--r--tests/phpunit/data/media/greyscale-na-png.pngbin0 -> 365 bytes
-rw-r--r--tests/phpunit/data/media/greyscale-png.pngbin0 -> 415 bytes
-rw-r--r--tests/phpunit/data/media/iptc-invalid-psir.jpgbin0 -> 9574 bytes
-rw-r--r--tests/phpunit/data/media/iptc-timetest-invalid.jpgbin0 -> 9573 bytes
-rw-r--r--tests/phpunit/data/media/iptc-timetest.jpgbin0 -> 9573 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-comment-binary.jpgbin0 -> 448 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-comment-iso8859-1.jpgbin0 -> 447 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-comment-multiple.jpgbin0 -> 431 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-comment-utf.jpgbin0 -> 445 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-iptc-bad-hash.jpgbin0 -> 499 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-iptc-good-hash.jpgbin0 -> 499 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-padding-even.jpgbin0 -> 450 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-padding-odd.jpgbin0 -> 451 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-xmp-alt.jpgbin0 -> 3255 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-xmp-psir.jpgbin0 -> 3308 bytes
-rw-r--r--tests/phpunit/data/media/jpeg-xmp-psir.xmp35
-rw-r--r--tests/phpunit/data/media/landscape-plain.jpgbin0 -> 38771 bytes
-rw-r--r--tests/phpunit/data/media/nonanimated.gifbin0 -> 200 bytes
-rw-r--r--tests/phpunit/data/media/portrait-rotated.jpgbin0 -> 38577 bytes
-rw-r--r--tests/phpunit/data/media/rgb-na-png.pngbin0 -> 593 bytes
-rw-r--r--tests/phpunit/data/media/rgb-png.pngbin0 -> 663 bytes
-rw-r--r--tests/phpunit/data/media/test.jpgbin0 -> 437 bytes
-rw-r--r--tests/phpunit/data/media/test.tiffbin0 -> 566 bytes
-rw-r--r--tests/phpunit/data/media/xmp.pngbin0 -> 582 bytes
-rw-r--r--tests/phpunit/data/xmp/1.result.php8
-rw-r--r--tests/phpunit/data/xmp/1.xmp11
-rw-r--r--tests/phpunit/data/xmp/2.result.php8
-rw-r--r--tests/phpunit/data/xmp/2.xmp12
-rw-r--r--tests/phpunit/data/xmp/3-invalid.result.php7
-rw-r--r--tests/phpunit/data/xmp/3-invalid.xmp31
-rw-r--r--tests/phpunit/data/xmp/3.result.php8
-rw-r--r--tests/phpunit/data/xmp/3.xmp29
-rw-r--r--tests/phpunit/data/xmp/4.result.php7
-rw-r--r--tests/phpunit/data/xmp/4.xmp22
-rw-r--r--tests/phpunit/data/xmp/5.result.php7
-rw-r--r--tests/phpunit/data/xmp/5.xmp16
-rw-r--r--tests/phpunit/data/xmp/6.result.php8
-rw-r--r--tests/phpunit/data/xmp/6.xmp18
-rw-r--r--tests/phpunit/data/xmp/7.result.php52
-rw-r--r--tests/phpunit/data/xmp/7.xmp67
-rw-r--r--tests/phpunit/data/xmp/README3
-rw-r--r--tests/phpunit/data/xmp/bag-for-seq.result.php10
-rw-r--r--tests/phpunit/data/xmp/bag-for-seq.xmp1
-rw-r--r--tests/phpunit/data/xmp/flash.result.php8
-rw-r--r--tests/phpunit/data/xmp/flash.xmp11
-rw-r--r--tests/phpunit/data/xmp/gps.result.php11
-rw-r--r--tests/phpunit/data/xmp/gps.xmp17
-rw-r--r--tests/phpunit/data/xmp/invalid-child-not-struct.result.php7
-rw-r--r--tests/phpunit/data/xmp/invalid-child-not-struct.xmp12
-rw-r--r--tests/phpunit/data/xmp/no-namespace.result.php7
-rw-r--r--tests/phpunit/data/xmp/no-namespace.xmp11
-rw-r--r--tests/phpunit/data/xmp/no-recognized-props.result.php2
-rw-r--r--tests/phpunit/data/xmp/no-recognized-props.xmp8
-rw-r--r--tests/phpunit/data/xmp/utf16BE.result.php12
-rw-r--r--tests/phpunit/data/xmp/utf16BE.xmpbin0 -> 930 bytes
-rw-r--r--tests/phpunit/data/xmp/utf16LE.result.php12
-rw-r--r--tests/phpunit/data/xmp/utf16LE.xmpbin0 -> 930 bytes
-rw-r--r--tests/phpunit/data/xmp/utf32BE.result.php12
-rw-r--r--tests/phpunit/data/xmp/utf32BE.xmpbin0 -> 1856 bytes
-rw-r--r--tests/phpunit/data/xmp/utf32LE.result.php12
-rw-r--r--tests/phpunit/data/xmp/utf32LE.xmpbin0 -> 1856 bytes
-rw-r--r--tests/phpunit/data/xmp/xmpExt.result.php8
-rw-r--r--tests/phpunit/data/xmp/xmpExt.xmp13
-rw-r--r--tests/phpunit/data/xmp/xmpExt2.xmp8
-rw-r--r--tests/phpunit/data/zip/cd-gap.zipbin0 -> 182 bytes
-rw-r--r--tests/phpunit/data/zip/cd-truncated.zipbin0 -> 171 bytes
-rw-r--r--tests/phpunit/data/zip/class-trailing-null.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/class-trailing-slash.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/class.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/empty.zipbin0 -> 22 bytes
-rw-r--r--tests/phpunit/data/zip/looks-like-zip64.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/nosig.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/split.zipbin0 -> 196 bytes
-rw-r--r--tests/phpunit/data/zip/trail.zipbin0 -> 181 bytes
-rw-r--r--tests/phpunit/data/zip/wrong-cd-start-disk.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/data/zip/wrong-central-entry-sig.zipbin0 -> 173 bytes
-rw-r--r--tests/phpunit/docs/ExportDemoTest.php39
-rw-r--r--tests/phpunit/includes/ArticleTablesTest.php32
-rw-r--r--tests/phpunit/includes/ArticleTest.php92
-rw-r--r--tests/phpunit/includes/BlockTest.php354
-rw-r--r--tests/phpunit/includes/CdbTest.php88
-rw-r--r--tests/phpunit/includes/CollationTest.php111
-rw-r--r--tests/phpunit/includes/DiffHistoryBlobTest.php44
-rw-r--r--tests/phpunit/includes/EditPageTest.php491
-rw-r--r--tests/phpunit/includes/ExternalStoreTest.php81
-rw-r--r--tests/phpunit/includes/ExtraParserTest.php157
-rw-r--r--tests/phpunit/includes/FallbackTest.php73
-rw-r--r--tests/phpunit/includes/FauxRequestTest.php15
-rw-r--r--tests/phpunit/includes/FauxResponseTest.php74
-rw-r--r--tests/phpunit/includes/FormOptionsInitializationTest.php84
-rw-r--r--tests/phpunit/includes/FormOptionsTest.php91
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalTest.php652
-rw-r--r--tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php31
-rw-r--r--tests/phpunit/includes/GlobalFunctions/README2
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php111
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php120
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php182
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php36
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php111
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php40
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php146
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php91
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php30
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php134
-rw-r--r--tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php119
-rw-r--r--tests/phpunit/includes/HTMLCheckMatrixTest.php102
-rw-r--r--tests/phpunit/includes/HashRingTest.php53
-rw-r--r--tests/phpunit/includes/HooksTest.php158
-rw-r--r--tests/phpunit/includes/HtmlFormatterTest.php81
-rw-r--r--tests/phpunit/includes/HtmlTest.php631
-rw-r--r--tests/phpunit/includes/HttpTest.php216
-rw-r--r--tests/phpunit/includes/IPTest.php595
-rw-r--r--tests/phpunit/includes/LanguageConverterTest.php148
-rw-r--r--tests/phpunit/includes/LicensesTest.php22
-rw-r--r--tests/phpunit/includes/LinkerTest.php71
-rw-r--r--tests/phpunit/includes/LinksUpdateTest.php175
-rw-r--r--tests/phpunit/includes/LocalFileTest.php107
-rw-r--r--tests/phpunit/includes/LocalisationCacheTest.php31
-rw-r--r--tests/phpunit/includes/MWExceptionHandlerTest.php73
-rw-r--r--tests/phpunit/includes/MWFunctionTest.php30
-rw-r--r--tests/phpunit/includes/MWNamespaceTest.php571
-rw-r--r--tests/phpunit/includes/MessageTest.php130
-rw-r--r--tests/phpunit/includes/OutputPageTest.php133
-rw-r--r--tests/phpunit/includes/PathRouterTest.php263
-rw-r--r--tests/phpunit/includes/PreferencesTest.php91
-rw-r--r--tests/phpunit/includes/RecentChangeTest.php280
-rw-r--r--tests/phpunit/includes/RequestContextTest.php73
-rw-r--r--tests/phpunit/includes/ResourceLoaderTest.php149
-rw-r--r--tests/phpunit/includes/RevisionStorageTest.php546
-rw-r--r--tests/phpunit/includes/RevisionStorageTest_ContentHandlerUseDB.php81
-rw-r--r--tests/phpunit/includes/RevisionTest.php481
-rw-r--r--tests/phpunit/includes/SampleTest.php105
-rw-r--r--tests/phpunit/includes/SanitizerTest.php302
-rw-r--r--tests/phpunit/includes/SanitizerValidateEmailTest.php103
-rw-r--r--tests/phpunit/includes/SiteConfigurationTest.php335
-rw-r--r--tests/phpunit/includes/StringUtilsTest.php147
-rw-r--r--tests/phpunit/includes/TemplateCategoriesTest.php53
-rw-r--r--tests/phpunit/includes/TestUser.php59
-rw-r--r--tests/phpunit/includes/TimeAdjustTest.php41
-rw-r--r--tests/phpunit/includes/TimestampTest.php304
-rw-r--r--tests/phpunit/includes/TitleMethodsTest.php300
-rw-r--r--tests/phpunit/includes/TitlePermissionTest.php742
-rw-r--r--tests/phpunit/includes/TitleTest.php485
-rw-r--r--tests/phpunit/includes/UIDGeneratorTest.php98
-rw-r--r--tests/phpunit/includes/UserMailerTest.php14
-rw-r--r--tests/phpunit/includes/UserTest.php237
-rw-r--r--tests/phpunit/includes/WebRequestTest.php310
-rw-r--r--tests/phpunit/includes/WikiPageTest.php1074
-rw-r--r--tests/phpunit/includes/WikiPageTest_ContentHandlerUseDB.php53
-rw-r--r--tests/phpunit/includes/XmlJsTest.php24
-rw-r--r--tests/phpunit/includes/XmlSelectTest.php173
-rw-r--r--tests/phpunit/includes/XmlTest.php402
-rw-r--r--tests/phpunit/includes/XmlTypeCheckTest.php30
-rw-r--r--tests/phpunit/includes/ZipDirectoryReaderTest.php85
-rw-r--r--tests/phpunit/includes/api/ApiAccountCreationTest.php159
-rw-r--r--tests/phpunit/includes/api/ApiBlockTest.php95
-rw-r--r--tests/phpunit/includes/api/ApiEditPageTest.php417
-rw-r--r--tests/phpunit/includes/api/ApiOptionsTest.php420
-rw-r--r--tests/phpunit/includes/api/ApiParseTest.php29
-rw-r--r--tests/phpunit/includes/api/ApiPurgeTest.php40
-rw-r--r--tests/phpunit/includes/api/ApiTest.php259
-rw-r--r--tests/phpunit/includes/api/ApiTestCase.php253
-rw-r--r--tests/phpunit/includes/api/ApiTestCaseUpload.php149
-rw-r--r--tests/phpunit/includes/api/ApiUploadTest.php561
-rw-r--r--tests/phpunit/includes/api/ApiWatchTest.php148
-rw-r--r--tests/phpunit/includes/api/PrefixUniquenessTest.php25
-rw-r--r--tests/phpunit/includes/api/RandomImageGenerator.php468
-rw-r--r--tests/phpunit/includes/api/format/ApiFormatPhpTest.php17
-rw-r--r--tests/phpunit/includes/api/format/ApiFormatTestBase.php22
-rw-r--r--tests/phpunit/includes/api/generateRandomImages.php46
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryBasicTest.php395
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinue2Test.php68
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinueTest.php313
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryContinueTestBase.php209
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php39
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryTest.php66
-rw-r--r--tests/phpunit/includes/api/query/ApiQueryTestBase.php150
-rw-r--r--tests/phpunit/includes/api/words.txt1000
-rw-r--r--tests/phpunit/includes/cache/GenderCacheTest.php104
-rw-r--r--tests/phpunit/includes/cache/MessageCacheTest.php128
-rw-r--r--tests/phpunit/includes/cache/ProcessCacheLRUTest.php237
-rw-r--r--tests/phpunit/includes/content/ContentHandlerTest.php451
-rw-r--r--tests/phpunit/includes/content/CssContentTest.php87
-rw-r--r--tests/phpunit/includes/content/JavaScriptContentTest.php287
-rw-r--r--tests/phpunit/includes/content/TextContentTest.php458
-rw-r--r--tests/phpunit/includes/content/WikitextContentHandlerTest.php227
-rw-r--r--tests/phpunit/includes/content/WikitextContentTest.php404
-rw-r--r--tests/phpunit/includes/db/DatabaseMysqlBaseTest.php209
-rw-r--r--tests/phpunit/includes/db/DatabaseSQLTest.php721
-rw-r--r--tests/phpunit/includes/db/DatabaseSqliteTest.php421
-rw-r--r--tests/phpunit/includes/db/DatabaseTest.php234
-rw-r--r--tests/phpunit/includes/db/DatabaseTestHelper.php166
-rw-r--r--tests/phpunit/includes/db/ORMRowTest.php226
-rw-r--r--tests/phpunit/includes/db/ORMTableTest.php146
-rw-r--r--tests/phpunit/includes/db/TestORMRowTest.php215
-rw-r--r--tests/phpunit/includes/debug/MWDebugTest.php72
-rw-r--r--tests/phpunit/includes/filebackend/FileBackendTest.php2306
-rw-r--r--tests/phpunit/includes/filerepo/FileRepoTest.php55
-rw-r--r--tests/phpunit/includes/filerepo/StoreBatchTest.php134
-rw-r--r--tests/phpunit/includes/installer/InstallDocFormatterTest.php65
-rw-r--r--tests/phpunit/includes/installer/OracleInstallerTest.php48
-rw-r--r--tests/phpunit/includes/jobqueue/JobQueueTest.php329
-rw-r--r--tests/phpunit/includes/json/FormatJsonTest.php161
-rw-r--r--tests/phpunit/includes/libs/CSSJanusTest.php606
-rw-r--r--tests/phpunit/includes/libs/CSSMinTest.php133
-rw-r--r--tests/phpunit/includes/libs/GenericArrayObjectTest.php263
-rw-r--r--tests/phpunit/includes/libs/IEUrlExtensionTest.php126
-rw-r--r--tests/phpunit/includes/libs/JavaScriptMinifierTest.php170
-rw-r--r--tests/phpunit/includes/logging/LogFormatterTest.php207
-rw-r--r--tests/phpunit/includes/logging/LogTests.i18n.php15
-rw-r--r--tests/phpunit/includes/media/BitmapMetadataHandlerTest.php167
-rw-r--r--tests/phpunit/includes/media/BitmapScalingTest.php137
-rw-r--r--tests/phpunit/includes/media/ExifBitmapTest.php139
-rw-r--r--tests/phpunit/includes/media/ExifRotationTest.php248
-rw-r--r--tests/phpunit/includes/media/ExifTest.php46
-rw-r--r--tests/phpunit/includes/media/FakeDimensionFile.php28
-rw-r--r--tests/phpunit/includes/media/FormatMetadataTest.php59
-rw-r--r--tests/phpunit/includes/media/GIFMetadataExtractorTest.php107
-rw-r--r--tests/phpunit/includes/media/GIFTest.php120
-rw-r--r--tests/phpunit/includes/media/IPTCTest.php81
-rw-r--r--tests/phpunit/includes/media/JpegMetadataExtractorTest.php109
-rw-r--r--tests/phpunit/includes/media/JpegTest.php33
-rw-r--r--tests/phpunit/includes/media/MediaHandlerTest.php53
-rw-r--r--tests/phpunit/includes/media/PNGMetadataExtractorTest.php155
-rw-r--r--tests/phpunit/includes/media/PNGTest.php123
-rw-r--r--tests/phpunit/includes/media/SVGMetadataExtractorTest.php112
-rw-r--r--tests/phpunit/includes/media/TiffTest.php39
-rw-r--r--tests/phpunit/includes/media/XMPTest.php166
-rw-r--r--tests/phpunit/includes/media/XMPValidateTest.php46
-rw-r--r--tests/phpunit/includes/normal/CleanUpTest.php409
-rw-r--r--tests/phpunit/includes/objectcache/BagOStuffTest.php149
-rw-r--r--tests/phpunit/includes/parser/MagicVariableTest.php242
-rw-r--r--tests/phpunit/includes/parser/MediaWikiParserTest.php120
-rw-r--r--tests/phpunit/includes/parser/NewParserTest.php969
-rw-r--r--tests/phpunit/includes/parser/ParserMethodsTest.php95
-rw-r--r--tests/phpunit/includes/parser/ParserOutputTest.php59
-rw-r--r--tests/phpunit/includes/parser/ParserPreloadTest.php80
-rw-r--r--tests/phpunit/includes/parser/PreprocessorTest.php239
-rw-r--r--tests/phpunit/includes/parser/TagHooksTest.php82
-rw-r--r--tests/phpunit/includes/parser/TidyTest.php44
-rw-r--r--tests/phpunit/includes/search/SearchEngineTest.php176
-rw-r--r--tests/phpunit/includes/search/SearchUpdateTest.php74
-rw-r--r--tests/phpunit/includes/site/MediaWikiSiteTest.php90
-rw-r--r--tests/phpunit/includes/site/SiteListTest.php197
-rw-r--r--tests/phpunit/includes/site/SiteSQLStoreTest.php134
-rw-r--r--tests/phpunit/includes/site/SiteTest.php296
-rw-r--r--tests/phpunit/includes/site/TestSites.php100
-rw-r--r--tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php79
-rw-r--r--tests/phpunit/includes/specials/SpecialPreferencesTest.php60
-rw-r--r--tests/phpunit/includes/specials/SpecialRecentchangesTest.php125
-rw-r--r--tests/phpunit/includes/specials/SpecialSearchTest.php139
-rw-r--r--tests/phpunit/includes/upload/UploadBaseTest.php147
-rw-r--r--tests/phpunit/includes/upload/UploadFromUrlTest.php350
-rw-r--r--tests/phpunit/includes/upload/UploadStashTest.php76
-rw-r--r--tests/phpunit/install-phpunit.sh37
-rw-r--r--tests/phpunit/languages/LanguageAmTest.php35
-rw-r--r--tests/phpunit/languages/LanguageArTest.php87
-rw-r--r--tests/phpunit/languages/LanguageBeTest.php42
-rw-r--r--tests/phpunit/languages/LanguageBe_taraskTest.php95
-rw-r--r--tests/phpunit/languages/LanguageBhoTest.php35
-rw-r--r--tests/phpunit/languages/LanguageBsTest.php42
-rw-r--r--tests/phpunit/languages/LanguageClassesTestCase.php74
-rw-r--r--tests/phpunit/languages/LanguageCsTest.php41
-rw-r--r--tests/phpunit/languages/LanguageCuTest.php42
-rw-r--r--tests/phpunit/languages/LanguageCyTest.php43
-rw-r--r--tests/phpunit/languages/LanguageDsbTest.php41
-rw-r--r--tests/phpunit/languages/LanguageFrTest.php35
-rw-r--r--tests/phpunit/languages/LanguageGaTest.php35
-rw-r--r--tests/phpunit/languages/LanguageGdTest.php53
-rw-r--r--tests/phpunit/languages/LanguageGvTest.php44
-rw-r--r--tests/phpunit/languages/LanguageHeTest.php132
-rw-r--r--tests/phpunit/languages/LanguageHiTest.php35
-rw-r--r--tests/phpunit/languages/LanguageHrTest.php42
-rw-r--r--tests/phpunit/languages/LanguageHsbTest.php41
-rw-r--r--tests/phpunit/languages/LanguageHuTest.php35
-rw-r--r--tests/phpunit/languages/LanguageHyTest.php35
-rw-r--r--tests/phpunit/languages/LanguageKshTest.php35
-rw-r--r--tests/phpunit/languages/LanguageLnTest.php35
-rw-r--r--tests/phpunit/languages/LanguageLtTest.php63
-rw-r--r--tests/phpunit/languages/LanguageLvTest.php44
-rw-r--r--tests/phpunit/languages/LanguageMgTest.php36
-rw-r--r--tests/phpunit/languages/LanguageMkTest.php40
-rw-r--r--tests/phpunit/languages/LanguageMlTest.php38
-rw-r--r--tests/phpunit/languages/LanguageMoTest.php45
-rw-r--r--tests/phpunit/languages/LanguageMtTest.php77
-rw-r--r--tests/phpunit/languages/LanguageNlTest.php24
-rw-r--r--tests/phpunit/languages/LanguageNsoTest.php34
-rw-r--r--tests/phpunit/languages/LanguagePlTest.php77
-rw-r--r--tests/phpunit/languages/LanguageRoTest.php45
-rw-r--r--tests/phpunit/languages/LanguageRuTest.php105
-rw-r--r--tests/phpunit/languages/LanguageSeTest.php53
-rw-r--r--tests/phpunit/languages/LanguageSgsTest.php71
-rw-r--r--tests/phpunit/languages/LanguageShTest.php42
-rw-r--r--tests/phpunit/languages/LanguageSkTest.php42
-rw-r--r--tests/phpunit/languages/LanguageSlTest.php44
-rw-r--r--tests/phpunit/languages/LanguageSmaTest.php53
-rw-r--r--tests/phpunit/languages/LanguageSrTest.php246
-rw-r--r--tests/phpunit/languages/LanguageTest.php1567
-rw-r--r--tests/phpunit/languages/LanguageTiTest.php34
-rw-r--r--tests/phpunit/languages/LanguageTlTest.php34
-rw-r--r--tests/phpunit/languages/LanguageTrTest.php59
-rw-r--r--tests/phpunit/languages/LanguageUkTest.php72
-rw-r--r--tests/phpunit/languages/LanguageUzTest.php121
-rw-r--r--tests/phpunit/languages/LanguageWaTest.php34
-rw-r--r--tests/phpunit/languages/utils/CLDRPluralRuleEvaluatorTest.php145
-rw-r--r--tests/phpunit/maintenance/DumpTestCase.php379
-rw-r--r--tests/phpunit/maintenance/MaintenanceTest.php816
-rw-r--r--tests/phpunit/maintenance/backupPrefetchTest.php275
-rw-r--r--tests/phpunit/maintenance/backupTextPassTest.php581
-rw-r--r--tests/phpunit/maintenance/backup_LogTest.php228
-rw-r--r--tests/phpunit/maintenance/backup_PageTest.php404
-rw-r--r--tests/phpunit/maintenance/fetchTextTest.php238
-rw-r--r--tests/phpunit/maintenance/getSlaveServerTest.php67
-rw-r--r--tests/phpunit/mocks/filebackend/MockFSFile.php69
-rw-r--r--tests/phpunit/mocks/filebackend/MockFileBackend.php122
-rw-r--r--tests/phpunit/mocks/media/MockBitmapHandler.php92
-rw-r--r--tests/phpunit/phpunit.php119
-rw-r--r--tests/phpunit/run-tests.bat1
-rw-r--r--tests/phpunit/skins/SideBarTest.php223
-rw-r--r--tests/phpunit/structure/AutoLoaderTest.php56
-rw-r--r--tests/phpunit/structure/ResourcesTest.php131
-rw-r--r--tests/phpunit/structure/StructureTest.php63
-rw-r--r--tests/phpunit/suite.xml51
-rw-r--r--tests/phpunit/suites/ExtensionsParserTestSuite.php8
-rw-r--r--tests/phpunit/suites/ExtensionsTestSuite.php33
-rw-r--r--tests/phpunit/suites/UploadFromUrlTestSuite.php207
363 files changed, 49070 insertions, 0 deletions
diff --git a/tests/phpunit/Makefile b/tests/phpunit/Makefile
new file mode 100644
index 00000000..c3e2a303
--- /dev/null
+++ b/tests/phpunit/Makefile
@@ -0,0 +1,91 @@
+.PHONY: help test phpunit install coverage warning destructive parser noparser safe databaseless list-groups
+.DEFAULT: warning
+
+SHELL = /bin/sh
+CONFIG_FILE = ${PWD}/suite.xml
+PHP = php
+PU = ${PHP} phpunit.php --configuration ${CONFIG_FILE} ${FLAGS}
+
+all test: warning
+
+warning:
+ @echo "Run 'make help' to get usage"
+ @echo ""
+ @echo "WARNING -- some tests are DESTRUCTIVE and will alter your wiki."
+ @echo "DO NOT RUN THESE TESTS on a production wiki."
+ @echo ""
+ @echo "Until the default tests are made non-destructive, you can run"
+ @echo "the destructive tests like so:"
+ @echo ""
+ @echo " make destructive"
+ @echo ""
+ @echo "Some tests are expected to be safe, you can run them with"
+ @echo ""
+ @echo " make safe"
+ @echo ""
+ @echo "You are recommended to run the tests with read-only credentials."
+ @echo ""
+ @echo "If you don't have a database running, you can still run"
+ @echo ""
+ @echo " make databaseless"
+ @echo ""
+
+destructive: phpunit
+
+phpunit:
+ ${PU}
+
+install:
+ ./install-phpunit.sh
+
+tap:
+ ${PU} --tap
+
+coverage:
+ ${PU} --coverage-html ../../docs/code-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,ParserFuzz,Stub
+
+safe:
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub
+
+databaseless:
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Database,Stub
+
+database:
+ ${PU} --exclude-group Broken,ParserFuzz,Destructive,Stub --group Database
+
+list-groups:
+ ${PU} --list-groups
+
+help:
+ # Usage:
+ # make <target> [OPTION=value]
+ #
+ # Targets:
+ # phpunit (default) Run all the tests with phpunit
+ # install Install PHPUnit from phpunit.de
+ # tap Run the tests individually through Test::Harness's prove(1)
+ # help You're looking at it!
+ # coverage Run the tests and generates an HTML code coverage report
+ # You will need the Xdebug PHP extension for the later.
+ # [no]parser Skip or only run Parser tests
+ #
+ # list-groups List availabe Tests groups.
+ #
+ # Options:
+ # CONFIG_FILE Path to a PHPUnit configuration file (default: suite.xml)
+ # FLAGS Additional flags to pass to PHPUnit
+ # PHP Path to php
diff --git a/tests/phpunit/MediaWikiLangTestCase.php b/tests/phpunit/MediaWikiLangTestCase.php
new file mode 100644
index 00000000..1131385f
--- /dev/null
+++ b/tests/phpunit/MediaWikiLangTestCase.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Base class that store and restore the Language objects
+ */
+abstract class MediaWikiLangTestCase extends MediaWikiTestCase {
+
+ protected function setUp() {
+ global $wgLanguageCode, $wgContLang;
+ parent::setUp();
+
+ if ( $wgLanguageCode != $wgContLang->getCode() ) {
+ throw new MWException( "Error in MediaWikiLangTestCase::setUp(): " .
+ "\$wgLanguageCode ('$wgLanguageCode') is different from " .
+ "\$wgContLang->getCode() (" . $wgContLang->getCode() . ")" );
+ }
+
+ // HACK: Call getLanguage() so the real $wgContLang is cached as the user language
+ // rather than our fake one. This is to avoid breaking other, unrelated tests.
+ RequestContext::getMain()->getLanguage();
+
+ $langCode = 'en'; # For mainpage to be 'Main Page'
+ $langObj = Language::factory( $langCode );
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => $langCode,
+ 'wgLang' => $langObj,
+ 'wgContLang' => $langObj,
+ ) );
+
+ MessageCache::singleton()->disable();
+ }
+}
diff --git a/tests/phpunit/MediaWikiPHPUnitCommand.php b/tests/phpunit/MediaWikiPHPUnitCommand.php
new file mode 100644
index 00000000..042956a9
--- /dev/null
+++ b/tests/phpunit/MediaWikiPHPUnitCommand.php
@@ -0,0 +1,119 @@
+<?php
+
+class MediaWikiPHPUnitCommand extends PHPUnit_TextUI_Command {
+
+ public static $additionalOptions = array(
+ 'regex=' => false,
+ 'file=' => false,
+ 'use-filebackend=' => false,
+ 'use-bagostuff=' => false,
+ 'use-jobqueue=' => false,
+ 'keep-uploads' => false,
+ 'use-normal-tables' => false,
+ 'reuse-db' => false,
+ 'wiki=' => false,
+ 'debug-tests' => false,
+ );
+
+ public function __construct() {
+ foreach ( self::$additionalOptions as $option => $default ) {
+ $this->longOptions[$option] = $option . 'Handler';
+ }
+ }
+
+ protected function handleArguments( array $argv ) {
+ parent::handleArguments( $argv );
+
+ if ( !isset( $this->arguments['listeners'] ) ) {
+ $this->arguments['listeners'] = array();
+ }
+
+ foreach ( $this->options[0] as $option ) {
+ switch ( $option[0] ) {
+ case '--debug-tests':
+ $this->arguments['listeners'][] = new MediaWikiPHPUnitTestListener( 'PHPUnitCommand' );
+ break;
+ }
+ }
+ }
+
+ public static function main( $exit = true ) {
+ $command = new self;
+
+ if ( wfIsWindows() ) {
+ # 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(
+ __DIR__
+ . PATH_SEPARATOR
+ . get_include_path()
+ );
+
+ $command->run( $_SERVER['argv'], $exit );
+ }
+
+ public function __call( $func, $args ) {
+
+ if ( substr( $func, -7 ) == 'Handler' ) {
+ if ( is_null( $args[0] ) ) {
+ $args[0] = true;
+ } //Booleans
+ self::$additionalOptions[substr( $func, 0, -7 )] = $args[0];
+ }
+ }
+
+ public function run( array $argv, $exit = true ) {
+ wfProfileIn( __METHOD__ );
+
+ $ret = parent::run( $argv, false );
+
+ wfProfileOut( __METHOD__ );
+
+ // Return to real wiki db, so profiling data is preserved
+ MediaWikiTestCase::teardownTestDB();
+
+ // Log profiling data, e.g. in the database or UDP
+ wfLogProfilingData();
+
+ if ( $exit ) {
+ exit( $ret );
+ } else {
+ return $ret;
+ }
+ }
+
+ public function showHelp() {
+ parent::showHelp();
+
+ print <<<EOT
+
+ParserTest-specific options:
+
+ --regex="<regex>" Only run parser tests that match the given regex
+ --file="<filename>" File describing parser tests
+ --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.
+
+
+Debugging options:
+ --debug-tests Log testing activity to the PHPUnitCommand log channel.
+
+EOT;
+ }
+}
diff --git a/tests/phpunit/MediaWikiPHPUnitTestListener.php b/tests/phpunit/MediaWikiPHPUnitTestListener.php
new file mode 100644
index 00000000..7237ef32
--- /dev/null
+++ b/tests/phpunit/MediaWikiPHPUnitTestListener.php
@@ -0,0 +1,114 @@
+<?php
+class MediaWikiPHPUnitTestListener implements PHPUnit_Framework_TestListener {
+
+ /**
+ * @var string
+ */
+ protected $logChannel;
+
+ public function __construct( $logChannel ) {
+ $this->logChannel = $logChannel;
+ }
+
+ protected function getTestName( PHPUnit_Framework_Test $test ) {
+ $name = get_class( $test );
+
+ if ( $test instanceof PHPUnit_Framework_TestCase ) {
+ $name .= '::' . $test->getName( true );
+ }
+
+ return $name;
+ }
+
+ protected function getErrorName( Exception $exception ) {
+ $name = get_class( $exception );
+ $name = "[$name] " . $exception->getMessage();
+
+ return $name;
+ }
+
+ /**
+ * An error occurred.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ */
+ public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) {
+ wfDebugLog( $this->logChannel, 'ERROR in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) );
+ }
+
+ /**
+ * A failure occurred.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param PHPUnit_Framework_AssertionFailedError $e
+ * @param float $time
+ */
+ public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) {
+ wfDebugLog( $this->logChannel, 'FAILURE in ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) );
+ }
+
+ /**
+ * Incomplete test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ */
+ public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
+ wfDebugLog( $this->logChannel, 'Incomplete test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) );
+ }
+
+ /**
+ * Skipped test.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param Exception $e
+ * @param float $time
+ *
+ * @since Method available since Release 3.0.0
+ */
+ public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) {
+ wfDebugLog( $this->logChannel, 'Skipped test ' . $this->getTestName( $test ) . ': ' . $this->getErrorName( $e ) );
+ }
+
+ /**
+ * A test suite started.
+ *
+ * @param PHPUnit_Framework_TestSuite $suite
+ * @since Method available since Release 2.2.0
+ */
+ public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) {
+ wfDebugLog( $this->logChannel, 'START suite ' . $suite->getName() );
+ }
+
+ /**
+ * A test suite ended.
+ *
+ * @param PHPUnit_Framework_TestSuite $suite
+ * @since Method available since Release 2.2.0
+ */
+ public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) {
+ wfDebugLog( $this->logChannel, 'END suite ' . $suite->getName() );
+ }
+
+ /**
+ * A test started.
+ *
+ * @param PHPUnit_Framework_Test $test
+ */
+ public function startTest( PHPUnit_Framework_Test $test ) {
+ wfDebugLog( $this->logChannel, 'Start test ' . $this->getTestName( $test ) );
+ }
+
+ /**
+ * A test ended.
+ *
+ * @param PHPUnit_Framework_Test $test
+ * @param float $time
+ */
+ public function endTest( PHPUnit_Framework_Test $test, $time ) {
+ wfDebugLog( $this->logChannel, 'End test ' . $this->getTestName( $test ) );
+ }
+}
diff --git a/tests/phpunit/MediaWikiTestCase.php b/tests/phpunit/MediaWikiTestCase.php
new file mode 100644
index 00000000..6ce78b56
--- /dev/null
+++ b/tests/phpunit/MediaWikiTestCase.php
@@ -0,0 +1,948 @@
+<?php
+
+abstract class MediaWikiTestCase extends PHPUnit_Framework_TestCase {
+ public $suite;
+ public $regex = '';
+ public $runDisabled = false;
+
+ /**
+ * $called tracks whether the setUp and tearDown method has been called.
+ * class extending MediaWikiTestCase usually override setUp and tearDown
+ * but forget to call the parent.
+ *
+ * The array format takes a method name as key and anything as a value.
+ * By asserting the key exist, we know the child class has called the
+ * parent.
+ *
+ * This property must be private, we do not want child to override it,
+ * they should call the appropriate parent method instead.
+ */
+ private $called = array();
+
+ /**
+ * @var Array of TestUser
+ */
+ public static $users;
+
+ /**
+ * @var DatabaseBase
+ */
+ protected $db;
+ protected $tablesUsed = array(); // tables with data
+
+ private static $useTemporaryTables = true;
+ private static $reuseDB = false;
+ private static $dbSetup = false;
+ private static $oldTablePrefix = false;
+
+ /**
+ * Holds the paths of temporary files/directories created through getNewTempFile,
+ * and getNewTempDirectory
+ *
+ * @var array
+ */
+ private $tmpfiles = array();
+
+ /**
+ * Holds original values of MediaWiki configuration settings
+ * to be restored in tearDown().
+ * See also setMwGlobal().
+ * @var array
+ */
+ private $mwGlobals = array();
+
+ /**
+ * Table name prefixes. Oracle likes it shorter.
+ */
+ const DB_PREFIX = 'unittest_';
+ const ORA_DB_PREFIX = 'ut_';
+
+ protected $supportedDBs = array(
+ 'mysql',
+ 'sqlite',
+ 'postgres',
+ 'oracle'
+ );
+
+ function __construct( $name = null, array $data = array(), $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->backupGlobals = false;
+ $this->backupStaticAttributes = false;
+ }
+
+ function run( PHPUnit_Framework_TestResult $result = null ) {
+ /* Some functions require some kind of caching, and will end up using the db,
+ * which we can't allow, as that would open a new connection for mysql.
+ * Replace with a HashBag. They would not be going to persist anyway.
+ */
+ ObjectCache::$instances[CACHE_DB] = new HashBagOStuff;
+
+ $needsResetDB = false;
+ $logName = get_class( $this ) . '::' . $this->getName( false );
+
+ if ( $this->needsDB() ) {
+ // set up a DB connection for this test to use
+
+ self::$useTemporaryTables = !$this->getCliArg( 'use-normal-tables' );
+ self::$reuseDB = $this->getCliArg( 'reuse-db' );
+
+ $this->db = wfGetDB( DB_MASTER );
+
+ $this->checkDbIsSupported();
+
+ if ( !self::$dbSetup ) {
+ wfProfileIn( $logName . ' (clone-db)' );
+
+ // switch to a temporary clone of the database
+ self::setupTestDB( $this->db, $this->dbPrefix() );
+
+ if ( ( $this->db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
+ $this->resetDB();
+ }
+
+ wfProfileOut( $logName . ' (clone-db)' );
+ }
+
+ wfProfileIn( $logName . ' (prepare-db)' );
+ $this->addCoreDBData();
+ $this->addDBData();
+ wfProfileOut( $logName . ' (prepare-db)' );
+
+ $needsResetDB = true;
+ }
+
+ wfProfileIn( $logName );
+ parent::run( $result );
+ wfProfileOut( $logName );
+
+ if ( $needsResetDB ) {
+ wfProfileIn( $logName . ' (reset-db)' );
+ $this->resetDB();
+ wfProfileOut( $logName . ' (reset-db)' );
+ }
+ }
+
+ function usesTemporaryTables() {
+ return self::$useTemporaryTables;
+ }
+
+ /**
+ * obtains a new temporary file name
+ *
+ * The obtained filename is enlisted to be removed upon tearDown
+ *
+ * @return string: absolute name of the temporary file
+ */
+ protected function getNewTempFile() {
+ $fname = tempnam( wfTempDir(), 'MW_PHPUnit_' . get_class( $this ) . '_' );
+ $this->tmpfiles[] = $fname;
+
+ return $fname;
+ }
+
+ /**
+ * obtains a new temporary directory
+ *
+ * The obtained directory is enlisted to be removed (recursively with all its contained
+ * files) upon tearDown.
+ *
+ * @return string: absolute name of the temporary directory
+ */
+ protected function getNewTempDirectory() {
+ // Starting of with a temporary /file/.
+ $fname = $this->getNewTempFile();
+
+ // Converting the temporary /file/ to a /directory/
+ //
+ // The following is not atomic, but at least we now have a single place,
+ // where temporary directory creation is bundled and can be improved
+ unlink( $fname );
+ $this->assertTrue( wfMkdirParents( $fname ) );
+
+ return $fname;
+ }
+
+ /**
+ * setUp and tearDown should (where significant)
+ * happen in reverse order.
+ */
+ protected function setUp() {
+ wfProfileIn( __METHOD__ );
+ parent::setUp();
+ $this->called['setUp'] = 1;
+
+ /*
+ // @todo global variables to restore for *every* test
+ array(
+ 'wgLang',
+ 'wgContLang',
+ 'wgLanguageCode',
+ 'wgUser',
+ 'wgTitle',
+ );
+ */
+
+ // Cleaning up temporary files
+ foreach ( $this->tmpfiles as $fname ) {
+ if ( is_file( $fname ) || ( is_link( $fname ) ) ) {
+ unlink( $fname );
+ } elseif ( is_dir( $fname ) ) {
+ wfRecursiveRemoveDir( $fname );
+ }
+ }
+
+ if ( $this->needsDB() && $this->db ) {
+ // Clean up open transactions
+ while ( $this->db->trxLevel() > 0 ) {
+ $this->db->rollback();
+ }
+
+ // don't ignore DB errors
+ $this->db->ignoreErrors( false );
+ }
+
+ wfProfileOut( __METHOD__ );
+ }
+
+ protected function tearDown() {
+ wfProfileIn( __METHOD__ );
+
+ // Cleaning up temporary files
+ foreach ( $this->tmpfiles as $fname ) {
+ if ( is_file( $fname ) || ( is_link( $fname ) ) ) {
+ unlink( $fname );
+ } elseif ( is_dir( $fname ) ) {
+ wfRecursiveRemoveDir( $fname );
+ }
+ }
+
+ if ( $this->needsDB() && $this->db ) {
+ // Clean up open transactions
+ while ( $this->db->trxLevel() > 0 ) {
+ $this->db->rollback();
+ }
+
+ // don't ignore DB errors
+ $this->db->ignoreErrors( false );
+ }
+
+ // Restore mw globals
+ foreach ( $this->mwGlobals as $key => $value ) {
+ $GLOBALS[$key] = $value;
+ }
+ $this->mwGlobals = array();
+
+ parent::tearDown();
+ wfProfileOut( __METHOD__ );
+ }
+
+ /**
+ * Make sure MediaWikiTestCase extending classes have called their
+ * parent setUp method
+ */
+ final public function testMediaWikiTestCaseParentSetupCalled() {
+ $this->assertArrayHasKey( 'setUp', $this->called,
+ get_called_class() . "::setUp() must call parent::setUp()"
+ );
+ }
+
+ /**
+ * Individual test functions may override globals (either directly or through this
+ * setMwGlobals() function), however one must call this method at least once for
+ * each key within the setUp().
+ * That way the key is added to the array of globals that will be reset afterwards
+ * in the tearDown(). And, equally important, that way all other tests are executed
+ * with the same settings (instead of using the unreliable local settings for most
+ * tests and fix it only for some tests).
+ *
+ * @example
+ * <code>
+ * protected function setUp() {
+ * $this->setMwGlobals( 'wgRestrictStuff', true );
+ * }
+ *
+ * function testFoo() {}
+ *
+ * function testBar() {}
+ * $this->assertTrue( self::getX()->doStuff() );
+ *
+ * $this->setMwGlobals( 'wgRestrictStuff', false );
+ * $this->assertTrue( self::getX()->doStuff() );
+ * }
+ *
+ * function testQuux() {}
+ * </code>
+ *
+ * @param array|string $pairs Key to the global variable, or an array
+ * of key/value pairs.
+ * @param mixed $value Value to set the global to (ignored
+ * if an array is given as first argument).
+ */
+ protected function setMwGlobals( $pairs, $value = null ) {
+
+ // Normalize (string, value) to an array
+ if ( is_string( $pairs ) ) {
+ $pairs = array( $pairs => $value );
+ }
+
+ foreach ( $pairs as $key => $value ) {
+ // NOTE: make sure we only save the global once or a second call to
+ // setMwGlobals() on the same global would override the original
+ // value.
+ if ( !array_key_exists( $key, $this->mwGlobals ) ) {
+ $this->mwGlobals[$key] = $GLOBALS[$key];
+ }
+
+ // Override the global
+ $GLOBALS[$key] = $value;
+ }
+ }
+
+ /**
+ * Merges the given values into a MW global array variable.
+ * Useful for setting some entries in a configuration array, instead of
+ * setting the entire array.
+ *
+ * @param String $name The name of the global, as in wgFooBar
+ * @param Array $values The array containing the entries to set in that global
+ *
+ * @throws MWException if the designated global is not an array.
+ */
+ protected function mergeMwGlobalArrayValue( $name, $values ) {
+ if ( !isset( $GLOBALS[$name] ) ) {
+ $merged = $values;
+ } else {
+ if ( !is_array( $GLOBALS[$name] ) ) {
+ throw new MWException( "MW global $name is not an array." );
+ }
+
+ // NOTE: do not use array_merge, it screws up for numeric keys.
+ $merged = $GLOBALS[$name];
+ foreach ( $values as $k => $v ) {
+ $merged[$k] = $v;
+ }
+ }
+
+ $this->setMwGlobals( $name, $merged );
+ }
+
+ function dbPrefix() {
+ return $this->db->getType() == 'oracle' ? self::ORA_DB_PREFIX : self::DB_PREFIX;
+ }
+
+ function needsDB() {
+ # if the test says it uses database tables, it needs the database
+ if ( $this->tablesUsed ) {
+ return true;
+ }
+
+ # if the test says it belongs to the Database group, it needs the database
+ $rc = new ReflectionClass( $this );
+ if ( preg_match( '/@group +Database/im', $rc->getDocComment() ) ) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Stub. If a test needs to add additional data to the database, it should
+ * implement this method and do so
+ */
+ 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();
+
+ //Make sysop user
+ $user = User::newFromName( 'UTSysop' );
+
+ if ( $user->idForName() == 0 ) {
+ $user->addToDatabase();
+ $user->setPassword( 'UTSysopPassword' );
+
+ $user->addGroup( 'sysop' );
+ $user->addGroup( 'bureaucrat' );
+ $user->saveSettings();
+ }
+
+ //Make 1 page with 1 revision
+ $page = WikiPage::factory( Title::newFromText( 'UTPage' ) );
+ if ( !$page->getId() == 0 ) {
+ $page->doEditContent(
+ new WikitextContent( 'UTContent' ),
+ 'UTPageSummary',
+ EDIT_NEW,
+ false,
+ User::newFromName( 'UTSysop' ) );
+ }
+ }
+
+ /**
+ * Restores MediaWiki to using the table set (table prefix) it was using before
+ * setupTestDB() was called. Useful if we need to perform database operations
+ * after the test run has finished (such as saving logs or profiling info).
+ */
+ public static function teardownTestDB() {
+ if ( !self::$dbSetup ) {
+ return;
+ }
+
+ CloneDatabase::changePrefix( self::$oldTablePrefix );
+
+ self::$oldTablePrefix = false;
+ self::$dbSetup = false;
+ }
+
+ /**
+ * Creates an empty skeleton of the wiki database by cloning its structure
+ * to equivalent tables using the given $prefix. Then sets MediaWiki to
+ * use the new set of tables (aka schema) instead of the original set.
+ *
+ * This is used to generate a dummy table set, typically consisting of temporary
+ * tables, that will be used by tests instead of the original wiki database tables.
+ *
+ * @note: the original table prefix is stored in self::$oldTablePrefix. This is used
+ * by teardownTestDB() to return the wiki to using the original table set.
+ *
+ * @note: this method only works when first called. Subsequent calls have no effect,
+ * even if using different parameters.
+ *
+ * @param DatabaseBase $db The database connection
+ * @param String $prefix The prefix to use for the new table set (aka schema).
+ *
+ * @throws MWException if the database table prefix is already $prefix
+ */
+ public static function setupTestDB( DatabaseBase $db, $prefix ) {
+ global $wgDBprefix;
+ if ( $wgDBprefix === $prefix ) {
+ throw new MWException( 'Cannot run unit tests, the database prefix is already "' . $prefix . '"' );
+ }
+
+ if ( self::$dbSetup ) {
+ return;
+ }
+
+ $tablesCloned = self::listTables( $db );
+ $dbClone = new CloneDatabase( $db, $tablesCloned, $prefix );
+ $dbClone->useTemporaryTables( self::$useTemporaryTables );
+
+ self::$dbSetup = true;
+ self::$oldTablePrefix = $wgDBprefix;
+
+ if ( ( $db->getType() == 'oracle' || !self::$useTemporaryTables ) && self::$reuseDB ) {
+ CloneDatabase::changePrefix( $prefix );
+
+ return;
+ } else {
+ $dbClone->cloneTableStructure();
+ }
+
+ if ( $db->getType() == 'oracle' ) {
+ $db->query( 'BEGIN FILL_WIKI_INFO; END;' );
+ }
+ }
+
+ /**
+ * Empty all tables so they can be repopulated for tests
+ */
+ private function resetDB() {
+ if ( $this->db ) {
+ if ( $this->db->getType() == 'oracle' ) {
+ if ( self::$useTemporaryTables ) {
+ wfGetLB()->closeAll();
+ $this->db = wfGetDB( DB_MASTER );
+ } else {
+ foreach ( $this->tablesUsed as $tbl ) {
+ if ( $tbl == 'interwiki' ) {
+ continue;
+ }
+ $this->db->query( 'TRUNCATE TABLE ' . $this->db->tableName( $tbl ), __METHOD__ );
+ }
+ }
+ } else {
+ foreach ( $this->tablesUsed as $tbl ) {
+ if ( $tbl == 'interwiki' || $tbl == 'user' ) {
+ continue;
+ }
+ $this->db->delete( $tbl, '*', __METHOD__ );
+ }
+ }
+ }
+ }
+
+ function __call( $func, $args ) {
+ static $compatibility = array(
+ 'assertInternalType' => 'assertType',
+ 'assertNotInternalType' => 'assertNotType',
+ 'assertInstanceOf' => 'assertType',
+ 'assertEmpty' => 'assertEmpty2',
+ );
+
+ if ( method_exists( $this->suite, $func ) ) {
+ return call_user_func_array( array( $this->suite, $func ), $args );
+ } elseif ( isset( $compatibility[$func] ) ) {
+ return call_user_func_array( array( $this, $compatibility[$func] ), $args );
+ } else {
+ throw new MWException( "Called non-existant $func method on "
+ . get_class( $this ) );
+ }
+ }
+
+ private function assertEmpty2( $value, $msg ) {
+ return $this->assertTrue( $value == '', $msg );
+ }
+
+ private static function unprefixTable( $tableName ) {
+ global $wgDBprefix;
+
+ return substr( $tableName, strlen( $wgDBprefix ) );
+ }
+
+ private static function isNotUnittest( $table ) {
+ return strpos( $table, 'unittest_' ) !== 0;
+ }
+
+ public static function listTables( $db ) {
+ global $wgDBprefix;
+
+ $tables = $db->listTables( $wgDBprefix, __METHOD__ );
+
+ if ( $db->getType() === 'mysql' ) {
+ # bug 43571: cannot clone VIEWs under MySQL
+ $views = $db->listViews( $wgDBprefix, __METHOD__ );
+ $tables = array_diff( $tables, $views );
+ }
+ $tables = array_map( array( __CLASS__, 'unprefixTable' ), $tables );
+
+ // Don't duplicate test tables from the previous fataled run
+ $tables = array_filter( $tables, array( __CLASS__, 'isNotUnittest' ) );
+
+ if ( $db->getType() == 'sqlite' ) {
+ $tables = array_flip( $tables );
+ // these are subtables of searchindex and don't need to be duped/dropped separately
+ unset( $tables['searchindex_content'] );
+ unset( $tables['searchindex_segdir'] );
+ unset( $tables['searchindex_segments'] );
+ $tables = array_flip( $tables );
+ }
+
+ return $tables;
+ }
+
+ protected function checkDbIsSupported() {
+ if ( !in_array( $this->db->getType(), $this->supportedDBs ) ) {
+ throw new MWException( $this->db->getType() . " is not currently supported for unit testing." );
+ }
+ }
+
+ public function getCliArg( $offset ) {
+
+ if ( isset( MediaWikiPHPUnitCommand::$additionalOptions[$offset] ) ) {
+ return MediaWikiPHPUnitCommand::$additionalOptions[$offset];
+ }
+ }
+
+ public function setCliArg( $offset, $value ) {
+
+ MediaWikiPHPUnitCommand::$additionalOptions[$offset] = $value;
+ }
+
+ /**
+ * Don't throw a warning if $function is deprecated and called later
+ *
+ * @param $function String
+ * @return null
+ */
+ function hideDeprecated( $function ) {
+ wfSuppressWarnings();
+ wfDeprecated( $function );
+ wfRestoreWarnings();
+ }
+
+ /**
+ * Asserts that the given database query yields the rows given by $expectedRows.
+ * The expected rows should be given as indexed (not associative) arrays, with
+ * the values given in the order of the columns in the $fields parameter.
+ * Note that the rows are sorted by the columns given in $fields.
+ *
+ * @since 1.20
+ *
+ * @param $table String|Array the table(s) to query
+ * @param $fields String|Array the columns to include in the result (and to sort by)
+ * @param $condition String|Array "where" condition(s)
+ * @param $expectedRows Array - an array of arrays giving the expected rows.
+ *
+ * @throws MWException if this test cases's needsDB() method doesn't return true.
+ * Test cases can use "@group Database" to enable database test support,
+ * or list the tables under testing in $this->tablesUsed, or override the
+ * needsDB() method.
+ */
+ protected function assertSelect( $table, $fields, $condition, array $expectedRows ) {
+ if ( !$this->needsDB() ) {
+ throw new MWException( 'When testing database state, the test cases\'s needDB()' .
+ ' method should return true. Use @group Database or $this->tablesUsed.' );
+ }
+
+ $db = wfGetDB( DB_SLAVE );
+
+ $res = $db->select( $table, $fields, $condition, wfGetCaller(), array( 'ORDER BY' => $fields ) );
+ $this->assertNotEmpty( $res, "query failed: " . $db->lastError() );
+
+ $i = 0;
+
+ foreach ( $expectedRows as $expected ) {
+ $r = $res->fetchRow();
+ self::stripStringKeys( $r );
+
+ $i += 1;
+ $this->assertNotEmpty( $r, "row #$i missing" );
+
+ $this->assertEquals( $expected, $r, "row #$i mismatches" );
+ }
+
+ $r = $res->fetchRow();
+ self::stripStringKeys( $r );
+
+ $this->assertFalse( $r, "found extra row (after #$i)" );
+ }
+
+ /**
+ * Utility method taking an array of elements and wrapping
+ * each element in it's own array. Useful for data providers
+ * that only return a single argument.
+ *
+ * @since 1.20
+ *
+ * @param array $elements
+ *
+ * @return array
+ */
+ protected function arrayWrap( array $elements ) {
+ return array_map(
+ function ( $element ) {
+ return array( $element );
+ },
+ $elements
+ );
+ }
+
+ /**
+ * Assert that two arrays are equal. By default this means that both arrays need to hold
+ * the same set of values. Using additional arguments, order and associated key can also
+ * be set as relevant.
+ *
+ * @since 1.20
+ *
+ * @param array $expected
+ * @param array $actual
+ * @param boolean $ordered If the order of the values should match
+ * @param boolean $named If the keys should match
+ */
+ protected function assertArrayEquals( array $expected, array $actual, $ordered = false, $named = false ) {
+ if ( !$ordered ) {
+ $this->objectAssociativeSort( $expected );
+ $this->objectAssociativeSort( $actual );
+ }
+
+ if ( !$named ) {
+ $expected = array_values( $expected );
+ $actual = array_values( $actual );
+ }
+
+ call_user_func_array(
+ array( $this, 'assertEquals' ),
+ array_merge( array( $expected, $actual ), array_slice( func_get_args(), 4 ) )
+ );
+ }
+
+ /**
+ * Put each HTML element on its own line and then equals() the results
+ *
+ * Use for nicely formatting of PHPUnit diff output when comparing very
+ * simple HTML
+ *
+ * @since 1.20
+ *
+ * @param String $expected HTML on oneline
+ * @param String $actual HTML on oneline
+ * @param String $msg Optional message
+ */
+ protected function assertHTMLEquals( $expected, $actual, $msg = '' ) {
+ $expected = str_replace( '>', ">\n", $expected );
+ $actual = str_replace( '>', ">\n", $actual );
+
+ $this->assertEquals( $expected, $actual, $msg );
+ }
+
+ /**
+ * Does an associative sort that works for objects.
+ *
+ * @since 1.20
+ *
+ * @param array $array
+ */
+ protected function objectAssociativeSort( array &$array ) {
+ uasort(
+ $array,
+ function ( $a, $b ) {
+ return serialize( $a ) > serialize( $b ) ? 1 : -1;
+ }
+ );
+ }
+
+ /**
+ * Utility function for eliminating all string keys from an array.
+ * Useful to turn a database result row as returned by fetchRow() into
+ * a pure indexed array.
+ *
+ * @since 1.20
+ *
+ * @param $r mixed the array to remove string keys from.
+ */
+ protected static function stripStringKeys( &$r ) {
+ if ( !is_array( $r ) ) {
+ return;
+ }
+
+ foreach ( $r as $k => $v ) {
+ if ( is_string( $k ) ) {
+ unset( $r[$k] );
+ }
+ }
+ }
+
+ /**
+ * Asserts that the provided variable is of the specified
+ * internal type or equals the $value argument. This is useful
+ * for testing return types of functions that return a certain
+ * type or *value* when not set or on error.
+ *
+ * @since 1.20
+ *
+ * @param string $type
+ * @param mixed $actual
+ * @param mixed $value
+ * @param string $message
+ */
+ protected function assertTypeOrValue( $type, $actual, $value = false, $message = '' ) {
+ if ( $actual === $value ) {
+ $this->assertTrue( true, $message );
+ } else {
+ $this->assertType( $type, $actual, $message );
+ }
+ }
+
+ /**
+ * Asserts the type of the provided value. This can be either
+ * in internal type such as boolean or integer, or a class or
+ * interface the value extends or implements.
+ *
+ * @since 1.20
+ *
+ * @param string $type
+ * @param mixed $actual
+ * @param string $message
+ */
+ protected function assertType( $type, $actual, $message = '' ) {
+ if ( class_exists( $type ) || interface_exists( $type ) ) {
+ $this->assertInstanceOf( $type, $actual, $message );
+ } else {
+ $this->assertInternalType( $type, $actual, $message );
+ }
+ }
+
+ /**
+ * Returns true if the given namespace defaults to Wikitext
+ * according to $wgNamespaceContentModels
+ *
+ * @param int $ns The namespace ID to check
+ *
+ * @return bool
+ * @since 1.21
+ */
+ protected function isWikitextNS( $ns ) {
+ global $wgNamespaceContentModels;
+
+ if ( isset( $wgNamespaceContentModels[$ns] ) ) {
+ return $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns the ID of a namespace that defaults to Wikitext.
+ * Throws an MWException if there is none.
+ *
+ * @return int the ID of the wikitext Namespace
+ * @since 1.21
+ */
+ protected function getDefaultWikitextNS() {
+ global $wgNamespaceContentModels;
+
+ static $wikitextNS = null; // this is not going to change
+ if ( $wikitextNS !== null ) {
+ return $wikitextNS;
+ }
+
+ // quickly short out on most common case:
+ if ( !isset( $wgNamespaceContentModels[NS_MAIN] ) ) {
+ return NS_MAIN;
+ }
+
+ // NOTE: prefer content namespaces
+ $namespaces = array_unique( array_merge(
+ MWNamespace::getContentNamespaces(),
+ array( NS_MAIN, NS_HELP, NS_PROJECT ), // prefer these
+ MWNamespace::getValidNamespaces()
+ ) );
+
+ $namespaces = array_diff( $namespaces, array(
+ NS_FILE, NS_CATEGORY, NS_MEDIAWIKI, NS_USER // don't mess with magic namespaces
+ ) );
+
+ $talk = array_filter( $namespaces, function ( $ns ) {
+ return MWNamespace::isTalk( $ns );
+ } );
+
+ // prefer non-talk pages
+ $namespaces = array_diff( $namespaces, $talk );
+ $namespaces = array_merge( $namespaces, $talk );
+
+ // check default content model of each namespace
+ foreach ( $namespaces as $ns ) {
+ if ( !isset( $wgNamespaceContentModels[$ns] ) ||
+ $wgNamespaceContentModels[$ns] === CONTENT_MODEL_WIKITEXT
+ ) {
+
+ $wikitextNS = $ns;
+
+ return $wikitextNS;
+ }
+ }
+
+ // give up
+ // @todo Inside a test, we could skip the test as incomplete.
+ // But frequently, this is used in fixture setup.
+ throw new MWException( "No namespace defaults to wikitext!" );
+ }
+
+ /**
+ * Check, if $wgDiff3 is set and ready to merge
+ * Will mark the calling test as skipped, if not ready
+ *
+ * @since 1.21
+ */
+ protected function checkHasDiff3() {
+ global $wgDiff3;
+
+ # This check may also protect against code injection in
+ # case of broken installations.
+ wfSuppressWarnings();
+ $haveDiff3 = $wgDiff3 && file_exists( $wgDiff3 );
+ wfRestoreWarnings();
+
+ if ( !$haveDiff3 ) {
+ $this->markTestSkipped( "Skip test, since diff3 is not configured" );
+ }
+ }
+
+ /**
+ * Check whether we have the 'gzip' commandline utility, will skip
+ * the test whenever "gzip -V" fails.
+ *
+ * Result is cached at the process level.
+ *
+ * @return bool
+ *
+ * @since 1.21
+ */
+ protected function checkHasGzip() {
+ static $haveGzip;
+
+ if ( $haveGzip === null ) {
+ $retval = null;
+ wfShellExec( 'gzip -V', $retval );
+ $haveGzip = ( $retval === 0 );
+ }
+
+ if ( !$haveGzip ) {
+ $this->markTestSkipped( "Skip test, requires the gzip utility in PATH" );
+ }
+
+ return $haveGzip;
+ }
+
+ /**
+ * Check if $extName is a loaded PHP extension, will skip the
+ * test whenever it is not loaded.
+ *
+ * @since 1.21
+ */
+ protected function checkPHPExtension( $extName ) {
+ $loaded = extension_loaded( $extName );
+ if ( !$loaded ) {
+ $this->markTestSkipped( "PHP extension '$extName' is not loaded, skipping." );
+ }
+
+ return $loaded;
+ }
+
+ /**
+ * Asserts that an exception of the specified type occurs when running
+ * the provided code.
+ *
+ * @since 1.21
+ * @deprecated since 1.22 Use setExpectedException
+ *
+ * @param callable $code
+ * @param string $expected
+ * @param string $message
+ */
+ protected function assertException( $code, $expected = 'Exception', $message = '' ) {
+ $pokemons = null;
+
+ try {
+ call_user_func( $code );
+ } catch ( Exception $pokemons ) {
+ // Gotta Catch 'Em All!
+ }
+
+ if ( $message === '' ) {
+ $message = 'An exception of type "' . $expected . '" should have been thrown';
+ }
+
+ $this->assertInstanceOf( $expected, $pokemons, $message );
+ }
+}
diff --git a/tests/phpunit/README b/tests/phpunit/README
new file mode 100644
index 00000000..0a32ba17
--- /dev/null
+++ b/tests/phpunit/README
@@ -0,0 +1,53 @@
+== MediaWiki PHPUnit Tests ==
+
+The unit tests for MediaWiki are implemented using the PHPUnit testing
+framework and require PHPUnit to run.
+
+
+=== WARNING ===
+
+Some of the unit tests are DESTRUCTIVE and WILL ALTER YOUR WIKI'S CONTENTS.
+
+DO NOT RUN THESE TESTS ON A PRODUCTION SYSTEM OR ON ANY SYSTEM WHERE YOU NEED
+TO RETAIN YOUR DATA.
+
+
+== Installation ==
+
+If PHPUnit is not installed, follow the installation instructions in the
+PHPUnit Manual at:
+
+ http://www.phpunit.de/manual/current/en/installation.html
+
+- or -
+
+On Unix-like operating systems, run:
+
+ make install
+
+
+== Running tests ==
+
+The tests are run from your operating system's command line.
+
+Ensure that you are in the tests/phpunit directory of your MediaWiki
+installation.
+
+
+On Unix-like operating systems, the tests runs are controlled with a makefile.
+Run command:
+
+ make help
+
+for a full list of options for running tests.
+
+
+On Windows-family operating systems, run the 'run-tests.bat' batch file.
+
+
+=== Writing tests ===
+
+A guide to writing unit tests for MediaWiki can be found at:
+
+ http://mediawiki.org/wiki/Unit_Testing
+
diff --git a/tests/phpunit/TODO b/tests/phpunit/TODO
new file mode 100644
index 00000000..b2fa7fb6
--- /dev/null
+++ b/tests/phpunit/TODO
@@ -0,0 +1,10 @@
+== Things To Do ==
+* Most of the tests are named poorly; naming should describe a use case in story-like language, not simply identify the
+unit under test. An example would be the difference between testCalculate and testAddingIntegersTogetherWorks.
+* Many of the tests make multiple assertions, and are thus not unitary tests. By using data-providers and more use-case
+oriented test selection nearly all of these cases can be easily resolved.
+* Some of the test files are either incorrectly named or in the wrong folder. Tests should be organized in a mirrored
+structure to the source they are testing, and named the same, with the exception of the word "Test" at the end.
+* Shared set-up code or base classes are present, but usually named improperly or appear to be poorly factored. Support
+code should share as much of the same naming as the code it's supporting, and test and test-case depenencies should be
+considered to resolve other shared needs.
diff --git a/tests/phpunit/bootstrap.php b/tests/phpunit/bootstrap.php
new file mode 100644
index 00000000..d929b79d
--- /dev/null
+++ b/tests/phpunit/bootstrap.php
@@ -0,0 +1,15 @@
+<?php
+/**
+ * Bootstrapping for MediaWiki PHPUnit tests
+ * This file is included by phpunit and is NOT in the global scope.
+ *
+ * @file
+ */
+
+if ( !defined( 'MW_PHPUNIT_TEST' ) ) {
+ echo <<<EOF
+You are running these tests directly from phpunit. You may not have all globals correctly set.
+Running phpunit.php instead is recommended.
+EOF;
+ require_once __DIR__ . "/phpunit.php";
+}
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..7e8f30ec
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.16.sql
@@ -0,0 +1,478 @@
+-- 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 /*_*/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..e02e3e14
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.17.sql
@@ -0,0 +1,511 @@
+-- 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 /*_*/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..8bfc28e2
--- /dev/null
+++ b/tests/phpunit/data/db/sqlite/tables-1.18.sql
@@ -0,0 +1,530 @@
+-- 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 /*_*/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/less/common/test.common.mixins.less b/tests/phpunit/data/less/common/test.common.mixins.less
new file mode 100644
index 00000000..2fbe9b79
--- /dev/null
+++ b/tests/phpunit/data/less/common/test.common.mixins.less
@@ -0,0 +1,5 @@
+.test-mixin (@value) {
+ color: @value;
+ border: @foo solid @Foo;
+ line-height: test-sum(@bar, 10, 20);
+}
diff --git a/tests/phpunit/data/less/module/dependency.less b/tests/phpunit/data/less/module/dependency.less
new file mode 100644
index 00000000..c7725a25
--- /dev/null
+++ b/tests/phpunit/data/less/module/dependency.less
@@ -0,0 +1,3 @@
+@import "test.common.mixins";
+
+@unitTestColor: green;
diff --git a/tests/phpunit/data/less/module/styles.css b/tests/phpunit/data/less/module/styles.css
new file mode 100644
index 00000000..b78780a9
--- /dev/null
+++ b/tests/phpunit/data/less/module/styles.css
@@ -0,0 +1,6 @@
+/* @noflip */
+.unit-tests {
+ color: green;
+ border: 2px solid #eeeeee;
+ line-height: 35;
+}
diff --git a/tests/phpunit/data/less/module/styles.less b/tests/phpunit/data/less/module/styles.less
new file mode 100644
index 00000000..ecac8392
--- /dev/null
+++ b/tests/phpunit/data/less/module/styles.less
@@ -0,0 +1,6 @@
+@import "dependency";
+
+/* @noflip */
+.unit-tests {
+ .test-mixin(@unitTestColor);
+}
diff --git a/tests/phpunit/data/media/1bit-png.png b/tests/phpunit/data/media/1bit-png.png
new file mode 100644
index 00000000..254e403a
--- /dev/null
+++ b/tests/phpunit/data/media/1bit-png.png
Binary files differ
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
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-2layers.xcf
Binary files 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
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-Greyscale.xcf
Binary files 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
--- /dev/null
+++ b/tests/phpunit/data/media/80x60-RGB.xcf
Binary files differ
diff --git a/tests/phpunit/data/media/Animated_PNG_example_bouncing_beach_ball.png b/tests/phpunit/data/media/Animated_PNG_example_bouncing_beach_ball.png
new file mode 100644
index 00000000..c2f45d90
--- /dev/null
+++ b/tests/phpunit/data/media/Animated_PNG_example_bouncing_beach_ball.png
Binary files differ
diff --git a/tests/phpunit/data/media/Gtk-media-play-ltr.svg b/tests/phpunit/data/media/Gtk-media-play-ltr.svg
new file mode 100644
index 00000000..fc22338a
--- /dev/null
+++ b/tests/phpunit/data/media/Gtk-media-play-ltr.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd" [
+ <!ATTLIST svg
+ xmlns:xlink CDATA #FIXED "http://www.w3.org/1999/xlink">
+]>
+<!-- Created with Sodipodi ("http://www.sodipodi.com/") -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1" x="0.00000000" y="0.00000000" width="60.0000000" height="60.0000000" viewBox="0 0 256 256" id="svg548">
+ <defs id="defs572"/>
+ <g style="font-size:12;stroke:#000000;" id="Layer_1">
+ <path d="M 256 256 L 0 256 L 0 0 L 256 0 L 256 256 z " style="fill:none;stroke:none;" id="path550"/>
+ </g>
+ <g style="font-size:12;stroke:#000000;" id="Layer_2">
+ <path d="M 35.159 8.29 C 32.18 10.01 30.329 13.216 30.329 16.656 L 30.329 245.539 C 30.329 248.978 32.179 252.184 35.158 253.902 C 38.138 255.623 41.839 255.623 44.817 253.904 L 243.037 139.463 C 246.016 137.742 247.867 134.537 247.867 131.098 C 247.867 127.658 246.016 124.452 243.037 122.731 L 44.818 8.29 C 41.839 6.57 38.138 6.57 35.159 8.29 z " style="opacity:0.2;stroke:none;" id="path552"/>
+ <path d="M 27.314 2.29 C 24.335 4.01 22.484 7.216 22.484 10.656 L 22.484 239.538 C 22.484 242.977 24.335 246.184 27.313 247.903 C 30.293 249.623 33.994 249.623 36.973 247.905 L 235.193 133.464 C 238.172 131.742 240.023 128.536 240.023 125.098 C 240.023 121.658 238.172 118.452 235.193 116.732 L 36.975 2.29 C 33.996 0.57 30.294 0.57 27.314 2.29 z " style="fill:#003399;stroke:none;" id="path553"/>
+ <path d="M 29.247 5.636 C 27.454 6.672 26.349 8.585 26.349 10.656 L 26.349 239.538 C 26.349 241.608 27.453 243.521 29.247 244.558 C 31.04 245.592 33.249 245.592 35.042 244.558 L 233.261 130.117 C 235.054 129.081 236.159 127.169 236.159 125.098 C 236.159 123.027 235.054 121.114 233.261 120.078 L 35.042 5.636 C 33.25 4.601 31.041 4.601 29.247 5.636 z " style="fill:#003399;stroke:none;" id="path554"/>
+ <path d="M 32.145 10.656 L 230.364 125.097 L 32.145 239.538 L 32.145 10.656 z " style="fill:#3399ff;stroke:none;" id="path555"/>
+ <linearGradient x1="109.971703" y1="8.70849991" x2="109.971703" y2="107.238800" id="XMLID_1_" gradientUnits="userSpaceOnUse" spreadMethod="pad">
+ <stop style="stop-color:#ffffff;stop-opacity:1;" offset="0.00000000" id="stop557"/>
+ <stop style="stop-color:#3399ff;stop-opacity:1;" offset="1.00000000" id="stop558"/>
+ <a:midPointStop offset="0" style="stop-color:#FFFFFF" id="midPointStop559"/>
+ <a:midPointStop offset="0.5" style="stop-color:#FFFFFF" id="midPointStop560"/>
+ <a:midPointStop offset="1" style="stop-color:#3399FF" id="midPointStop561"/>
+ </linearGradient>
+ <path d="M 32.145 141.057 C 36.775 141.258 41.456 141.368 46.183 141.368 C 105.41 141.368 157.526 125.124 187.799 100.524 L 32.145 10.656 L 32.145 141.057 z " style="fill:url(#XMLID_1_);stroke:none;" id="path562"/>
+ <linearGradient x1="109.972198" y1="264.875000" x2="109.972198" y2="145.249298" id="XMLID_2_" gradientUnits="userSpaceOnUse" spreadMethod="pad">
+ <stop style="stop-color:#ccffff;stop-opacity:1;" offset="0.00000000" id="stop564"/>
+ <stop style="stop-color:#3399ff;stop-opacity:1;" offset="1.00000000" id="stop565"/>
+ <a:midPointStop offset="0" style="stop-color:#CCFFFF" id="midPointStop566"/>
+ <a:midPointStop offset="0.5" style="stop-color:#CCFFFF" id="midPointStop567"/>
+ <a:midPointStop offset="1" style="stop-color:#3399FF" id="midPointStop568"/>
+ </linearGradient>
+ <path d="M 32.145 108.517 C 36.775 108.315 41.456 108.206 46.183 108.206 C 105.41 108.206 157.526 124.451 187.799 149.05 L 32.145 238.916 L 32.145 108.517 z " style="fill:url(#XMLID_2_);stroke:none;" id="path569"/>
+ <path d="M 37.145 19.316 C 36.526 19.673 36.145 20.334 36.145 21.048 L 36.145 162.69 C 36.145 163.768 36.999 198.629 38.077 198.667 C 39.154 198.703 40.41 48.03 48.066 40.375 C 55.722 32.72 212.492 122.951 213 122 C 213.507 121.049 186.703 104.509 185.77 103.97 L 39.145 19.316 C 38.526 18.959 37.764 18.959 37.145 19.316 z " style="opacity:0.5;fill:#ffffff;stroke:none;" id="path570"/>
+ </g>
+</svg> \ No newline at end of file
diff --git a/tests/phpunit/data/media/Png-native-test.png b/tests/phpunit/data/media/Png-native-test.png
new file mode 100644
index 00000000..a0b81ca9
--- /dev/null
+++ b/tests/phpunit/data/media/Png-native-test.png
Binary files differ
diff --git a/tests/phpunit/data/media/QA_icon.svg b/tests/phpunit/data/media/QA_icon.svg
new file mode 100644
index 00000000..6b5d86e4
--- /dev/null
+++ b/tests/phpunit/data/media/QA_icon.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg:svg xmlns:svg="http://www.w3.org/2000/svg" version="1.0" width="60" height="60" viewBox="0 0 128 128" id="svg548">
+ <svg:defs id="defs601">
+ <svg:linearGradient id="linearGradient2802">
+ <svg:stop style="stop-color:#1d12aa;stop-opacity:1" offset="0" id="stop2804"/>
+ <svg:stop style="stop-color:#8b12aa;stop-opacity:0" offset="1" id="stop2806"/>
+ </svg:linearGradient>
+ <svg:linearGradient id="linearGradient2812">
+ <svg:stop style="stop-color:#1d25aa;stop-opacity:1" offset="0" id="stop2814"/>
+ <svg:stop style="stop-color:#8b12aa;stop-opacity:0" offset="1" id="stop2816"/>
+ </svg:linearGradient>
+ <svg:marker refX="0" refY="0" orient="auto" style="overflow:visible" id="Arrow1Lstart">
+ <svg:path d="M 0,0 L 5,-5 L -12.5,0 L 5,5 L 0,0 z " transform="scale(0.8)" style="fill-rule:evenodd;stroke:#000000;stroke-width:1pt;marker-start:none" id="path2991"/>
+ </svg:marker>
+ <svg:linearGradient id="linearGradient4766">
+ <svg:stop style="stop-color:#0447ff;stop-opacity:1" offset="0" id="stop4768"/>
+ <svg:stop style="stop-color:#000000;stop-opacity:0" offset="1" id="stop4770"/>
+ </svg:linearGradient>
+ <svg:linearGradient x1="55.4272" y1="102.1953" x2="55.4272" y2="-7.1773" id="XMLID_1_" gradientUnits="userSpaceOnUse" gradientTransform="translate(0, -0.496766)" spreadMethod="pad">
+ <svg:stop style="stop-color:#7c74ff;stop-opacity:1" offset="0" id="stop556"/>
+ <svg:stop style="stop-color:#b3caff;stop-opacity:1" offset="0.41010001" id="stop557"/>
+ <svg:stop style="stop-color:#dfeaff;stop-opacity:1" offset="0.8258" id="stop558"/>
+ <svg:stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop559"/>
+ <midPointStop offset="0" style="stop-color:#7C74FF" id="midPointStop560"/>
+ <midPointStop offset="0.5" style="stop-color:#7C74FF" id="midPointStop561"/>
+ <midPointStop offset="0.4101" style="stop-color:#B3CAFF" id="midPointStop562"/>
+ <midPointStop offset="0.5" style="stop-color:#B3CAFF" id="midPointStop563"/>
+ <midPointStop offset="0.8258" style="stop-color:#DFEAFF" id="midPointStop564"/>
+ <midPointStop offset="0.5" style="stop-color:#DFEAFF" id="midPointStop565"/>
+ <midPointStop offset="1" style="stop-color:#FFFFFF" id="midPointStop566"/>
+ </svg:linearGradient>
+ <svg:linearGradient x1="54.7607" y1="7.2758999" x2="54.7607" y2="57.487301" id="XMLID_2_" gradientUnits="userSpaceOnUse" spreadMethod="pad">
+ <svg:stop style="stop-color:#ffffff;stop-opacity:1" offset="0" id="stop569"/>
+ <svg:stop style="stop-color:#b3caff;stop-opacity:1" offset="1" id="stop570"/>
+ <midPointStop offset="0" style="stop-color:#FFFFFF" id="midPointStop571"/>
+ <midPointStop offset="0.5" style="stop-color:#FFFFFF" id="midPointStop572"/>
+ <midPointStop offset="1" style="stop-color:#B3CAFF" id="midPointStop573"/>
+ </svg:linearGradient>
+ <svg:linearGradient x1="83.637703" y1="119.3457" x2="83.637703" y2="42.033901" id="XMLID_3_" gradientUnits="userSpaceOnUse" spreadMethod="pad">
+ <svg:stop style="stop-color:#006dff;stop-opacity:1" offset="0" id="stop577"/>
+ <svg:stop style="stop-color:#94caff;stop-opacity:1" offset="0.41010001" id="stop578"/>
+ <svg:stop style="stop-color:#dcf0ff;stop-opacity:1" offset="0.8258" id="stop579"/>
+ <svg:stop style="stop-color:#ffffff;stop-opacity:1" offset="1" id="stop580"/>
+ <midPointStop offset="0" style="stop-color:#006DFF" id="midPointStop581"/>
+ <midPointStop offset="0.5" style="stop-color:#006DFF" id="midPointStop582"/>
+ <midPointStop offset="0.4101" style="stop-color:#94CAFF" id="midPointStop583"/>
+ <midPointStop offset="0.5" style="stop-color:#94CAFF" id="midPointStop584"/>
+ <midPointStop offset="0.8258" style="stop-color:#DCF0FF" id="midPointStop585"/>
+ <midPointStop offset="0.5" style="stop-color:#DCF0FF" id="midPointStop586"/>
+ <midPointStop offset="1" style="stop-color:#FFFFFF" id="midPointStop587"/>
+ </svg:linearGradient>
+ <svg:linearGradient x1="265.11331" y1="52.250999" x2="265.11331" y2="87.743599" id="XMLID_4_" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1, 0, 0, 1, 349, 0)" spreadMethod="pad">
+ <svg:stop style="stop-color:#ffffff;stop-opacity:1" offset="0" id="stop590"/>
+ <svg:stop style="stop-color:#94caff;stop-opacity:1" offset="1" id="stop591"/>
+ <midPointStop offset="0" style="stop-color:#FFFFFF" id="midPointStop592"/>
+ <midPointStop offset="0.5" style="stop-color:#FFFFFF" id="midPointStop593"/>
+ <midPointStop offset="1" style="stop-color:#94CAFF" id="midPointStop594"/>
+ </svg:linearGradient>
+ </svg:defs>
+ <svg:g style="font-size:12px;stroke:#000000" id="Layer_2">
+ <svg:path d="M 128,128 L 0,128 L 0,0 L 128,0 L 128,128 z " style="fill:none;stroke:none" id="path550"/>
+ </svg:g>
+ <svg:g style="font-size:12px;stroke:#000000" id="Layer_1"/>
+ <svg:path d="M 9.041,92.189 C 9.041,92.189 21.955,85.393 30.11,67.382 L 52.198,76.897 C 52.198,76.897 46.422,92.189 9.041,92.189 z " style="font-size:12px;fill:#00008d;stroke:none" id="path553"/>
+ <svg:path d="M 1.905,49.712 C 1.905,70.733 25.867,87.773 55.427,87.773 C 84.987,87.773 108.949,70.733 108.949,49.712 C 108.949,28.692 84.987,11.651 55.427,11.651 C 25.867,11.651 1.905,28.692 1.905,49.712 z " style="font-size:12px;fill:#00008d;stroke:none" id="path554"/>
+ <svg:path d="M 55.427,13.193234 C 27.039,13.193234 3.943,29.352234 3.943,49.214234 C 3.943,61.333234 12.55,72.067234 25.703,78.598234 C 22.202,83.521234 18.6,87.075234 15.722,89.464234 C 27.71,88.800234 35.664,86.388234 40.883,83.762234 C 45.498,84.716234 50.377,85.236234 55.427,85.236234 C 83.815,85.236234 106.91,69.077234 106.91,49.215234 C 106.91,29.353234 83.815,13.193234 55.427,13.193234 z " style="font-size:12px;fill:url(#XMLID_1_);stroke:none" id="path567"/>
+ <svg:path d="M 12.999,35.282 C 30.044,44.81 49.474,47.149 69.356,41.962 C 73.46,40.821 77.627,39.436 81.656,38.096 C 86.51,36.482 91.504,34.846 96.524,33.573 C 88.559,23.302 72.888,16.748 55.428,16.748 C 37.091,16.749 20.396,24.128 12.999,35.282 z " style="font-size:12px;fill:url(#XMLID_2_);stroke:none" id="path574"/>
+ <svg:text x="32.487015" y="68.006958" style="font-size:48px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Microsoft Sans Serif" id="text2303" xml:space="preserve"><svg:tspan x="32.487015" y="68.006958" style="font-size:64px;font-style:normal;font-weight:bold;fill:#000000;fill-opacity:1;font-family:sans" id="tspan2305">?</svg:tspan></svg:text>
+ <svg:path d="M 42.401,82.248 C 42.401,98.96 60.9,112.557 83.638,112.557 C 86.794,112.557 90.053,112.231 93.329,111.651 C 98.255,113.817 104.317,115.142 111.437,115.538 L 126.095,116.35 L 114.798,106.972 C 114.067,106.367 113.056,105.441 111.922,104.237 C 120.165,98.531 124.875,90.66 124.875,82.25 C 124.875,65.538 106.376,51.942 83.637,51.942 C 60.9,51.94 42.401,65.536 42.401,82.248 z " style="font-size:12px;fill:#0032a4;stroke:none" id="path575"/>
+ <svg:path d="M 44.823,82.248 C 44.823,97.624 62.236,110.133 83.637,110.133 C 87.009,110.133 90.357,109.784 93.616,109.163 C 98.327,111.368 104.334,112.717 111.571,113.118 L 118.9,113.524 L 113.251,108.835 C 111.96,107.763 110.162,106.071 108.268,103.754 C 117.176,98.487 122.454,90.629 122.454,82.248 C 122.454,66.871 105.042,54.363 83.639,54.363 C 62.236,54.363 44.823,66.871 44.823,82.248 z " style="font-size:12px;fill:url(#XMLID_3_);stroke:none" id="path588"/>
+ <svg:path d="M 83.638,57.505 C 98.257,57.505 110.759,63.777 115.655,72.576 C 102.935,80.147 88.183,82.013 73.429,78.165 C 66.228,76.163 59.247,73.276 52.12,71.719 C 57.332,63.374 69.498,57.505 83.638,57.505 z " style="font-size:12px;fill:url(#XMLID_4_);stroke:none" id="path595"/>
+ <svg:g transform="matrix(1.38561, 0, 0, 1.38561, -32.2514, -30.5491)" id="g4248">
+ <svg:path d="M 103.21356 24.205935 A 24.311146 23.627199 0 1 1 54.591267,24.205935 A 24.311146 23.627199 0 1 1 103.21356 24.205935 z" transform="matrix(0.148134, 0, 0, 0.152972, 71.9504, 64.0705)" style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:6.64218044;stroke-linecap:square;marker-start:none;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" id="path3881"/>
+ <svg:path d="M 87.539971,77.627004 C 87.539971,78.31674 87.591107,91.916423 87.60756,94.355578 C 87.61771,95.860439 89.879004,95.050778 90.026509,95.980703 C 90.2785,97.569343 86.888685,97.025111 86.718511,97.01762 C 85.743882,96.974724 82.425764,97.036144 81.376943,97.036144 C 81.101002,97.036144 77.578516,97.69007 77.314172,96.309196 C 77.071189,95.039902 79.49446,95.29146 79.833236,94.380195 C 81.070282,91.052684 81.154686,84.029315 80.322646,79.891188 C 79.902772,77.802954 76.928763,78.363984 77.263297,76.859643 C 77.479369,75.888015 78.579837,75.778912 79.35102,75.513942 C 81.049574,74.930337 83.123826,75.068206 84.579101,74.012707 C 86.187481,72.846162 87.539971,75.631913 87.539971,77.627004 z " style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.99986994;stroke-linecap:square;marker-start:none;stroke-miterlimit:4;stroke-dashoffset:0;stroke-opacity:1" id="path4774"/>
+ </svg:g>
+</svg:svg> \ No newline at end of file
diff --git a/tests/phpunit/data/media/README b/tests/phpunit/data/media/README
new file mode 100644
index 00000000..fe3bc682
--- /dev/null
+++ b/tests/phpunit/data/media/README
@@ -0,0 +1,38 @@
+This directory contains media files for use with the
+tests in includes/media directory.
+
+Image credits:
+
+QA_icon.svg:
+http://es.wikipedia.org/wiki/Archivo:QA_icon.svg
+GNU Lesser General Public License
+~~helix84 (16.4.2007), Philverney (6.12.2005) David Vignoni
+
+Gtk-media-play-ltr.svg
+http://commons.wikimedia.org/wiki/File:Gtk-media-play-ltr.svg
+GNU Lesser General Public License
+http://ftp.gnome.org/pub/GNOME/sources/gnome-themes-extras/0.9/gnome-themes-extras-0.9.0.tar.gz
+David Vignoni
+
+US_states_by_total_state_tax_revenue.svg
+http://commons.wikimedia.org/wiki/File:US_states_by_total_state_tax_revenue.svg
+CC-BY 3.0
+TastyCakes on English Wikipedia
+
+greyscale-na-png.png, rgb-png.png, Xmp-exif-multilingual_test.jpg
+greyscale-png.png, 1bit-png.png, Png-native-test.png, rgb-na-png.png,
+test.tiff, test.jpg, jpeg-comment-multiple.jpg, jpeg-comment-utf.jpg,
+jpeg-comment-iso8859-1.jpg, jpeg-comment-binary.jpg, jpeg-xmp-psir.jpg,
+jpeg-xmp-alt.jpg, animated.gif, exif-user-comment.jpg, animated-xmp.gif,
+iptc-timetest-invalid.jpg, jpeg-iptc-bad-hash.jpg, iptc-timetest.jpg,
+xmp.png, nonanimated.gif, exif-gps.jpg, jpeg-xmp-psir.xmp, jpeg-iptc-good-hash.jpg,
+jpeg-padding-even.jpg, jpeg-padding-odd.jpg
+Are all by Bawolff. I don't think they contain enough originality to
+claim copyright, but on the off chance they do, feel free to use them
+however you feel fit, without restriction.
+
+Animated_PNG_example_bouncing_beach_ball.png
+http://commons.wikimedia.org/wiki/File:Animated_PNG_example_bouncing_beach_ball.png (originally http://www.treebuilder.de/default.asp?file=89031.xml )
+Public Domain
+Holger Will
+
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/US_states_by_total_state_tax_revenue.svg b/tests/phpunit/data/media/US_states_by_total_state_tax_revenue.svg
new file mode 100644
index 00000000..9afea859
--- /dev/null
+++ b/tests/phpunit/data/media/US_states_by_total_state_tax_revenue.svg
@@ -0,0 +1,248 @@
+<ns0:svg height="592.78998" id="svg2275" version="1.0" width="958.69" ns1:docbase="C:\Users\Adam\Desktop" ns1:docname="Blank_US_Map_with_borders.svg" ns1:version="0.32" ns2:output_extension="org.inkscape.output.svg.inkscape" ns2:version="0.46" xmlns:ns0="http://www.w3.org/2000/svg" xmlns:ns1="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:ns2="http://www.inkscape.org/namespaces/inkscape">
+ <ns0:metadata id="metadata2625">
+ <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <ns4:Work rdf:about="" xmlns:ns4="http://creativecommons.org/ns#">
+ <ns5:format xmlns:ns5="http://purl.org/dc/elements/1.1/">image/svg+xml</ns5:format>
+ <ns5:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" xmlns:ns5="http://purl.org/dc/elements/1.1/" />
+ </ns4:Work>
+ </rdf:RDF>
+ </ns0:metadata>
+ <ns0:defs id="defs2623">
+ <ns2:perspective id="perspective226" ns1:type="inkscape:persp3d" ns2:persp3d-origin="479.345 : 197.59666 : 1" ns2:vp_x="0 : 296.39499 : 1" ns2:vp_y="0 : 1000 : 0" ns2:vp_z="958.69 : 296.39499 : 1" />
+ </ns0:defs>
+ <ns1:namedview bordercolor="#666666" borderopacity="1.0" gridtolerance="10.0" guidetolerance="10.0" id="base" objecttolerance="10.0" pagecolor="#ffffff" showgrid="false" ns2:current-layer="svg2275" ns2:cx="479.345" ns2:cy="299.1307" ns2:pageopacity="0.0" ns2:pageshadow="2" ns2:window-height="820" ns2:window-width="1440" ns2:window-x="-8" ns2:window-y="-8" ns2:zoom="0.99941554" />
+ <ns0:path d="M 798.49579,591.98217 L 799.73403,593.07468 L 802.54072,590.88965 L 807.98899,586.51962 L 811.78627,582.48573 L 814.3453,575.59451 L 815.3359,573.82969 L 815.501,570.30004 L 814.75805,570.80427 L 813.76746,573.74564 L 812.28156,578.53588 L 808.97958,583.99844 L 804.52191,588.36847 L 801.05483,590.38542 L 798.49579,591.98217 z M 784.71002,597.19259 L 787.18651,596.52028 L 788.5073,596.26817 L 789.99319,593.83102 L 792.38713,592.15024 L 793.70792,592.65448 L 795.44146,592.99063 L 795.8542,594.08315 L 792.30458,595.34374 L 788.012,596.85644 L 785.61807,598.11703 L 784.71002,597.19259 z M 657.3254,482.07418 L 660.96585,481.47149 L 667.07449,479.28647 L 673.18314,478.78224 L 677.6408,478.10993 L 685.40042,479.95879 L 693.65535,483.99266 L 695.30633,485.50536 L 698.2781,486.6819 L 699.92909,488.69884 L 700.25929,491.55616 L 703.56126,490.21154 L 707.52362,490.21154 L 711.15578,488.1946 L 714.95305,484.49689 L 718.08992,484.66497 L 718.58522,483.48842 L 717.75972,482.47995 L 717.92482,480.46302 L 722.05228,479.62263 L 724.69386,479.62263 L 727.66563,481.13533 L 731.95819,482.64803 L 734.43467,486.51382 L 737.24134,487.52229 L 738.39703,491.05193 L 741.8641,492.73271 L 743.51508,495.42195 L 745.49627,496.09427 L 750.77942,497.43889 L 752.1002,500.63237 L 755.23708,504.49816 L 755.23708,514.41476 L 753.75119,519.28902 L 754.08139,522.14634 L 755.40217,527.18868 L 757.21826,531.39063 L 758.04375,530.8864 L 759.52964,526.18021 L 756.88806,525.17175 L 756.55786,524.49943 L 758.20885,523.82712 L 762.83161,524.83559 L 762.9967,526.51637 L 759.69473,532.23102 L 757.54845,534.75219 L 761.18062,538.61798 L 763.8222,541.81146 L 766.79397,547.35803 L 769.76574,551.3919 L 771.91202,556.60232 L 773.7281,556.93847 L 775.37909,554.75346 L 777.19517,555.93001 L 779.83675,560.13195 L 780.49714,563.82967 L 783.63401,568.36777 L 784.4595,567.02315 L 788.42187,567.3593 L 792.05403,569.7124 L 795.5211,575.09089 L 796.34659,578.62053 L 796.67679,581.64593 L 797.83248,582.6544 L 799.15327,583.15863 L 801.62975,582.15016 L 803.11563,580.46938 L 807.078,580.3013 L 810.21487,578.7886 L 813.02154,575.42704 L 812.52624,573.41011 L 812.19605,570.88894 L 812.85644,568.87201 L 812.52624,566.85507 L 815.00272,565.51045 L 815.33292,561.98081 L 814.67252,560.13195 L 814.17723,547.69419 L 812.85644,539.79453 L 808.23368,531.22255 L 804.60152,525.17175 L 801.95994,519.62517 L 798.98817,516.59977 L 796.0164,508.86819 L 796.84189,507.52356 L 797.99758,506.17894 L 796.34659,503.15354 L 792.21913,499.28775 L 787.26618,493.5731 L 783.46891,487.01806 L 778.02066,477.26954 L 774.21165,467.14054 L 771.56179,458.12552" id="FL_Gulf" style="fill:#cccccc;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 772.07835,458.61245 L 774.21165,467.14054 L 778.02066,477.26954 L 783.46891,487.01806 L 787.26618,493.5731 L 792.21913,499.28775 L 796.34659,503.15354 L 797.99758,506.17894 L 796.84189,507.52356 L 796.0164,508.86819 L 798.98817,516.59977 L 801.95994,519.62517 L 804.60152,525.17175 L 808.23368,531.22255 L 812.85644,539.79453 L 814.17723,547.69419 L 814.67252,560.13195 L 815.33292,561.98081 L 815.00272,565.51045 L 812.52624,566.85507 L 812.85644,568.87201 L 812.19605,570.88894 L 812.52624,573.41011 L 813.02154,575.42704 L 810.21487,578.7886 L 807.078,580.3013 L 803.11563,580.46938 L 801.62975,582.15016 L 799.15327,583.15863 L 797.83248,582.6544 L 796.67679,581.64593 L 796.34659,578.62053 L 795.5211,575.09089 L 792.05403,569.7124 L 788.42187,567.3593 L 784.4595,567.02315 L 783.63401,568.36777 L 780.49714,563.82967 L 779.83675,560.13195 L 777.19517,555.93001 L 775.37909,554.75346 L 773.7281,556.93847 L 771.91202,556.60232 L 769.76574,551.3919 L 766.79397,547.35803 L 763.8222,541.81146 L 761.18062,538.61798 L 757.54845,534.75219 L 759.69473,532.23102 L 762.9967,526.51637 L 762.83161,524.83559 L 758.20885,523.82712 L 756.55786,524.49943 L 756.88806,525.17175 L 759.52964,526.18021 L 758.04375,530.8864 L 757.21826,531.39063 L 755.40217,527.18868 L 754.08139,522.14634 L 753.75119,519.28902 L 755.23708,514.41476 L 755.23708,504.49816 L 752.1002,500.63237 L 750.77942,497.43889 L 745.49627,496.09427 L 743.51508,495.42195 L 741.8641,492.73271 L 738.39703,491.05193 L 737.24134,487.52229 L 734.43467,486.51382 L 731.95819,482.64803 L 727.66563,481.13533 L 724.69386,479.62263 L 722.05228,479.62263 L 717.92482,480.46302 L 717.75972,482.47995 L 718.58522,483.48842 L 718.08992,484.66497 L 714.95305,484.49689 L 711.15578,488.1946 L 707.52362,490.21154 L 703.56126,490.21154 L 700.25929,491.55616 L 699.92909,488.69884 L 698.2781,486.6819 L 695.30633,485.50536 L 693.65535,483.99266 L 685.40042,479.95879 L 677.6408,478.10993 L 673.18314,478.78224 L 667.07449,479.28647 L 660.96585,481.47149 L 657.41261,482.10878 L 657.16963,473.73948 L 654.52806,471.72255 L 652.71197,469.87369 L 653.04217,466.6802 L 663.44338,465.33558 L 689.52898,462.31017 L 696.46313,461.63786 L 702.73688,461.46977 L 705.37846,465.50365 L 706.86434,467.01635 L 714.95419,467.18443 L 726.00404,466.51212 L 747.97393,465.1675 L 753.53546,464.46636 L 758.21005,464.49519 L 758.37515,467.52059 L 761.01673,468.36098 L 761.34692,463.82287 L 759.69594,459.11668 L 760.85163,457.4359 L 766.79518,458.27629 L 772.07835,458.61245 z M 784.71002,597.19259 L 787.18651,596.52028 L 788.5073,596.26817 L 789.99319,593.83102 L 792.38713,592.15024 L 793.70792,592.65448 L 795.44146,592.99063 L 795.8542,594.08315 L 792.30458,595.34374 L 788.012,596.85644 L 785.61807,598.11703 L 784.71002,597.19259 z M 798.49579,591.98217 L 799.73403,593.07468 L 802.54072,590.88965 L 807.98899,586.51962 L 811.78627,582.48573 L 814.3453,575.59451 L 815.3359,573.82969 L 815.501,570.30004 L 814.75805,570.80427 L 813.76746,573.74564 L 812.28156,578.53588 L 808.97958,583.99844 L 804.52191,588.36847 L 801.05483,590.38542 L 798.49579,591.98217 z " id="FL" style="fill:#501616" />
+ <ns0:path d="M 777.85557,425.66962 L 776.04071,426.6776 L 773.39913,425.33297 L 772.73874,423.14795 L 771.41795,419.45024 L 769.10656,417.26521 L 766.46499,416.5929 L 764.814,411.55057 L 762.00732,405.33167 L 757.71476,403.31473 L 755.56847,401.29779 L 754.24768,398.60854 L 752.1014,396.5916 L 749.79002,395.24697 L 747.47863,392.22157 L 744.34176,389.86848 L 739.71899,388.01961 L 739.2237,386.50691 L 736.74722,383.48151 L 736.25192,381.9688 L 732.78485,376.5903 L 729.31778,376.75838 L 725.19031,374.2372 L 723.86952,372.89258 L 723.53932,371.04372 L 724.36481,369.02679 L 726.67619,368.01831 L 726.346,365.8333 L 732.61975,363.14405 L 741.86527,358.43786 L 749.29471,357.59747 L 766.13479,357.09323 L 768.44617,359.11017 L 770.09715,362.47174 L 774.55482,361.9675 L 787.43252,360.45479 L 790.4043,361.29519 L 803.282,369.19486 L 813.60504,377.63896 L 808.06859,383.31398 L 805.42701,389.70094 L 804.93171,396.25598 L 803.28073,397.09637 L 802.12504,399.95369 L 799.64856,400.62601 L 797.50228,404.32372 L 794.69561,407.18104 L 792.38423,410.71068 L 790.73325,411.55107 L 787.10108,415.08071 L 784.12931,415.24879 L 785.1199,418.61034 L 780.00185,424.32499 L 777.85557,425.66962 z " id="SC" style="fill:#e9afaf" />
+ <ns0:path d="M 704.71806,368.52255 L 699.7651,369.36294 L 691.17997,370.53949 L 682.42974,371.46391 L 682.42974,373.73297 L 682.59484,375.91799 L 683.25523,379.44763 L 686.72231,387.68346 L 689.19879,397.93623 L 690.68467,404.3232 L 692.33566,409.36554 L 693.82155,416.5929 L 695.96784,423.14795 L 698.60941,426.6776 L 699.10471,430.20724 L 701.0859,431.04763 L 701.251,433.23265 L 699.4349,438.27499 L 698.93961,441.63656 L 698.77451,443.65349 L 700.4255,448.19161 L 700.7557,453.73818 L 699.9302,456.25936 L 700.5906,457.09975 L 702.07649,457.94014 L 702.73688,461.46977 L 705.37846,465.50365 L 706.86434,467.01635 L 714.95419,467.18443 L 726.00404,466.51212 L 747.97393,465.1675 L 753.53546,464.46636 L 758.21005,464.49519 L 758.37515,467.52059 L 761.01673,468.36098 L 761.34692,463.82287 L 759.69594,459.11668 L 760.85163,457.4359 L 766.79518,458.27629 L 771.87844,458.60669 L 771.08653,452.05785 L 773.39791,441.63702 L 774.88379,437.26699 L 774.3885,434.57775 L 778.51596,427.3504 L 777.90454,425.66937 L 776.04071,426.6776 L 773.39913,425.33297 L 772.73874,423.14795 L 771.41795,419.45024 L 769.10656,417.26521 L 766.46499,416.5929 L 764.814,411.55057 L 762.00732,405.33167 L 757.71476,403.31473 L 755.56847,401.29779 L 754.24768,398.60854 L 752.1014,396.5916 L 749.79002,395.24697 L 747.47863,392.22157 L 744.34176,389.86848 L 739.71899,388.01961 L 739.2237,386.50691 L 736.74722,383.48151 L 736.25192,381.9688 L 732.78485,376.5903 L 729.31778,376.75838 L 725.19031,374.2372 L 723.86952,372.89258 L 723.53932,371.04372 L 724.36481,369.02679 L 726.67619,368.01831 L 726.51109,365.64481 L 724.69501,366.16945 L 718.75146,367.17792 L 711.65221,368.01831 L 704.71806,368.52255 z " id="GA" style="fill:#d35f5f" />
+ <ns0:path d="M 639.33795,481.63956 L 637.68799,465.83981 L 634.88131,446.34274 L 635.04641,431.71994 L 635.8719,399.44893 L 635.7068,382.13688 L 635.87539,375.46299 L 643.79664,375.07759 L 672.19362,372.38834 L 682.58068,371.46391 L 682.42974,373.73297 L 682.59484,375.91799 L 683.25523,379.44763 L 686.72231,387.68346 L 689.19879,397.93623 L 690.68467,404.3232 L 692.33566,409.36554 L 693.82155,416.5929 L 695.96784,423.14795 L 698.60941,426.6776 L 699.10471,430.20724 L 701.0859,431.04763 L 701.251,433.23265 L 699.4349,438.27499 L 698.93961,441.63656 L 698.77451,443.65349 L 700.4255,448.19161 L 700.7557,453.73818 L 699.9302,456.25936 L 700.5906,457.09975 L 702.07649,457.94014 L 702.90198,461.63786 L 696.46313,461.63786 L 689.52898,462.31017 L 663.44338,465.33558 L 653.04217,466.6802 L 652.71197,469.87369 L 654.52806,471.72255 L 657.16963,473.73948 L 657.76284,481.98993 L 651.05994,484.66497 L 648.25327,484.32881 L 651.05994,482.31188 L 651.05994,481.30341 L 647.92307,475.08453 L 645.61169,474.41221 L 644.12581,478.95032 L 642.80502,481.80764 L 642.14462,481.63956 L 639.33795,481.63956 z " id="AL" style="fill:#e9afaf" />
+ <ns0:path d="M 850.23842,306.65958 L 851.98478,311.54471 L 855.61694,318.26782 L 858.09342,320.78899 L 858.75382,323.14208 L 856.27734,323.31016 L 857.10283,323.98247 L 856.77263,328.3525 L 854.13106,329.69712 L 853.47066,331.88214 L 852.14988,334.90754 L 848.35261,336.58832 L 845.87614,336.25216 L 844.39025,336.08408 L 842.73926,334.73946 L 843.06946,336.08408 L 843.06946,337.09255 L 845.05064,337.09255 L 845.87614,338.43717 L 843.89495,344.99221 L 848.18751,344.99221 L 848.84791,346.67299 L 851.15929,344.3199 L 852.48007,343.81567 L 850.49889,347.51338 L 847.36202,352.55572 L 846.04123,352.55572 L 844.88554,352.05149 L 842.07887,352.7238 L 836.79572,355.24497 L 830.19178,360.79154 L 826.72471,365.6658 L 824.74353,372.38892 L 824.24824,374.91008 L 819.46038,375.41432 L 813.43993,377.723 L 803.282,369.19486 L 790.4043,361.29519 L 787.43252,360.45479 L 774.55482,361.9675 L 770.09715,362.47174 L 768.44617,359.11017 L 766.13479,357.09323 L 749.29471,357.59747 L 741.86527,358.43786 L 732.61975,363.14405 L 726.346,365.8333 L 724.69501,366.16945 L 718.75146,367.17792 L 711.65221,368.01831 L 704.71806,368.52255 L 705.04826,363.4802 L 706.86434,361.9675 L 709.67103,361.29519 L 710.33142,357.42939 L 714.62399,354.57206 L 718.58636,353.05935 L 722.87893,349.36164 L 727.33659,347.17662 L 727.99698,343.98313 L 731.95935,339.94926 L 732.61975,339.78119 C 732.61975,339.78119 732.61975,340.95773 733.44524,340.95773 C 734.27073,340.95773 735.42643,341.29389 735.42643,341.29389 L 737.73781,337.59616 L 739.88409,336.92385 L 742.19547,337.26001 L 743.84646,333.56229 L 746.81824,330.87303 L 747.31353,328.68802 L 747.31353,324.57011 L 751.9363,325.32646 L 759.22415,323.98183 L 775.38031,321.96489 L 792.88078,319.27565 L 813.92151,315.35219 L 833.49506,311.37597 L 845.21707,308.35056 L 850.23842,306.65958 z M 854.21672,340.95692 L 856.85831,338.3517 L 860.07773,335.66244 L 861.64617,334.99013 L 861.81127,332.88915 L 861.15088,326.50217 L 859.66499,324.06503 L 859.00459,322.13213 L 859.74753,321.88001 L 862.55422,327.59468 L 862.96697,332.21684 L 862.80187,335.74649 L 859.33479,337.34323 L 856.44555,339.86441 L 855.28987,341.125 L 854.21672,340.95692 z " id="NC" style="fill:#c83737" />
+ <ns0:path d="M 712.3126,329.69649 L 659.31592,334.90691 L 643.2212,336.75577 L 638.50172,337.28883 L 634.55111,337.26001 L 634.55111,341.29389 L 625.96598,341.79812 L 618.86673,342.47043 L 607.53473,342.52544 L 607.26436,348.59252 L 605.08072,355.11718 L 604.06449,358.25292 L 602.68706,362.80789 L 602.35687,365.49714 L 598.22939,367.85023 L 599.71528,371.54796 L 598.72469,376.08606 L 597.15628,377.85089 L 605.49374,377.76685 L 630.09345,375.74991 L 635.54175,375.58184 L 643.79664,375.07759 L 672.19362,372.38834 L 682.58068,371.54796 L 691.17997,370.53949 L 699.7651,369.36294 L 704.71806,368.52255 L 705.04826,363.4802 L 706.86434,361.9675 L 709.67103,361.29519 L 710.33142,357.42939 L 714.62399,354.57206 L 718.58636,353.05935 L 722.87893,349.36164 L 727.33659,347.17662 L 727.99698,343.98313 L 731.95935,339.94926 L 732.61975,339.78119 C 732.61975,339.78119 732.61975,340.95773 733.44524,340.95773 C 734.27073,340.95773 735.42643,341.29389 735.42643,341.29389 L 737.73781,337.59616 L 739.88409,336.92385 L 742.19547,337.26001 L 743.84646,333.56229 L 746.81824,330.87303 L 747.31353,328.68802 L 747.49366,324.59981 L 745.16725,324.65414 L 742.69078,326.67109 L 734.60093,326.83916 L 722.3505,328.8153 L 712.3126,329.69649 z " id="TN" style="fill:#de8787" />
+ <ns0:path d="M 893.09433,183.30123 L 892.6011,178.92994 L 891.77561,174.39182 L 890.04208,168.25697 L 895.90308,166.66023 L 897.55407,167.83677 L 901.02115,172.37489 L 903.99187,176.99768 L 901.01902,178.59507 L 899.69824,178.42699 L 898.54255,180.27585 L 896.06607,182.29279 L 893.09433,183.30123 z " id="RI" style="fill:#f4d7d7" />
+ <ns0:path d="M 893.58963,183.30123 L 892.6011,178.92994 L 891.77561,174.39182 L 890.12463,168.17293 L 884.84146,169.34947 L 862.55312,174.30778 L 863.21351,177.75339 L 864.6994,185.31692 L 864.6994,193.72083 L 863.54371,196.07393 L 865.41508,198.26677 L 870.47581,194.73055 L 874.10797,191.36899 L 876.08916,189.18398 L 876.91465,189.85629 L 879.72132,188.34359 L 885.00447,187.16705 L 893.58963,183.30123 z " id="CT" style="fill:#de8787" />
+ <ns0:path d="M 919.55232,177.09192 L 921.77043,176.37882 L 922.23741,174.59609 L 923.28809,174.71493 L 924.33877,177.09192 L 923.0546,177.56732 L 919.08535,177.68617 L 919.55232,177.09192 z M 909.97943,177.92387 L 912.31427,175.19033 L 913.94868,175.19033 L 915.81656,176.73537 L 913.36497,177.80501 L 911.14686,178.87466 L 909.97943,177.92387 z M 874.44023,155.06282 L 892.27091,150.69278 L 894.5823,150.02047 L 896.72858,146.65891 L 900.54482,144.92957 L 903.4955,149.51759 L 901.01902,154.89608 L 900.68883,156.40879 L 902.67001,159.09803 L 903.8257,158.25764 L 905.64178,158.25764 L 907.95316,160.94689 L 911.91552,167.16577 L 915.54769,167.67001 L 917.85907,166.66154 L 919.67515,164.81268 L 918.84966,161.95536 L 916.70338,160.27458 L 915.21749,161.11497 L 914.2269,159.77034 L 914.7222,159.26611 L 916.86848,159.09803 L 918.68456,159.93842 L 920.66574,162.45959 L 921.65633,165.48499 L 921.98653,168.00616 L 917.69397,169.51886 L 913.73161,171.5358 L 909.76924,176.24198 L 907.78806,177.75468 L 907.78806,176.74621 L 910.26454,175.23351 L 910.75983,173.38466 L 909.93434,170.19118 L 906.96257,171.70388 L 906.13708,173.21658 L 906.63237,175.56967 L 903.82678,177.08172 L 901.02115,172.37489 L 897.55407,167.83677 L 895.90308,166.66023 L 890.04208,168.25697 L 884.84146,169.34947 L 862.55312,174.30778 L 861.56253,168.34101 L 862.22292,157.33189 L 867.50608,156.40745 L 874.44023,155.06282" id="MA" style="fill:#c83737" />
+ <ns0:path d="M 943.28423,76.73985 L 945.26541,78.924863 L 947.57679,82.790656 L 947.57679,84.807591 L 945.43051,89.68185 L 943.44933,90.354162 L 939.98226,93.547643 L 935.02931,99.262292 C 935.02931,99.262292 934.36891,99.262292 933.70852,99.262292 C 933.04813,99.262292 932.71793,97.077279 932.71793,97.077279 L 930.90185,97.245357 L 929.91126,98.758058 L 927.43478,100.27076 L 926.44419,101.78346 L 928.09517,103.29616 L 927.59988,103.96847 L 927.10458,106.8258 L 925.1234,106.65772 L 925.1234,104.97694 L 924.7932,103.63232 L 923.30732,103.96847 L 921.49123,100.60692 L 919.34495,101.95154 L 920.66574,103.46424 L 920.99594,104.64079 L 920.17045,105.98541 L 920.50064,109.17889 L 920.66574,110.85967 L 919.01476,113.54892 L 916.04298,114.05315 L 915.71279,117.07855 L 910.26454,120.27203 L 908.94375,120.77627 L 907.29277,119.26356 L 904.15589,122.96128 L 905.14649,126.32284 L 903.6606,127.66746 L 903.4955,132.20556 L 901.88477,140.12915 L 899.37016,138.9273 L 898.87486,135.73381 L 894.91249,134.55727 L 894.5823,131.69993 L 887.15284,107.32858 L 882.28553,91.967581 L 884.77927,91.608771 L 886.32526,92.034941 L 886.32526,89.345695 L 887.15075,83.631045 L 889.79233,78.756786 L 891.27821,74.554837 L 889.29703,72.033669 L 889.29703,65.814786 L 890.12252,64.806318 L 890.94802,61.948993 L 890.78292,60.436292 L 890.61782,55.393954 L 892.4339,50.351617 L 895.40568,41.107331 L 897.55196,36.737305 L 898.87274,36.737305 L 900.19353,36.905383 L 900.19353,38.081928 L 901.51432,40.435019 L 904.32099,41.107331 L 905.14649,40.266941 L 905.14649,39.258474 L 909.27395,36.233071 L 911.09003,34.384214 L 912.57592,34.552292 L 918.68456,37.073461 L 920.66574,38.081928 L 929.91126,69.176344 L 936.0199,69.176344 L 936.84539,71.193279 L 937.01049,76.235617 L 939.98226,78.588708 L 940.80775,78.588708 L 940.97285,78.084474 L 940.47756,76.907928 L 943.28423,76.73985 z M 921.90732,108.08415 L 923.47577,106.48741 L 924.87911,107.57992 L 925.45696,110.1011 L 923.72342,111.02553 L 921.90732,108.08415 z M 928.75894,101.94929 L 930.57502,103.88219 C 930.57502,103.88219 931.89582,103.96623 931.89582,103.63007 C 931.89582,103.29391 932.14346,101.52909 932.14346,101.52909 L 933.05151,100.6887 L 932.22602,98.839833 L 930.16228,99.596189 L 928.75894,101.94929 z " id="ME" style="fill:#f4d7d7" />
+ <ns0:path d="M 900.54588,144.88986 L 900.85393,143.29871 L 901.96733,139.87704 L 899.37016,138.9273 L 898.87486,135.73381 L 894.91249,134.55727 L 894.5823,131.69993 L 887.15284,107.32858 L 882.45357,92.208279 L 881.5374,92.203019 L 880.87701,93.883799 L 880.21662,93.379565 L 879.22603,92.371097 L 877.74014,94.388032 L 876.76354,100.09176 L 877.08182,105.98396 L 879.063,108.84129 L 879.063,113.04325 L 875.26572,117.2452 L 872.62415,118.42176 L 872.62415,119.5983 L 873.77984,121.44716 L 873.77984,130.35531 L 872.95434,139.93577 L 872.78925,144.97812 L 873.77984,146.32275 L 873.61474,151.02894 L 873.11944,152.8778 L 874.60533,154.97877 L 892.27091,150.69278 L 894.5823,150.02047 L 896.72858,146.65891 L 900.54588,144.88986 z " id="NH" style="fill:#f4d7d7" />
+ <ns0:path d="M 862.38802,157.584 L 861.56253,151.70126 L 858.42565,140.27193 L 857.76525,139.93577 L 854.79347,138.59115 L 855.61896,135.56574 L 854.79347,133.38072 L 852.1519,128.67453 L 853.14249,124.64065 L 852.31699,119.26214 L 849.84051,112.53901 L 849.0178,107.42109 L 876.75058,99.933872 L 877.08182,105.98396 L 879.063,108.84129 L 879.063,113.04325 L 875.26572,117.2452 L 872.62415,118.42176 L 872.62415,119.5983 L 873.77984,121.44716 L 873.77984,130.35531 L 872.95434,139.93577 L 872.78925,144.97812 L 873.77984,146.32275 L 873.61474,151.02894 L 873.11944,152.8778 L 874.60533,154.97877 L 867.50608,156.40745 L 862.38802,157.584 z " id="VT" style="fill:#f4d7d7" />
+ <ns0:path d="M 846.20833,194.22506 L 845.05264,193.21659 L 842.41105,193.04851 L 840.09968,191.03158 L 837.62319,185.485 L 834.55471,184.51732 L 832.17493,182.29151 L 813.18856,186.49346 L 769.27227,195.56969 L 760.19184,197.08239 L 759.43798,189.88537 L 762.17121,188.00743 L 763.492,186.83089 L 764.48259,185.15011 L 766.29867,183.97356 L 768.27985,182.12471 L 768.77515,180.44393 L 770.92143,177.5866 L 772.07712,176.57814 L 771.91202,175.56967 L 770.59123,172.37619 L 768.77515,172.20811 L 766.79397,165.82115 L 769.76574,163.97229 L 774.2234,162.45959 L 778.35086,161.11497 L 781.65283,160.61073 L 788.09167,160.44266 L 790.07285,161.78728 L 791.72384,161.95536 L 793.87012,160.61073 L 796.51169,159.43419 L 801.79484,158.92995 L 803.94112,157.0811 L 805.75721,153.71954 L 807.40819,151.7026 L 809.55447,151.7026 L 811.53565,150.52606 L 811.70075,148.17297 L 810.21487,145.98795 L 809.88467,144.47525 L 811.04036,142.29024 L 811.04036,140.77754 L 809.22428,140.77754 L 807.40819,139.93715 L 806.5827,138.7606 L 806.4176,136.07136 L 812.36115,130.35671 L 813.02154,129.51632 L 814.50743,126.49092 L 817.4792,121.78473 L 820.28587,117.91894 L 822.43215,115.39777 L 824.89861,113.49969 L 828.0455,112.20429 L 833.65885,110.85967 L 836.96082,111.02775 L 841.58358,109.51505 L 849.30966,107.36166 L 849.84051,112.53901 L 852.31699,119.26214 L 853.14249,124.64065 L 852.1519,128.67453 L 854.79347,133.38072 L 855.61896,135.56574 L 854.79347,138.59115 L 857.76525,139.93577 L 858.42565,140.27193 L 861.56253,151.70126 L 862.05782,157.07976 L 861.56253,168.34101 L 862.38802,174.05567 L 863.21351,177.75339 L 864.6994,185.31692 L 864.6994,193.72083 L 863.54371,196.07393 L 865.42216,198.14582 L 865.19266,199.77289 L 863.21147,201.62175 L 863.54167,202.96637 L 864.86246,202.63021 L 866.34835,201.28559 L 868.65972,198.59634 L 869.81541,197.92403 L 871.4664,198.59634 L 873.77778,198.76442 L 881.8676,194.73055 L 884.83937,191.87323 L 886.16016,190.36053 L 890.45272,192.0413 L 886.98565,195.73902 L 883.02329,198.76442 L 875.75896,204.31099 L 873.11738,205.31946 L 867.17384,207.3364 L 863.04638,208.51294 L 861.49899,207.95886 L 860.90212,204.47784 L 861.39742,201.62051 L 861.23232,199.4355 L 858.59075,198.25894 L 853.96798,197.25047 L 850.0056,196.07393 L 846.20833,194.22506 z " id="NY" style="fill:#280b0b" />
+ <ns0:path d="M 846.20833,194.22506 L 844.06205,196.74624 L 844.06205,199.93973 L 842.08086,203.13321 L 841.91576,204.814 L 843.23656,206.15862 L 843.07146,208.6798 L 840.76007,209.85635 L 841.58556,212.71367 L 841.75066,213.89023 L 844.55734,214.22639 L 845.54794,216.91563 L 849.18011,219.43681 L 851.65659,221.11759 L 851.65659,221.95798 L 848.35462,225.15147 L 846.70362,227.50456 L 845.21774,230.3619 L 842.90636,231.70652 L 841.66812,232.46288 L 841.42046,233.72347 L 840.79828,236.43369 L 841.91377,238.76697 L 845.21574,241.79237 L 850.1687,244.14546 L 854.29616,244.81777 L 854.46126,246.33047 L 853.63576,247.33894 L 853.96596,250.19627 L 854.79145,250.19627 L 856.93773,247.6751 L 857.76322,242.63276 L 860.5699,238.43081 L 863.70677,231.70769 L 864.86246,225.99305 L 864.20207,224.8165 L 864.03697,215.06798 L 862.38598,211.53834 L 861.23029,212.37873 L 858.42362,212.71489 L 857.92832,212.21066 L 859.08401,211.20219 L 861.23029,209.18525 L 861.29469,208.048 L 860.90212,204.47784 L 861.39742,201.62051 L 861.23232,199.4355 L 858.59075,198.25894 L 853.96798,197.25047 L 850.0056,196.07393 L 846.20833,194.22506 z " id="NJ" style="fill:#a02c2c" />
+ <ns0:path d="M 841.75066,232.37883 L 842.90636,231.70652 L 845.21774,230.3619 L 846.70362,227.50456 L 848.35462,225.15147 L 851.65659,221.95798 L 851.65659,221.11759 L 849.18011,219.43681 L 845.54794,216.91563 L 844.55734,214.22639 L 841.75066,213.89023 L 841.58556,212.71367 L 840.76007,209.85635 L 843.07146,208.6798 L 843.23656,206.15862 L 841.91576,204.814 L 842.08086,203.13321 L 844.06205,199.93973 L 844.06205,196.74624 L 846.45598,194.22507 L 845.05264,193.21659 L 842.41105,193.04851 L 840.09968,191.03158 L 837.62319,185.485 L 834.55471,184.51732 L 832.17493,182.29151 L 813.18856,186.49346 L 769.27227,195.56969 L 760.19184,197.08239 L 759.68563,189.71729 L 754.08139,195.57094 L 752.7606,196.07518 L 748.46894,199.20351 L 751.4416,219.10066 L 753.16482,230.27806 L 756.81257,250.30417 L 761.54207,249.5232 L 773.73965,247.96108 L 812.47286,239.9916 L 827.66544,237.0562 L 836.14231,235.36944 L 837.45809,234.05962 L 839.60438,232.37883 L 841.75066,232.37883 z " id="PA" style="fill:#782121" />
+ <ns0:path d="M 840.59298,235.90964 L 841.42046,233.72347 L 841.66812,232.37883 L 839.60438,232.37883 L 837.45809,234.05962 L 835.9722,235.57232 L 837.45809,239.94236 L 839.76948,245.8251 L 841.91576,255.9098 L 843.56675,262.46486 L 848.68482,262.29678 L 854.95755,261.03674 L 852.64517,253.38975 L 851.65458,253.89398 L 848.02242,251.37281 L 846.20633,246.49855 L 844.22515,242.80084 L 841.91377,241.79237 L 839.76749,238.09466 L 840.59298,235.90964 z " id="DE" style="fill:#f4d7d7" />
+ <ns0:path d="M 854.95655,260.95325 L 848.68482,262.29678 L 843.56675,262.46486 L 841.91576,255.9098 L 839.76948,245.8251 L 837.45809,239.94236 L 836.14231,235.36944 L 827.66544,237.0562 L 812.47286,239.9916 L 774.22495,247.84224 L 775.38031,253.05285 L 776.37091,258.93558 L 776.7011,258.59942 L 778.84739,256.07825 L 781.15877,252.88476 L 783.63525,252.71668 L 785.12115,251.20398 L 786.93723,248.51473 L 788.25802,249.18705 L 791.22979,248.85089 L 793.87137,246.66588 L 795.92094,245.15492 L 797.80542,244.65068 L 799.48473,245.82549 L 802.45651,247.33819 L 804.43769,249.18705 L 805.67593,250.78379 L 809.88595,252.5486 L 809.88595,255.57402 L 815.49931,256.91864 L 817.48049,258.26326 L 818.47108,256.24633 L 820.78247,257.92711 L 819.29657,261.28868 L 818.96637,264.146 L 817.15029,266.83525 L 817.15029,269.02027 L 817.81068,270.86913 L 822.98233,272.27864 L 827.38511,272.21447 L 830.52198,273.22294 L 832.66826,273.5591 L 833.65885,271.37408 L 832.17296,269.18907 L 832.17296,267.34021 L 829.69649,265.1552 L 827.55021,259.44055 L 828.87099,253.89398 L 828.70589,251.70897 L 827.38511,250.36434 C 827.38511,250.36434 828.87099,248.68356 828.87099,248.01125 C 828.87099,247.33894 829.36629,245.82624 829.36629,245.82624 L 831.34747,244.48162 L 833.32865,242.80084 L 833.82395,243.8093 L 832.33806,245.49008 L 831.01727,249.35588 L 831.34747,250.53242 L 833.16355,250.86858 L 833.65885,256.58323 L 831.51257,257.59169 L 831.84277,261.28941 L 832.33806,261.12133 L 833.49375,259.1044 L 835.14473,260.95325 L 833.49375,262.29788 L 833.16355,265.82751 L 835.80513,269.35715 L 839.76749,269.86138 L 841.41848,269.02099 L 844.72045,274.39949 L 846.53653,274.90372 L 846.53653,278.60143 L 844.22515,283.64377 L 843.72986,290.87112 L 845.21574,294.40076 L 846.70163,294.56884 L 848.68281,290.19881 L 849.5083,286.5011 L 849.6734,279.10567 L 852.81027,274.06333 L 854.95655,266.83598 L 854.95655,260.95325 z M 838.20212,271.12031 L 839.3578,273.72552 L 839.5229,275.57439 L 840.67859,277.50729 C 840.67859,277.50729 841.58664,276.58285 841.58664,276.2467 C 841.58664,275.91054 840.8437,273.05321 840.8437,273.05321 L 840.10075,270.61606 L 838.20212,271.12031 z " id="MD" style="fill:#d35f5f" />
+ <ns0:path d="M 822.59725,272.21447 L 827.38511,272.21447 L 830.52198,273.22294 L 832.66826,273.5591 L 833.65885,271.37408 L 832.17296,269.18907 L 832.17296,267.34021 L 829.69649,265.1552 L 827.55021,259.44055 L 828.87099,253.89398 L 828.70589,251.70897 L 827.38511,250.36434 C 827.38511,250.36434 828.87099,248.68356 828.87099,248.01125 C 828.87099,247.33894 829.36629,245.82624 829.36629,245.82624 L 831.34747,244.48162 L 833.32865,242.80084 L 833.82395,243.8093 L 832.33806,245.49008 L 831.01727,249.35588 L 831.34747,250.53242 L 833.16355,250.86858 L 833.65885,256.58323 L 831.51257,257.59169 L 831.84277,261.28941 L 832.33806,261.12133 L 833.49375,259.1044 L 835.14473,260.95325 L 833.49375,262.29788 L 833.16355,265.82751 L 835.80513,269.35715 L 839.76749,269.86138 L 841.41848,269.02099 L 844.72045,274.39949 L 846.53653,274.90372 L 846.53653,278.60143 L 844.22515,283.64377 L 843.72986,290.87112 L 845.21574,294.40076 L 846.70163,294.56884 L 848.68281,290.19881 L 849.5083,286.5011 L 849.6734,279.10567 L 852.81027,274.06333 L 854.95655,266.83598 L 854.95655,260.95325 M 838.20212,271.12031 L 839.3578,273.72552 L 839.5229,275.57439 L 840.67859,277.50729 C 840.67859,277.50729 841.58664,276.58285 841.58664,276.2467 C 841.58664,275.91054 840.8437,273.05321 840.8437,273.05321 L 840.10075,270.61606 L 838.20212,271.12031 z " id="MD_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 774.24466,247.91204 L 775.38031,253.05285 L 776.37091,258.93558 L 776.7011,258.59942 L 778.84739,256.07825 L 781.15877,252.88476 L 783.63525,252.71668 L 785.12115,251.20398 L 786.93723,248.51473 L 788.25802,249.18705 L 791.22979,248.85089 L 793.87137,246.66588 L 795.92094,245.15492 L 797.80542,244.65068 L 799.48473,245.82549 L 802.45651,247.33819 L 804.43769,249.18705 L 805.84103,250.53167 L 804.7679,255.7421 L 798.98943,252.5486 L 794.36667,250.69975 L 794.20157,256.24633 L 793.70628,258.43134 L 792.05529,261.28868 L 791.39489,262.96946 L 788.25802,265.49063 L 787.76272,267.84373 L 784.29564,268.17988 L 783.96545,271.37336 L 782.80976,277.08802 L 780.16818,277.08802 L 778.84739,276.24763 L 777.1964,273.3903 L 775.38031,273.55838 L 775.05012,278.09649 L 772.90384,284.9877 L 767.78577,296.24894 L 768.61127,297.59356 L 768.44617,300.45089 L 766.29989,302.46782 L 764.814,302.13167 L 761.51202,304.65285 L 758.87045,303.64438 L 757.05436,308.51864 C 757.05436,308.51864 753.25709,309.35903 752.59669,309.52711 C 751.9363,309.69518 750.12022,308.18248 750.12022,308.18248 L 747.64373,310.53557 L 745.00215,311.2079 L 742.03037,310.36749 L 740.70958,309.02287 L 738.47074,305.8795 L 735.26132,303.81246 L 732.61975,300.95512 L 729.64798,297.08933 L 728.98758,294.73623 L 726.346,293.22353 L 725.5205,291.54275 L 725.27285,286.0802 L 727.50168,285.99616 L 729.48288,285.15577 L 729.64798,282.29845 L 731.29896,280.78574 L 731.46406,275.57532 L 732.45465,271.54144 L 733.77544,270.86913 L 735.09623,272.04567 L 735.59153,273.89453 L 737.40761,272.88606 L 737.90291,271.20529 L 736.74722,269.35643 L 736.74722,266.83525 L 737.73781,265.49063 L 740.04919,261.96099 L 741.36998,260.44829 L 743.51627,260.95252 L 745.82765,259.27173 L 748.96452,255.7421 L 751.27591,251.70821 L 751.6061,245.82549 L 752.1014,240.61506 L 752.1014,235.74079 L 750.94571,232.54731 L 751.9363,231.0346 L 753.24707,229.68997 L 756.81257,250.30417 L 761.54207,249.5232 L 774.24466,247.91204 z " id="WV" style="fill:#f4d7d7" />
+ <ns0:path d="M 738.61165,306.09768 L 735.42643,309.35903 L 731.13386,313.05675 L 726.51109,318.60333 L 724.69501,320.45219 L 724.69501,322.6372 L 720.73264,324.82222 L 714.95419,328.35186 L 712.28688,329.81359 L 659.31592,334.90691 L 643.2212,336.75577 L 638.50172,337.28883 L 634.55111,337.26001 L 634.55111,341.29389 L 625.96598,341.79812 L 618.86673,342.47043 L 608.21432,342.68419 L 609.24067,341.39196 L 611.46709,339.55999 L 613.56845,338.3715 L 613.80194,335.04372 L 614.73588,333.14213 L 613.09488,330.50244 L 613.91377,328.51994 L 616.22516,326.67109 L 618.37144,325.99877 L 621.17811,327.3434 L 624.81029,328.68802 L 625.96598,328.35186 L 626.13108,325.99877 L 624.81029,323.47759 L 625.14049,321.1245 L 627.12167,319.6118 L 629.76325,318.93949 L 631.41424,318.26718 L 630.58875,316.41831 L 629.92835,314.40138 L 631.08404,313.56099 L 632.15718,310.11537 L 635.2115,308.35056 L 641.15506,307.34209 L 644.78724,306.83786 L 646.27312,308.85479 L 648.08921,309.69518 L 649.90529,306.33362 L 652.87707,304.82092 L 654.85825,306.5017 L 655.68375,307.67825 L 657.83004,307.17401 L 657.66493,303.64438 L 660.63671,301.96359 L 661.7924,301.1232 L 662.94809,302.80398 L 667.73595,302.80398 L 668.56145,300.61896 L 668.23125,298.26587 L 671.20302,294.56815 L 675.99089,290.53428 L 676.48618,285.82809 L 679.29287,285.49193 L 683.25523,283.64307 L 686.06192,281.62613 L 685.73171,279.60919 L 684.24582,278.09649 L 684.82367,275.82744 L 689.03369,275.57532 L 691.51017,274.73493 L 694.48195,276.41571 L 696.13293,280.95382 L 702.07649,281.28997 L 703.89257,283.13884 L 706.03885,283.30692 L 708.51534,281.79422 L 711.65221,282.29845 L 712.973,283.81115 L 715.77968,281.12189 L 717.59577,279.77727 L 719.24675,279.77727 L 719.90714,282.63461 L 721.72324,283.64307 L 725.3554,285.82809 L 725.5205,291.54275 L 726.346,293.22353 L 728.98758,294.73623 L 729.64798,297.08933 L 732.61975,300.95512 L 735.26132,303.81246 L 738.61165,306.09768 z " id="KY" style="fill:#e9afaf" />
+ <ns0:path d="M 748.46982,198.97029 L 741.20371,203.30253 L 737.24134,205.65562 L 733.77427,209.52141 L 729.64681,213.55528 L 726.34484,214.39567 L 723.37307,214.8999 L 717.75972,217.58915 L 715.61344,217.75723 L 712.14638,214.56375 L 706.86322,215.23606 L 704.22165,213.72336 L 701.78994,212.3189 L 696.79333,213.05024 L 686.39211,214.73102 L 678.46737,215.99161 L 679.78816,231.20268 L 681.60425,245.48932 L 684.24582,269.86066 L 684.82367,275.82744 L 689.03369,275.57532 L 691.51017,274.73493 L 694.48195,276.41571 L 696.13293,280.95382 L 702.07649,281.28997 L 703.89257,283.13884 L 706.03885,283.30692 L 708.51534,281.79422 L 711.65221,282.29845 L 712.973,283.81115 L 715.77968,281.12189 L 717.59577,279.77727 L 719.24675,279.77727 L 719.90714,282.63461 L 721.72324,283.64307 L 725.27285,286.0802 L 727.50168,285.99616 L 729.48288,285.15577 L 729.64798,282.29845 L 731.29896,280.78574 L 731.46406,275.57532 L 732.45465,271.54144 L 733.77544,270.86913 L 735.09623,272.04567 L 735.59153,273.89453 L 737.40761,272.88606 L 737.90291,271.20529 L 736.74722,269.35643 L 736.74722,266.83525 L 737.73781,265.49063 L 740.04919,261.96099 L 741.36998,260.44829 L 743.51627,260.95252 L 745.82765,259.27173 L 748.96452,255.7421 L 751.27591,251.70821 L 751.6061,245.82549 L 752.1014,240.61506 L 752.1014,235.74079 L 750.94571,232.54731 L 751.9363,231.0346 L 753.33994,230.27806 L 751.4416,219.10066 L 748.46982,198.97029 z " id="OH" style="fill:#c83737" />
+ <ns0:path d="M 594.42414,81.655837 L 596.29202,79.516552 L 598.51013,78.684606 L 603.99703,74.643722 L 606.33188,74.049474 L 606.79885,74.524879 L 601.54544,79.873103 L 598.1599,81.893534 L 596.05854,82.844333 L 594.42414,81.655837 z M 682.43117,115.05941 L 683.09156,117.66462 L 686.39354,117.8327 L 687.71434,116.57211 C 687.71434,116.57211 687.63178,115.05941 687.30159,114.89133 C 686.97139,114.72326 685.6506,112.95843 685.6506,112.95843 L 683.42177,113.21054 L 681.77077,113.37862 L 681.44058,114.55518 L 682.43117,115.05941 z M 713.13697,180.61201 L 709.835,172.04003 L 707.52362,162.62767 L 705.04714,159.26611 L 702.40557,157.41725 L 700.75458,158.5938 L 696.79222,160.44266 L 694.81104,165.65307 L 692.00436,169.51886 L 690.84867,170.19118 L 689.36279,169.51886 C 689.36279,169.51886 686.72121,168.00616 686.88631,167.33385 C 687.05141,166.66154 687.38161,162.12343 687.38161,162.12343 L 690.84867,160.77881 L 691.67417,157.24918 L 692.33456,154.55993 L 694.81104,152.87915 L 694.48084,142.45832 L 692.82985,140.10523 L 691.50907,139.26484 L 690.68357,137.07982 L 691.50907,136.23943 L 693.16005,136.57559 L 693.32515,134.89481 L 690.84867,132.54172 L 689.52789,129.85247 L 686.88631,129.85247 L 682.26355,128.33977 L 676.6502,124.81014 L 673.84353,124.81014 L 673.18314,125.48245 L 672.19255,124.97821 L 669.05568,122.62512 L 666.0839,124.47398 L 663.11213,126.82707 L 663.44233,130.52479 L 664.43292,130.86094 L 666.5792,131.36518 L 667.07449,132.20556 L 664.43292,133.04595 L 661.79134,133.38211 L 660.30546,135.23097 L 659.97526,137.41598 L 660.30546,139.09676 L 660.63565,144.81141 L 657.00349,146.99642 L 656.34309,146.82834 L 656.34309,142.45832 L 657.66388,139.93715 L 658.32427,137.41598 L 657.49878,136.57559 L 655.5176,137.41598 L 654.52701,141.78601 L 651.72034,142.96255 L 649.90425,144.97949 L 649.73915,145.98795 L 650.39955,146.82834 L 649.73915,149.51759 L 647.42778,150.02182 L 647.42778,151.19837 L 648.25327,153.71954 L 647.09758,160.1065 L 645.44659,164.30845 L 646.10699,169.18271 L 646.60228,170.35925 L 645.77679,172.88042 L 645.44659,173.72081 L 645.1164,176.57814 L 648.74856,182.79702 L 651.72034,189.52014 L 653.20622,194.56247 L 652.38073,199.43673 L 651.39014,205.65562 L 648.91366,211.03411 L 648.58347,213.89143 L 645.43483,217.12572 L 644.67586,217.92491 L 649.40999,217.75643 L 671.86343,215.40333 L 678.13717,214.73102 L 678.46737,215.99161 L 686.39211,214.73102 L 696.79333,213.05024 L 702.12014,212.57103 L 700.75458,211.37027 L 700.91968,209.85756 L 703.06596,205.99177 L 705.10906,204.18493 L 704.88204,198.9325 L 706.51299,197.27212 L 707.62681,196.91556 L 707.85382,193.21785 L 709.42225,190.0664 L 710.49539,190.69668 L 710.66049,191.36899 L 711.48598,191.53707 L 713.46716,190.5286 L 713.13697,180.61201 z M 578.8376,112.43927 L 580.72799,111.3639 L 583.53467,110.52351 L 587.16683,108.17042 L 587.16683,107.16195 L 587.82723,106.48964 L 593.93587,105.48118 L 596.41235,103.46424 L 600.87001,101.27923 L 601.03511,99.934604 L 603.01629,96.909201 L 604.83237,96.068811 L 606.15316,94.219954 L 608.46454,91.866863 L 612.9222,89.345695 L 617.71005,88.841461 L 618.86574,90.018006 L 618.53554,91.026474 L 614.73828,92.034941 L 613.25239,95.228422 L 610.94101,96.068811 L 610.44572,98.58998 L 607.96924,101.95154 L 607.63904,104.64079 L 608.46454,105.14502 L 609.45513,103.96847 L 613.08729,100.94307 L 614.40808,102.28769 L 616.71946,102.28769 L 620.02143,103.29616 L 621.50732,104.47271 L 622.9932,107.66619 L 625.79988,110.52351 L 629.76224,110.35543 L 631.24813,109.34697 L 632.89911,110.69159 L 634.5501,111.19582 L 635.87088,110.35543 L 637.02657,110.35543 L 638.67756,109.34697 L 642.80502,105.64925 L 646.27209,104.47271 L 653.04112,104.13655 L 657.66388,102.11962 L 660.30546,100.77499 L 661.79134,100.94307 L 661.79134,106.8258 L 662.28664,107.16195 L 665.25841,108.00234 L 667.23959,107.49811 L 673.51333,105.81733 L 674.66902,104.64079 L 676.15491,105.14502 L 676.15491,112.37237 L 679.45688,115.56585 L 680.77767,116.23816 L 682.09845,117.24663 L 680.77767,117.58279 L 679.95217,117.24663 L 676.15491,116.7424 L 674.00863,117.41471 L 671.69725,117.24663 L 668.39528,118.75933 L 666.5792,118.75933 L 660.63565,117.41471 L 655.3525,117.58279 L 653.37132,120.27203 L 646.27209,120.94434 L 643.79561,121.78473 L 642.63992,124.97821 L 641.31913,126.15476 L 640.82384,125.98668 L 639.33795,124.3059 L 634.71519,126.82707 L 634.0548,126.82707 L 632.89911,125.14629 L 632.07362,125.31437 L 630.09244,129.85247 L 629.10185,134.05442 L 625.27363,142.51293 L 623.6083,141.31962 L 622.20739,139.89342 L 620.57299,129.197 L 616.83724,128.00851 L 615.43633,125.63153 L 602.59467,122.77914 L 600.02634,121.59066 L 591.62089,119.21367 L 583.21544,118.02518 L 578.8376,112.43927 z " id="MI" style="fill:#c83737" />
+ <ns0:path d="M 363.20447,145.98954 L 351.44763,144.98362 L 318.67723,141.55738 L 302.09981,139.41809 L 273.14771,135.13952 L 252.83454,132.04945 L 251.38528,143.66906 L 247.4644,168.89268 L 242.09425,200.50655 L 240.53069,211.44019 L 238.82546,223.80098 L 245.48808,224.7662 L 262.73518,227.14318 L 271.75391,228.36645 L 292.76047,230.93187 L 330.81813,235.21041 L 355.80134,237.34972 L 360.23755,191.23625 L 361.87193,164.85174 L 363.20447,145.98954 z " id="WY" style="fill:#f4d7d7" />
+ <ns0:path d="M 365.51098,123.96764 L 366.33647,111.87386 L 368.64299,85.935913 L 370.0439,70.247815 L 371.33142,55.452236 L 338.69364,52.032396 L 308.81082,48.334682 L 278.92799,44.132734 L 245.9083,38.586162 L 227.08707,35.056526 L 193.6675,27.848581 L 189.09322,50.043547 L 192.59549,57.887585 L 191.19458,62.64155 L 193.06246,67.395514 L 196.33125,68.821708 L 200.067,79.518133 L 203.80276,83.321298 L 204.26973,84.509794 L 207.772,85.698291 L 208.23897,87.837565 L 201.00095,106.14034 L 201.00095,108.75502 L 203.56928,112.08279 L 204.50321,112.08279 L 209.40639,108.99272 L 210.10685,107.80422 L 211.74124,108.51732 L 211.50775,113.98438 L 214.30957,127.05779 L 217.34487,129.67247 L 218.2788,130.38556 L 220.14668,132.76254 L 219.67972,136.32802 L 220.38017,139.89349 L 221.5476,140.84429 L 223.88244,138.4673 L 226.68426,138.4673 L 229.95305,140.13119 L 232.52138,139.1804 L 236.7241,139.1804 L 240.45985,140.84429 L 243.26167,140.36889 L 243.72864,137.27881 L 246.76394,136.56572 L 248.16485,137.99191 L 248.63182,141.31968 L 251.26853,143.90677 L 252.83454,132.04945 L 273.14771,135.13952 L 302.09981,139.41809 L 318.67723,141.55738 L 351.44763,144.98362 L 363.16317,146.23449 L 364.89307,129.69427 L 365.51098,123.96764 z " id="MT" style="fill:#f4d7d7" />
+ <ns0:path d="M 144.08485,180.96023 L 148.93381,161.76187 L 153.37002,143.34026 L 154.77093,138.94284 L 157.33926,132.76269 L 156.0551,130.38571 L 153.48676,130.50455 L 152.66957,129.43491 L 153.13654,128.24642 L 153.48676,125.0375 L 158.03971,119.33273 L 159.90759,118.85734 L 161.07501,117.66885 L 161.65873,114.34107 L 162.59266,113.62798 L 166.5619,107.56668 L 170.53114,103.05041 L 170.76463,99.128389 L 167.26235,96.394856 L 165.91982,91.819167 L 166.32842,81.776422 L 170.06418,64.662142 L 174.61712,43.031605 L 178.46962,29.00742 L 179.24775,25.053259 L 193.6675,27.848581 L 189.09322,50.043547 L 192.59549,57.887585 L 191.19458,62.64155 L 193.06246,67.395514 L 196.33125,68.821708 L 200.067,79.518133 L 203.80276,83.321298 L 204.26973,84.509794 L 207.772,85.698291 L 208.23897,87.837565 L 201.00095,106.14034 L 201.00095,108.75502 L 203.56928,112.08279 L 204.50321,112.08279 L 209.40639,108.99272 L 210.10685,107.80422 L 211.74124,108.51732 L 211.50775,113.98438 L 214.30957,127.05779 L 217.34487,129.67247 L 218.2788,130.38556 L 220.14668,132.76254 L 219.67972,136.32802 L 220.38017,139.89349 L 221.5476,140.84429 L 223.88244,138.4673 L 226.68426,138.4673 L 229.95305,140.13119 L 232.52138,139.1804 L 236.7241,139.1804 L 240.45985,140.84429 L 243.26167,140.36889 L 243.72864,137.27881 L 246.76394,136.56572 L 248.16485,137.99191 L 248.63182,141.31968 L 251.31689,143.45897 L 247.4644,168.89268 L 242.21095,200.38777 L 237.30774,199.55589 L 228.78555,198.12969 L 218.27875,196.22811 L 206.02081,194.08882 L 193.0624,191.65242 L 184.89044,189.57255 L 175.43432,187.67098 L 165.51122,185.65054 L 144.08485,180.96023 z " id="ID" style="fill:#f4d7d7" />
+ <ns0:path d="M 95.99889,2.9536428 L 100.45655,4.4663441 L 110.36246,7.3236687 L 119.11268,9.3406038 L 139.58489,15.223331 L 163.02887,21.106058 L 179.49525,24.969183 L 178.46962,29.00742 L 174.61712,43.031605 L 170.06418,64.662142 L 166.32842,81.776422 L 166.13328,91.861195 L 151.85237,88.313121 L 136.44238,84.628799 L 120.68217,84.747642 L 120.21521,83.321459 L 114.61157,85.460744 L 110.05862,84.866496 L 107.60703,83.202605 L 106.32286,83.915707 L 101.53644,83.678 L 99.785303,82.251817 L 94.415149,80.112532 L 93.597952,80.231386 L 89.161743,78.686338 L 87.177124,80.587926 L 80.873036,80.231386 L 74.802439,75.952816 L 75.50289,75.12087 L 75.736374,67.039124 L 73.401527,62.998262 L 69.198802,62.404014 L 68.498351,59.789335 L 66.094359,59.304248 L 64.13488,57.747045 L 62.318797,58.755513 L 60.007419,55.73011 L 60.337616,52.704708 L 63.14429,52.368552 L 64.795274,48.166604 L 62.153699,46.990058 L 62.318797,43.124266 L 66.776456,42.451954 L 63.969782,39.59463 L 62.483896,32.199201 L 63.14429,29.173799 L 63.14429,20.93798 L 61.328206,17.576422 L 63.639585,7.8279025 L 65.785865,8.3321363 L 68.262342,11.357539 L 71.069016,14.046786 L 74.370985,16.063721 L 78.993743,18.248734 L 82.130616,18.921045 L 85.102388,20.433747 L 88.569459,21.442214 L 90.880838,21.274136 L 90.880838,18.752967 L 92.201625,17.576422 L 94.347905,16.231799 L 94.678102,17.408344 L 95.008299,19.257201 L 92.696921,19.761435 L 92.366724,21.946448 L 94.182807,23.459149 L 95.338496,25.980318 L 95.99889,27.997253 L 97.484776,27.829175 L 97.649875,26.484552 L 96.659284,25.139928 L 96.163989,21.77837 L 96.989481,19.929513 L 96.329087,18.416812 L 96.329087,16.063721 L 98.14517,12.366006 L 96.989481,9.6767597 L 94.513004,4.634422 L 94.843201,3.7940324 L 95.99889,2.9536428 z M 86.341086,9.169955 L 88.404826,9.001877 L 88.900121,10.430545 L 90.468562,8.7497548 L 92.862495,8.7497548 L 93.687987,10.3465 L 92.119546,12.111324 L 92.779951,12.951724 L 92.037002,15.052704 L 90.63366,15.472893 C 90.63366,15.472893 89.725613,15.556938 89.725613,15.220782 C 89.725613,14.884626 91.21151,12.531524 91.21151,12.531524 L 89.477971,11.943246 L 89.147774,13.455958 L 88.404826,14.12827 L 86.836382,11.775168 L 86.341086,9.169955 z " id="WA" style="fill:#d35f5f" />
+ <ns0:path d="M 224.65378,521.59843 L 226.52879,518.16091 L 228.7163,517.84841 L 229.0288,518.62966 L 226.99754,521.59843 L 224.65378,521.59843 z M 234.49758,518.00466 L 240.43511,520.50467 L 242.46637,520.19217 L 244.02887,516.44215 L 243.40387,513.16089 L 239.34135,512.69214 L 235.43508,514.41089 L 234.49758,518.00466 z M 264.18522,527.69221 L 267.77898,533.00473 L 270.12274,532.69223 L 271.2165,532.22348 L 272.62275,533.47348 L 276.21652,533.31723 L 277.15403,531.91098 L 274.34151,530.19222 L 272.4665,526.59845 L 270.43524,523.16094 L 264.81022,525.97345 L 264.18522,527.69221 z M 283.71656,536.286 L 284.96656,534.41099 L 289.49783,535.34849 L 290.12284,534.87974 L 296.06036,535.50474 L 295.74786,536.75475 L 293.24785,538.161 L 289.02908,537.8485 L 283.71656,536.286 z M 288.87283,541.28602 L 290.74784,545.03604 L 293.7166,543.94228 L 294.0291,542.37977 L 292.4666,540.34851 L 288.87283,540.03601 L 288.87283,541.28602 z M 295.59161,540.19226 L 297.77912,537.37975 L 302.31039,539.72351 L 306.52916,540.81727 L 310.74793,543.47353 L 310.74793,545.34854 L 307.31042,547.0673 L 302.62289,548.0048 L 300.27913,546.59854 L 295.59161,540.19226 z M 311.68544,555.19233 L 313.24794,553.94233 L 316.52921,555.50484 L 323.87299,558.94235 L 327.15426,560.97361 L 328.71676,563.31737 L 330.59177,567.53614 L 334.49804,570.03615 L 334.18554,571.28616 L 330.43552,574.41117 L 326.373,575.81743 L 324.96675,575.19243 L 321.99798,576.91118 L 319.65422,580.0362 L 317.46671,582.84871 L 315.74795,582.69246 L 312.31044,580.19245 L 311.99794,575.81743 L 312.62294,573.47367 L 311.06043,568.00489 L 309.02917,566.28613 L 308.87292,563.78612 L 311.06043,562.84862 L 313.09169,559.87986 L 313.56044,558.94235 L 311.99794,557.22359 L 311.68544,555.19233 z " id="HI_Pacific" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 224.65378,521.59843 L 226.52879,518.16091 L 228.7163,517.84841 L 229.0288,518.62966 L 226.99754,521.59843 L 224.65378,521.59843 z M 234.49758,518.00466 L 240.43511,520.50467 L 242.46637,520.19217 L 244.02887,516.44215 L 243.40387,513.16089 L 239.34135,512.69214 L 235.43508,514.41089 L 234.49758,518.00466 z M 264.18522,527.69221 L 267.77898,533.00473 L 270.12274,532.69223 L 271.2165,532.22348 L 272.62275,533.47348 L 276.21652,533.31723 L 277.15403,531.91098 L 274.34151,530.19222 L 272.4665,526.59845 L 270.43524,523.16094 L 264.81022,525.97345 L 264.18522,527.69221 z M 283.71656,536.286 L 284.96656,534.41099 L 289.49783,535.34849 L 290.12284,534.87974 L 296.06036,535.50474 L 295.74786,536.75475 L 293.24785,538.161 L 289.02908,537.8485 L 283.71656,536.286 z M 288.87283,541.28602 L 290.74784,545.03604 L 293.7166,543.94228 L 294.0291,542.37977 L 292.4666,540.34851 L 288.87283,540.03601 L 288.87283,541.28602 z M 295.59161,540.19226 L 297.77912,537.37975 L 302.31039,539.72351 L 306.52916,540.81727 L 310.74793,543.47353 L 310.74793,545.34854 L 307.31042,547.0673 L 302.62289,548.0048 L 300.27913,546.59854 L 295.59161,540.19226 z M 311.68544,555.19233 L 313.24794,553.94233 L 316.52921,555.50484 L 323.87299,558.94235 L 327.15426,560.97361 L 328.71676,563.31737 L 330.59177,567.53614 L 334.49804,570.03615 L 334.18554,571.28616 L 330.43552,574.41117 L 326.373,575.81743 L 324.96675,575.19243 L 321.99798,576.91118 L 319.65422,580.0362 L 317.46671,582.84871 L 315.74795,582.69246 L 312.31044,580.19245 L 311.99794,575.81743 L 312.62294,573.47367 L 311.06043,568.00489 L 309.02917,566.28613 L 308.87292,563.78612 L 311.06043,562.84862 L 313.09169,559.87986 L 313.56044,558.94235 L 311.99794,557.22359 L 311.68544,555.19233 z " id="HI" style="fill:#e9afaf" />
+ <ns0:path d="M 365.08234,342.9472 L 388.2557,344.07626 L 420.00963,345.26475 L 418.60872,369.98537 L 418.14175,388.52584 L 418.37523,390.18973 L 422.81144,393.9929 L 424.9128,395.18139 L 425.61327,394.94369 L 426.31372,392.80441 L 427.71463,394.70599 L 429.81599,394.70599 L 429.81599,393.2798 L 432.6178,394.70599 L 432.15084,398.74687 L 436.35356,398.98457 L 438.92189,400.17306 L 443.12462,400.88615 L 445.69295,402.78774 L 448.02779,400.64846 L 451.53007,401.36155 L 454.0984,404.92703 L 455.03233,404.92703 L 455.03233,407.30401 L 457.36718,408.0171 L 459.70203,405.64012 L 461.5699,406.35321 L 464.13823,406.35321 L 465.07218,408.9679 L 469.97536,410.86948 L 471.37627,410.15638 L 473.24415,405.87781 L 474.41156,405.87781 L 475.57899,408.0171 L 479.78172,408.73019 L 483.51747,410.15638 L 486.55277,411.10718 L 488.42065,410.15638 L 489.1211,407.54171 L 493.55731,407.54171 L 495.65868,408.49249 L 498.46049,406.35321 L 499.62792,406.35321 L 500.32837,408.0171 L 504.53109,408.0171 L 506.16549,405.87781 L 508.03337,406.35321 L 510.13473,408.9679 L 513.40351,410.86948 L 516.6723,411.82028 L 519.47412,413.12761 L 521.80896,415.14805 L 524.84426,413.72186 L 527.64608,414.91035 L 528.33194,426.45357 L 528.34653,436.5409 L 529.04699,446.28652 L 529.74745,450.3274 L 532.31578,454.60597 L 533.24971,459.83533 L 537.68592,465.54009 L 537.9194,468.86787 L 538.61987,469.58096 L 537.9194,478.3758 L 534.88411,483.60516 L 536.5185,485.74444 L 535.81804,488.35912 L 535.11759,495.96547 L 533.71668,499.29324 L 534.00598,503.01936 L 527.40119,504.83432 L 517.33018,509.5405 L 516.33959,511.55743 L 513.69802,513.57437 L 511.55174,515.08707 L 510.23095,515.92746 L 504.4525,521.47403 L 501.64583,523.65904 L 496.19758,527.0206 L 490.41913,529.54177 L 483.98029,533.07141 L 482.16421,534.58411 L 476.22066,538.28182 L 472.7536,538.95414 L 468.79123,544.66878 L 464.66377,545.00494 L 463.67318,547.02188 L 465.98456,549.03881 L 464.49867,554.75346 L 463.17788,559.45964 L 462.0222,563.49351 L 461.1967,568.19969 L 462.0222,570.72086 L 463.83828,577.94821 L 464.82887,584.33517 L 466.64495,587.1925 L 465.65436,588.7052 L 462.51749,590.72214 L 456.73904,586.68827 L 451.1257,585.51172 L 449.80491,586.01595 L 446.50294,585.34364 L 442.21038,582.15016 L 436.92723,580.97362 L 429.1676,577.44398 L 427.02132,573.41011 L 425.70053,566.68699 L 422.39856,564.67006 L 421.73817,562.31697 L 422.39856,561.64466 L 422.72876,558.11502 L 421.40797,557.44271 L 420.74758,556.43424 L 422.06837,551.89614 L 420.41738,549.54304 L 417.11541,548.19842 L 413.64834,543.66032 L 410.01618,536.76912 L 405.72362,534.07988 L 405.88872,532.06294 L 400.44047,519.28902 L 399.61497,514.91899 L 397.79889,512.90206 L 397.63379,511.38936 L 391.52515,505.84279 L 388.88357,502.6493 L 388.88357,501.47276 L 386.242,499.28775 L 379.30786,498.1112 L 371.71333,497.43889 L 368.57646,495.0858 L 363.9537,496.93466 L 360.32154,498.44736 L 358.01016,501.80891 L 357.01957,505.67471 L 352.56191,512.06167 L 350.08543,514.58284 L 347.44386,513.57437 L 345.62777,512.39782 L 343.64659,511.72551 L 339.68423,509.37242 L 339.68423,508.70011 L 337.86815,506.68317 L 332.585,504.49816 L 324.99047,496.43042 L 322.67909,491.55616 L 322.67909,483.15227 L 319.37712,476.42915 L 318.88182,473.57182 L 317.23084,472.56336 L 316.07515,470.37834 L 310.9571,468.19333 L 309.63631,466.51255 L 302.37198,458.27673 L 301.05119,454.91517 L 296.26332,452.56207 L 294.77744,448.02394 L 292.13584,444.99855 L 290.15467,444.49434 L 289.49163,439.63101 L 297.66367,440.34413 L 327.31615,443.19648 L 356.96871,444.86037 L 359.30356,420.13975 L 363.27277,362.37936 L 364.90719,342.88781 L 366.3081,342.91754 M 467.38967,586.18345 L 466.81183,578.788 L 464.00514,571.30851 L 463.42729,563.99709 L 464.99573,555.42509 L 468.38027,548.28176 L 471.92989,542.65112 L 475.14933,538.95339 L 475.80972,539.20552 L 470.9393,546.09673 L 466.48163,552.90391 L 464.41788,559.79513 L 464.08769,565.17365 L 464.99573,571.56063 L 467.63732,579.04012 L 468.13261,584.41863 L 468.29771,585.93134 L 467.38967,586.18345 z " id="TX" style="fill:#280b0b" />
+ <ns0:path d="M 140.74058,399.1133 L 144.96457,398.27159 L 146.48222,396.01346 L 147.06593,393.16108 L 143.33019,392.56684 L 142.86321,391.73489 L 143.33019,389.95216 L 143.44692,383.89086 L 145.54828,383.17775 L 148.46685,380.32538 L 149.05056,375.21486 L 150.56821,371.4117 L 152.55282,369.39125 L 156.0551,367.60852 L 157.68948,366.18234 L 157.80623,363.80536 L 156.75555,363.21111 L 155.93835,362.14147 L 154.65419,356.08016 L 151.85237,350.96965 L 152.44317,347.57226 L 149.86775,344.19525 L 135.04148,320.54427 L 115.19528,290.35661 L 91.963567,255.17727 L 79.318752,235.85757 L 80.989783,229.03046 L 88.111072,202.05171 L 96.399776,169.3682 L 82.624178,165.56503 L 68.848582,161.99955 L 56.006928,157.72098 L 48.301935,155.58169 L 36.627704,152.49162 L 29.426941,149.98419 L 27.813217,154.89608 L 27.648119,162.62767 L 22.364968,174.89736 L 19.228097,177.5866 L 18.8979,178.76315 L 17.081817,179.60354 L 15.595931,183.97356 L 14.770438,187.33512 L 17.577112,191.70515 L 19.228097,196.07518 L 20.383786,199.77289 L 20.053589,206.49601 L 18.237506,209.68949 L 17.577112,215.74029 L 16.586521,219.60608 L 18.402605,223.63995 L 21.209279,228.34614 L 23.520657,233.38847 L 24.841445,237.59042 L 24.511248,240.95198 L 24.181051,241.45621 L 24.181051,243.64123 L 29.959497,250.19627 L 29.464202,252.71743 L 28.803808,255.07053 L 28.143414,257.08746 L 28.308513,265.65943 L 30.454793,269.52523 L 32.435974,272.21447 L 35.242648,272.71871 L 36.233239,275.57603 L 35.07755,279.27375 L 32.93127,280.95453 L 31.775581,280.95453 L 30.950088,284.9884 L 31.445384,288.0138 L 34.747353,292.5519 L 36.398338,298.09847 L 37.884224,302.97273 L 39.205012,306.16621 L 42.672079,312.21702 L 44.157966,314.90627 L 44.653261,317.93167 L 46.304246,318.94014 L 46.304246,321.4613 L 45.478753,323.47824 L 43.66267,330.87367 L 43.167375,332.8906 L 45.643852,335.74793 L 49.936412,336.25216 L 54.559169,338.10102 L 58.521532,340.28603 L 61.493305,340.28603 L 64.465077,343.47951 L 67.106653,348.52185 L 68.262342,350.87494 L 72.224705,353.05995 L 77.177659,353.90034 L 78.663546,356.08536 L 79.32394,359.44692 L 77.838053,360.11923 L 78.16825,361.12769 L 81.470222,361.96808 L 84.276896,362.13616 L 87.248668,367.01042 L 91.211035,371.38045 L 92.036527,373.73354 L 94.678102,378.10356 L 95.008299,381.46512 L 95.008299,391.21364 L 95.503595,393.0625 L 105.7397,394.5752 L 125.88171,397.43253 L 140.74058,399.1133 z M 50.26694,346.75563 L 51.587732,348.35237 L 51.422633,349.697 L 48.120652,349.61296 L 47.542806,348.35237 L 46.88241,346.83966 L 50.26694,346.75563 z M 52.248128,346.75563 L 53.48637,346.08332 L 57.118549,348.26833 L 60.255432,349.52892 L 59.347387,350.20124 L 54.724613,349.94912 L 53.073623,348.26833 L 52.248128,346.75563 z M 73.380807,367.34524 L 75.19689,369.78238 L 76.022393,370.79086 L 77.590834,371.37912 L 78.168673,369.86642 L 77.178082,368.01756 L 74.453952,365.91658 L 73.380807,366.08465 L 73.380807,367.34524 z M 71.89491,376.33744 L 73.711004,379.61497 L 74.949248,381.63192 L 73.463351,381.88403 L 72.142563,380.62344 C 72.142563,380.62344 71.399615,379.11074 71.399615,378.69054 C 71.399615,378.27035 71.399615,376.42148 71.399615,376.42148 L 71.89491,376.33744 z " id="CA" style="fill:#280b0b" />
+ <ns0:path d="M 141.11208,399.22238 L 138.4292,401.4664 L 138.099,402.9791 L 138.5943,403.98756 L 157.91082,415.08071 L 170.2932,422.98037 L 185.31716,431.8885 L 202.4874,442.30933 L 215.03489,444.8305 L 242.33612,448.47703 L 244.42909,434.63935 L 248.26156,406.64864 L 255.37454,351.33455 L 259.72234,319.30647 L 233.45531,315.31482 L 205.67064,310.56085 L 171.53056,303.99238 L 168.54652,322.80241 L 168.07955,323.2778 L 166.32842,326.01134 L 163.76009,325.89248 L 162.47592,323.04011 L 159.67411,322.68356 L 158.74017,321.49507 L 157.80623,321.49507 L 156.87229,322.08932 L 154.88767,323.15896 L 154.77093,330.40875 L 154.53744,332.19149 L 153.95374,345.26489 L 152.43609,347.52302 L 151.85237,350.96965 L 154.65419,356.08016 L 155.93835,362.14147 L 156.75555,363.21111 L 157.80623,363.80536 L 157.68948,366.18234 L 156.0551,367.60852 L 152.55282,369.39125 L 150.56821,371.4117 L 149.05056,375.21486 L 148.46685,380.32538 L 145.54828,383.17775 L 143.44692,383.89086 L 143.33019,389.95216 L 142.86321,391.73489 L 143.33019,392.56684 L 147.06593,393.16108 L 146.48222,396.01346 L 144.96457,398.27159 L 141.11208,399.22238 z " id="AZ" style="fill:#de8787" />
+ <ns0:path d="M 144.08485,180.96023 L 165.51122,185.65054 L 175.43432,187.67098 L 184.89044,189.57255 L 193.12078,191.77126 L 191.89498,197.53545 L 188.27597,215.71936 L 184.42347,236.99336 L 182.43886,246.26359 L 180.22075,260.40663 L 176.83522,277.63975 L 173.56644,293.44668 L 171.55619,304.36486 L 168.54652,322.80241 L 168.07955,323.2778 L 166.32842,326.01134 L 163.76009,325.89248 L 162.47592,323.04011 L 159.67411,322.68356 L 158.74017,321.49507 L 157.80623,321.49507 L 156.87229,322.08932 L 154.88767,323.15896 L 154.77093,330.40875 L 154.53744,332.19149 L 153.95374,345.26489 L 152.43963,347.54764 L 149.86775,344.19525 L 135.04148,320.54427 L 115.19528,290.35661 L 91.963567,255.17727 L 79.318752,235.85757 L 80.989783,229.03046 L 88.111072,202.05171 L 96.166292,169.45944 L 130.48854,177.92533 L 144.49761,181.0154" id="NV" style="fill:#e9afaf" />
+ <ns0:path d="M 259.6056,319.59339 L 233.45531,315.31482 L 205.67064,310.56085 L 171.45228,304.13508 L 173.56644,293.44668 L 176.83522,277.63975 L 180.22075,260.40663 L 182.43886,246.26359 L 184.42347,236.99336 L 188.27597,215.71936 L 191.89498,197.53545 L 193.03322,191.74155 L 206.02081,194.08882 L 218.27875,196.22811 L 228.78555,198.12969 L 237.30774,199.55589 L 242.21095,200.38777 L 240.53069,211.44019 L 238.82546,223.80098 L 245.48808,224.7662 L 262.73518,227.14318 L 272.10412,228.36648 L 268.94498,251.37398 L 265.6762,274.66841 L 261.84373,303.76606 L 260.30605,315.31482 L 259.6056,319.59339 z " id="UT" style="fill:#e9afaf" />
+ <ns0:path d="M 384.05299,331.95365 L 388.2557,263.49654 L 389.8901,240.2021 L 355.80134,237.34972 L 330.81813,235.21041 L 292.76047,230.93187 L 271.63008,228.31722 L 268.94498,251.37398 L 265.6762,274.66841 L 261.84373,303.76606 L 260.30605,315.31482 L 259.72234,319.35569 L 294.86179,323.63426 L 332.58567,328.23704 L 366.04543,330.52751 L 372.84567,331.2406 L 384.5199,331.83485" id="CO" style="fill:#e9afaf" />
+ <ns0:path d="M 290.31977,444.66242 L 289.49163,439.63101 L 297.66367,440.34413 L 327.31615,443.19648 L 356.96871,444.86037 L 359.30356,420.13975 L 363.27277,362.37936 L 364.90719,342.88781 L 366.3081,342.91754 L 366.29351,330.73549 L 332.58567,328.23704 L 294.86179,323.63426 L 259.66398,319.35569 L 255.37454,351.33455 L 248.26156,406.64864 L 244.42909,434.63935 L 242.33612,448.47703 L 258.12559,450.54515 L 259.44637,440.12432 L 276.45152,442.81356 L 290.31977,444.66242 z " id="NM" style="fill:#e9afaf" />
+ <ns0:path d="M 144.38087,180.54003 L 148.93381,161.76187 L 153.37002,143.34026 L 154.77093,138.94284 L 157.33926,132.76269 L 156.0551,130.38571 L 153.48676,130.50455 L 152.66957,129.43491 L 153.13654,128.24642 L 153.48676,125.0375 L 158.03971,119.33273 L 159.90759,118.85734 L 161.07501,117.66885 L 161.65873,114.34107 L 162.59266,113.62798 L 166.5619,107.56668 L 170.53114,103.05041 L 170.76463,99.128389 L 167.26235,96.394856 L 166.19165,92.03947 L 151.85237,88.313121 L 136.44238,84.628799 L 120.68217,84.747642 L 120.21521,83.321459 L 114.61157,85.460744 L 110.05862,84.866496 L 107.60703,83.202605 L 106.32286,83.915707 L 101.53644,83.678 L 99.785303,82.251817 L 94.415149,80.112532 L 93.597952,80.231386 L 89.161743,78.686338 L 87.177124,80.587926 L 80.873036,80.231386 L 74.802439,75.952816 L 75.50289,75.12087 L 75.736374,67.039124 L 73.401527,62.998262 L 69.198802,62.404014 L 68.498351,59.789335 L 66.094359,59.304248 L 60.172517,61.44476 L 57.861139,68.167876 L 54.559169,78.588708 L 51.2572,85.311824 L 46.139147,99.934604 L 39.535209,114.05315 L 31.280285,127.16323 L 29.299104,130.18863 L 28.473611,139.09676 L 27.152823,145.31564 L 29.426941,149.98419 L 36.627704,152.49162 L 48.301935,155.58169 L 56.006928,157.72098 L 68.848582,161.99955 L 82.624178,165.56503 L 96.399776,169.60589 M 144.08485,180.96023 L 96.166292,169.45944 L 130.48854,177.92533 L 144.49761,181.0154" id="OR" style="fill:#e9afaf" />
+ <ns0:path d="M 482.58353,129.91009 L 481.88308,121.11525 L 480.0152,113.50891 L 478.14732,99.484714 L 477.68036,89.263683 L 475.81248,85.698205 L 474.17808,80.468846 L 474.17808,69.772421 L 474.87853,65.731548 L 472.88209,60.014242 L 442.87077,59.427825 L 423.88445,58.755513 L 396.8083,57.410889 L 371.33142,55.452236 L 370.0439,70.247815 L 368.64299,85.935913 L 366.33647,111.87386 L 365.67607,124.49947 L 423.04492,128.24621 L 482.58353,129.91009 z " id="ND" style="fill:#f4d7d7" />
+ <ns0:path d="M 484.10703,208.42015 L 483.13305,206.29529 L 481.4161,203.35887 L 483.28398,198.8426 L 484.6849,192.90014 L 481.88308,190.76086 L 481.4161,187.90848 L 482.35005,185.29379 L 484.21793,185.29379 L 484.6849,178.16284 L 484.45141,146.54897 L 483.98444,143.4589 L 479.78172,139.89342 L 478.61429,137.99183 L 478.61429,136.32794 L 480.71565,134.66406 L 482.11657,133.23787 L 482.4668,129.91009 L 423.04492,128.24621 L 365.67608,124.20534 L 364.89307,129.69427 L 363.24575,146.19243 L 361.87193,164.85174 L 360.23755,191.5928 L 376.1145,192.66245 L 396.66115,193.85093 L 414.87297,195.03943 L 439.15538,196.22791 L 450.12916,195.75252 L 452.23052,198.1295 L 457.1337,201.21959 L 458.30112,202.17037 L 462.73733,200.74418 L 466.70658,200.26879 L 469.50839,200.03109 L 471.37627,201.45728 L 476.51293,203.12117 L 479.54822,204.78505 L 480.0152,206.44894 L 480.94914,208.58823 L 482.81702,208.58823 L 484.10703,208.42015 z " id="SD" style="fill:#f4d7d7" />
+ <ns0:path d="M 496.12564,252.80011 L 497.52656,255.41479 L 497.29307,257.79177 L 499.8614,261.83265 L 503.13018,266.11122 L 496.82609,266.11122 L 451.76325,265.63581 L 410.43676,264.20963 L 388.13897,263.37769 L 389.8901,240.2021 L 355.80134,237.34972 L 360.23755,191.5928 L 376.1145,192.66245 L 396.66115,193.85093 L 414.87297,195.03943 L 439.15538,196.22791 L 450.12916,195.75252 L 452.23052,198.1295 L 457.1337,201.21959 L 458.30112,202.17037 L 462.73733,200.74418 L 466.70658,200.26879 L 469.50839,200.03109 L 471.37627,201.45728 L 476.51293,203.12117 L 479.54822,204.78505 L 480.0152,206.44894 L 480.94914,208.58823 L 482.81702,208.58823 L 484.45141,208.46938 L 485.61883,214.05529 L 488.42065,221.89933 L 489.35459,226.891 L 491.68943,230.69417 L 492.38988,236.16123 L 494.02428,240.4398 L 494.25776,247.33306 L 496.23653,253.05224" id="NE" style="fill:#f4d7d7" />
+ <ns0:path d="M 580.12177,205.73586 L 580.18014,207.63744 L 582.51499,208.35053 L 583.44892,209.53902 L 583.91589,211.44061 L 587.88513,215.00608 L 588.58559,217.38307 L 587.88513,220.94855 L 586.01725,224.75171 L 585.3168,227.36639 L 582.98196,229.26798 L 581.11408,229.98108 L 575.74393,231.40726 L 575.04347,233.30885 L 574.34302,235.44814 L 575.04347,236.87433 L 576.91135,238.53822 L 576.67787,242.81678 L 574.80999,244.48067 L 574.10954,246.14456 L 574.10954,248.99694 L 572.24166,249.47233 L 570.60726,250.66083 L 570.37378,252.08702 L 570.60726,254.22631 L 568.85613,256.06846 L 565.4706,252.56242 L 564.30317,250.18543 L 556.3647,250.89852 L 546.32485,251.37392 L 520.40805,252.32472 L 506.63246,252.56242 L 497.05959,252.80011 L 495.9476,252.92617 L 494.25776,247.33306 L 494.02428,240.4398 L 492.38988,236.16123 L 491.68943,230.69417 L 489.35459,226.891 L 488.42065,221.89933 L 485.61883,214.05529 L 484.45141,208.46938 L 483.0505,206.21125 L 481.4161,203.35887 L 483.28398,198.8426 L 484.6849,192.90014 L 481.88308,190.76086 L 481.4161,187.90848 L 482.35005,185.29379 L 484.10119,185.29379 L 495.89216,185.29379 L 546.55834,184.5807 L 565.00364,183.86761 L 569.20636,183.74877 L 569.90681,187.19538 L 572.24166,188.85927 L 572.47514,190.28546 L 570.37378,193.85093 L 570.60726,197.17871 L 573.1756,201.21959 L 575.74393,202.40807 L 578.77923,202.88347 L 580.12177,205.73586 z " id="IA" style="fill:#e9afaf" />
+ <ns0:path d="M 639.20393,481.84625 L 638.01716,483.15227 L 632.73401,483.15227 L 631.24813,482.31188 L 629.10185,481.97572 L 622.16771,483.99266 L 620.35163,483.15227 L 617.71005,487.52229 L 616.58406,488.3312 L 615.43633,485.74444 L 614.2689,481.70357 L 610.76664,478.3758 L 611.93406,470.53175 L 611.23361,469.58096 L 609.36573,469.81866 L 600.96028,470.53175 L 576.2109,471.24485 L 575.74393,469.58096 L 576.44438,461.26152 L 579.94666,454.84366 L 585.3168,445.33573 L 584.38287,443.19645 L 585.55029,443.19645 L 586.25075,439.86868 L 583.91589,437.96709 L 584.14938,436.0655 L 582.04802,431.31154 L 581.75616,425.75534 L 583.15706,422.99209 L 582.74847,418.47583 L 581.34756,415.38574 L 582.74847,413.95956 L 581.34756,411.82028 L 581.81454,409.91869 L 582.74847,403.50083 L 585.78377,400.64846 L 585.08332,398.50917 L 588.81908,393.0421 L 591.62089,392.09132 L 591.62089,389.47664 L 590.92044,388.05044 L 593.72225,382.58339 L 596.52407,381.39489 L 596.63379,377.84737 L 605.49374,377.76685 L 630.09345,375.74991 L 635.69855,375.51222 L 635.7068,382.13688 L 635.8719,399.44893 L 635.04641,431.71994 L 634.88131,446.34274 L 637.68799,465.83981 L 639.20393,481.84625 z " id="MS" style="fill:#e9afaf" />
+ <ns0:path d="M 632.23973,310.19942 L 632.07463,306.16555 L 632.56993,301.45935 L 634.88131,298.43395 L 636.6974,294.40007 L 639.33898,290.03004 L 638.84368,283.97923 L 637.0276,281.12189 L 636.6974,277.76034 L 637.52289,272.04567 L 637.0276,264.81831 L 635.7068,248.17858 L 634.38601,232.21115 L 633.3949,220.02589 L 636.53128,220.95071 L 638.01716,221.95917 L 639.17285,221.62302 L 641.31913,219.60608 L 644.20888,217.92491 L 649.40999,217.75643 L 671.86343,215.40333 L 678.13717,214.73102 L 678.30227,215.90756 L 679.78816,231.20268 L 681.60425,245.48932 L 684.24582,269.86066 L 684.74112,275.7434 L 684.24582,278.09649 L 685.73171,279.60919 L 686.06192,281.62613 L 683.25523,283.64307 L 679.29287,285.49193 L 676.48618,285.82809 L 675.99089,290.53428 L 671.20302,294.56815 L 668.23125,298.26587 L 668.56145,300.61896 L 667.73595,302.80398 L 662.94809,302.80398 L 661.7924,301.1232 L 660.63671,301.96359 L 657.66493,303.64438 L 657.83004,307.17401 L 655.68375,307.67825 L 654.85825,306.5017 L 652.87707,304.82092 L 649.90529,306.33362 L 648.08921,309.69518 L 646.27312,308.85479 L 644.78724,306.83786 L 641.15506,307.34209 L 635.2115,308.35056 L 632.23973,310.19942 z " id="IN" style="fill:#de8787" />
+ <ns0:path d="M 632.07463,310.03134 L 632.07463,306.16555 L 632.56993,301.45935 L 634.88131,298.43395 L 636.6974,294.40007 L 639.33898,290.03004 L 638.84368,283.97923 L 637.0276,281.12189 L 636.6974,277.76034 L 637.52289,272.04567 L 637.0276,264.81831 L 635.7068,248.17858 L 634.38601,232.21115 L 633.56001,220.10992 L 632.23872,219.26993 L 631.41322,216.58068 L 630.09244,212.71489 L 628.44145,210.86603 L 626.95557,208.17679 L 626.71703,202.46993 L 616.60376,203.83427 L 588.81909,205.617 L 579.94666,205.17133 L 580.18014,207.63744 L 582.51499,208.35053 L 583.44892,209.53902 L 583.91589,211.44061 L 587.88513,215.00608 L 588.58559,217.38307 L 587.88513,220.94855 L 586.01725,224.75171 L 585.3168,227.36639 L 582.98196,229.26798 L 581.11408,229.98108 L 575.74393,231.40726 L 575.04347,233.30885 L 574.34302,235.44814 L 575.04347,236.87433 L 576.91135,238.53822 L 576.67787,242.81678 L 574.80999,244.48067 L 574.10954,246.14456 L 574.10954,248.99694 L 572.24166,249.47233 L 570.60726,250.66083 L 570.37378,252.08702 L 570.60726,254.22631 L 568.85613,255.59306 L 567.80545,258.50488 L 568.27242,262.30804 L 570.60726,269.91439 L 578.07878,277.75843 L 583.68241,281.56161 L 583.44892,286.07787 L 584.38287,287.50407 L 590.92044,287.97946 L 593.72225,289.40566 L 593.0218,293.20882 L 590.68696,299.38898 L 589.98649,302.71676 L 592.32134,306.75762 L 598.85891,312.22469 L 603.52862,312.93778 L 605.62997,318.16715 L 607.73133,321.49492 L 606.7974,324.58499 L 608.43179,328.86356 L 610.29967,331.00285 L 613.32836,330.65102 L 613.91377,328.51994 L 616.22516,326.67109 L 618.37144,325.99877 L 621.17811,327.3434 L 624.81029,328.68802 L 625.96598,328.35186 L 626.13108,325.99877 L 624.81029,323.47759 L 625.14049,321.1245 L 627.12167,319.6118 L 629.76325,318.93949 L 631.41424,318.26718 L 630.58875,316.41831 L 629.92835,314.40138 L 631.08404,313.56099 L 632.07463,310.03134 z " id="IL" style="fill:#782121" />
+ <ns0:path d="M 482.35005,129.91009 L 481.88308,121.11525 L 480.0152,113.50891 L 478.14732,99.484714 L 477.68036,89.263683 L 475.81248,85.698205 L 474.17808,80.468846 L 474.17808,69.772421 L 474.87853,65.731548 L 473.01887,60.063466 L 503.79211,60.100136 L 504.1223,51.528162 L 504.7827,51.360084 L 507.09408,51.864318 L 509.07526,52.704708 L 509.90075,58.419357 L 511.38664,64.806318 L 513.03762,66.487097 L 517.99058,66.487097 L 518.32077,67.999799 L 524.75961,68.335954 L 524.75961,70.520967 L 529.71257,70.520967 L 530.04276,69.176344 L 531.19845,67.999799 L 533.50983,67.327487 L 534.83062,68.335954 L 537.80239,68.335954 L 541.76476,71.025201 L 547.21301,73.54637 L 549.68948,74.050604 L 550.18478,73.042136 L 551.67066,72.537902 L 552.16596,75.563305 L 554.80753,76.907928 L 555.30283,76.403695 L 556.62362,76.571773 L 556.62362,78.756786 L 559.26519,79.765253 L 562.40206,79.765253 L 564.05305,78.924863 L 567.35502,75.563305 L 569.99659,75.059071 L 570.82209,76.907928 L 571.31738,78.252552 L 572.30797,78.252552 L 573.29856,77.412162 L 582.37898,77.076006 L 584.19506,80.269487 L 584.85546,80.269487 L 585.58425,79.142165 L 590.11857,78.756786 L 589.49346,81.126733 L 585.47097,83.036786 L 576.02857,87.25913 L 571.15228,89.345695 L 568.01541,92.034941 L 565.53894,95.732656 L 563.22756,99.766526 L 561.41147,100.60692 L 556.78872,105.81733 L 555.46793,105.98541 L 552.00086,109.17889 L 552.70347,109.74365 L 549.82713,112.55812 L 549.59365,115.4105 L 549.59365,124.20534 L 548.42622,125.86923 L 543.05607,129.91009 L 540.72123,136.09025 L 541.18819,136.32794 L 543.75652,138.46723 L 544.45698,141.79501 L 542.5891,145.12278 L 542.5891,149.16365 L 543.05607,156.0569 L 546.09137,159.14699 L 549.59365,159.14699 L 551.46153,162.47476 L 554.96379,162.95015 L 558.93303,168.89261 L 566.17105,173.17118 L 568.27242,176.02356 L 569.20636,183.86761 L 565.00364,183.86761 L 546.55834,184.5807 L 495.89216,185.29379 L 484.10119,185.29379 L 484.6849,178.16284 L 484.45141,146.54897 L 483.98444,143.4589 L 479.78172,139.89342 L 478.61429,137.99183 L 478.61429,136.32794 L 480.71565,134.66406 L 482.11657,133.23787 L 482.35005,129.91009 z " id="MN" style="fill:#d35f5f" />
+ <ns0:path d="M 626.6436,202.64577 L 626.79047,198.26019 L 625.13948,193.55401 L 624.47909,187.16705 L 623.3234,184.64588 L 624.31399,181.4524 L 625.13948,178.42699 L 626.62537,175.73775 L 625.96497,172.20811 L 625.30458,168.5104 L 625.79988,166.66154 L 627.78106,164.14037 L 627.94616,161.28305 L 627.12066,159.93842 L 627.78106,157.24918 L 628.27635,153.88762 L 631.08303,148.00489 L 634.0548,140.94562 L 634.2199,138.59253 L 633.8897,137.58406 L 633.06421,138.08829 L 628.77165,144.64333 L 625.96497,148.84528 L 623.98379,150.69414 L 623.1583,153.04723 L 621.67241,153.88762 L 620.51673,155.90455 L 619.03084,155.5684 L 618.86574,153.71954 L 620.18653,151.19837 L 622.33281,146.32411 L 624.14889,144.64333 L 625.27363,142.26081 L 623.6083,141.31962 L 622.20739,139.89342 L 620.57299,129.197 L 616.83724,128.00851 L 615.43633,125.63153 L 602.59467,122.77914 L 600.02634,121.59066 L 591.62089,119.21367 L 583.21544,118.02518 L 578.9573,112.40581 L 578.41662,113.71699 L 577.26093,113.54892 L 576.60053,112.37237 L 573.79386,111.53198 L 572.63817,111.70006 L 570.82209,112.70853 L 569.8315,112.03621 L 570.49189,110.01928 L 572.47307,106.8258 L 573.62876,105.64925 L 571.64758,104.13655 L 569.5013,104.97694 L 566.52953,106.99388 L 558.935,110.35543 L 555.96322,111.02775 L 552.99145,110.52351 L 551.98885,109.6104 L 549.82713,112.55812 L 549.59365,115.4105 L 549.59365,124.20534 L 548.42622,125.86923 L 543.05607,129.91009 L 540.72123,136.09025 L 541.18819,136.32794 L 543.75652,138.46723 L 544.45698,141.79501 L 542.5891,145.12278 L 542.5891,149.16365 L 543.05607,156.0569 L 546.09137,159.14699 L 549.59365,159.14699 L 551.46153,162.47476 L 554.96379,162.95015 L 558.93303,168.89261 L 566.17105,173.17118 L 568.27242,176.02356 L 569.20636,183.74877 L 569.90681,187.19538 L 572.24166,188.85927 L 572.47514,190.28546 L 570.37378,193.85093 L 570.60726,197.17871 L 573.1756,201.21959 L 575.74393,202.40807 L 578.77923,202.88347 L 580.03422,205.49817 L 589.40281,205.49815 L 616.60376,203.83427 L 626.6436,202.64577 z " id="WI" style="fill:#de8787" />
+ <ns0:path d="M 568.73938,255.89021 L 565.4706,252.56242 L 564.30317,250.18543 L 556.3647,250.89852 L 546.32485,251.37392 L 520.40805,252.32472 L 506.63246,252.56242 L 498.57727,252.68126 L 496.24239,252.80011 L 497.52656,255.41479 L 497.29307,257.79177 L 499.8614,261.83265 L 503.01343,266.11122 L 506.16549,268.96359 L 508.50034,269.20129 L 509.90124,270.15209 L 509.90124,273.24216 L 508.03337,274.90605 L 507.56639,277.28304 L 509.66775,280.84852 L 512.23609,283.93859 L 514.80442,285.84018 L 516.20533,297.96278 L 515.50487,334.68718 L 515.73836,339.55999 L 516.20533,346.69094 L 540.02077,346.21554 L 563.83621,345.50245 L 585.08332,344.55165 L 596.29058,344.07626 L 598.15846,347.16634 L 597.69149,349.54332 L 594.4227,352.3957 L 593.72225,355.48577 L 600.02634,355.96118 L 605.163,355.24808 L 607.26436,348.59252 L 607.11843,342.73921 L 610.06619,341.22388 L 611.46709,339.55999 L 613.56845,338.3715 L 613.80194,335.04372 L 614.73588,333.14213 L 613.33497,330.61659 L 610.29967,331.00285 L 608.43179,328.86356 L 606.7974,324.58499 L 607.73133,321.49492 L 605.62997,318.16715 L 603.52862,312.93778 L 598.85891,312.22469 L 592.32134,306.75762 L 589.98649,302.71676 L 590.68696,299.38898 L 593.0218,293.20882 L 593.72225,289.40566 L 590.92044,287.97946 L 584.38287,287.50407 L 583.44892,286.07787 L 583.68241,281.56161 L 578.07878,277.75843 L 570.60726,269.91439 L 568.27242,262.30804 L 567.80545,258.50488 L 568.73938,255.89021 z " id="MO" style="fill:#de8787" />
+ <ns0:path d="M 604.99844,354.98628 L 600.02634,355.96118 L 593.72225,355.48577 L 594.4227,352.3957 L 597.69149,349.54332 L 598.15846,347.16634 L 596.29058,344.07626 L 585.08332,344.55165 L 563.83621,345.50245 L 540.02077,346.21554 L 516.20533,346.69094 L 517.83972,353.82189 L 517.83971,362.37904 L 519.24063,373.78867 L 519.47412,413.12761 L 521.80896,415.14805 L 524.84426,413.72186 L 527.64608,414.91035 L 528.31735,426.58728 L 550.99455,426.55757 L 570.60726,425.60677 L 581.63941,425.75534 L 583.15706,422.99209 L 582.74847,418.47583 L 581.34756,415.38574 L 582.74847,413.95956 L 581.34756,411.82028 L 581.81454,409.91869 L 582.74847,403.50083 L 585.78377,400.64846 L 585.08332,398.50917 L 588.81908,393.0421 L 591.62089,392.09132 L 591.62089,389.47664 L 590.92044,388.05044 L 593.72225,382.58339 L 596.52407,381.39489 L 596.22104,377.59524 L 598.72469,376.42223 L 599.71528,371.54796 L 598.22939,367.85023 L 602.35687,365.49714 L 602.68706,362.80789 L 604.06449,358.25292 L 604.99844,354.98628 z " id="AR" style="fill:#e9afaf" />
+ <ns0:path d="M 383.76113,331.71594 L 372.84567,331.2406 L 366.27891,330.73549 L 366.54158,330.94347 L 365.98708,342.94722 L 388.2557,344.07626 L 420.00963,345.26475 L 418.60872,369.98537 L 418.14175,388.52584 L 418.37523,390.18973 L 422.81144,393.9929 L 424.9128,395.18139 L 425.61327,394.94369 L 426.31372,392.80441 L 427.71463,394.70599 L 429.81599,394.70599 L 429.81599,393.2798 L 432.6178,394.70599 L 432.15084,398.74687 L 436.35356,398.98457 L 438.92189,400.17306 L 443.12462,400.88615 L 445.69295,402.78774 L 448.02779,400.64846 L 451.53007,401.36155 L 454.0984,404.92703 L 455.03233,404.92703 L 455.03233,407.30401 L 457.36718,408.0171 L 459.70203,405.64012 L 461.5699,406.35321 L 464.13823,406.35321 L 465.07218,408.9679 L 469.97536,410.86948 L 471.37627,410.15638 L 473.24415,405.87781 L 474.41156,405.87781 L 475.57899,408.0171 L 479.78172,408.73019 L 483.51747,410.15638 L 486.55277,411.10718 L 488.42065,410.15638 L 489.1211,407.54171 L 493.55731,407.54171 L 495.65868,408.49249 L 498.46049,406.35321 L 499.62792,406.35321 L 500.32837,408.0171 L 504.53109,408.0171 L 506.16549,405.87781 L 508.03337,406.35321 L 510.13473,408.9679 L 513.40351,410.86948 L 516.6723,411.82028 L 519.47412,413.48417 L 519.24063,373.78867 L 517.83971,362.37904 L 517.83972,353.82189 L 516.20533,346.69094 L 515.73836,339.55999 L 515.50487,334.92488 L 501.9627,335.75681 L 454.56566,335.28143 L 408.56892,333.14212 L 383.76113,331.71594 z " id="OK" style="fill:#e9afaf" />
+ <ns0:path d="M 515.50487,335.04372 L 501.9627,335.75681 L 454.56566,335.28143 L 408.56892,333.14212 L 383.90706,331.83479 L 388.13897,263.37769 L 410.43676,264.20963 L 451.76325,265.63581 L 496.82609,266.11122 L 503.01343,266.11122 L 506.16549,268.96359 L 508.50034,269.20129 L 509.90124,270.15209 L 509.90124,273.24216 L 508.03337,274.90605 L 507.56639,277.28304 L 509.66775,280.84852 L 512.23609,283.93859 L 514.80442,285.84018 L 516.20533,297.96278 L 515.50487,335.04372 z " id="KS" style="fill:#e9afaf" />
+ <ns0:path d="M 616.71945,488.11058 L 615.43633,485.74444 L 614.2689,481.70357 L 610.76664,478.3758 L 611.93406,470.53175 L 611.23361,469.58096 L 609.36573,469.81866 L 600.96028,470.53175 L 576.2109,471.24485 L 575.74393,469.58096 L 576.44438,461.26152 L 579.94666,454.84366 L 585.3168,445.33573 L 584.38287,443.19645 L 585.55029,443.19645 L 586.25075,439.86868 L 583.91589,437.96709 L 584.14938,436.0655 L 582.04802,431.31154 L 581.69779,425.60677 L 570.60726,425.60677 L 550.99455,426.55757 L 528.31735,426.58728 L 528.34653,436.5409 L 529.04699,446.28652 L 529.74745,450.3274 L 532.31578,454.60597 L 533.24971,459.83533 L 537.68592,465.54009 L 537.9194,468.86787 L 538.61987,469.58096 L 537.9194,478.3758 L 534.88411,483.60516 L 536.5185,485.74444 L 535.81804,488.35912 L 535.11759,495.96547 L 533.71668,499.29324 L 533.84174,503.05325 L 538.62788,501.47276 L 546.88281,501.1366 L 557.44911,504.83432 L 564.05305,506.01086 L 567.85031,504.49816 L 571.15228,505.67471 L 574.45425,506.68317 L 575.27974,504.49816 L 571.97778,503.32162 L 569.3362,503.82585 L 566.52953,502.14507 C 566.52953,502.14507 566.69462,500.80045 567.35502,500.63237 C 568.01541,500.46429 570.49189,499.6239 570.49189,499.6239 L 572.30797,501.1366 L 574.12406,500.12814 L 577.42603,500.80045 L 578.91191,503.32162 L 579.24211,505.67471 L 583.86487,506.01086 L 585.68095,507.85972 L 584.85546,509.5405 L 583.53467,510.38089 L 585.18565,512.06167 L 593.77077,515.75938 L 597.40294,514.41476 L 598.39353,511.89359 L 601.03511,511.22128 L 602.85119,509.70858 L 604.17198,510.71704 L 604.99747,513.74245 L 602.68609,514.58284 L 603.34648,515.25515 L 606.81355,513.91053 L 609.12493,510.38089 L 609.95042,509.87666 L 607.80414,509.5405 L 608.62964,507.85972 L 608.46454,506.34702 L 610.61082,505.84279 L 611.76651,504.49816 L 612.4269,505.33855 C 612.4269,505.33855 612.2618,508.53203 613.08729,508.53203 C 613.91279,508.53203 617.37985,509.20434 617.37985,509.20434 L 621.50732,511.22128 L 622.49791,512.73398 L 625.46968,512.73398 L 626.62537,513.74245 L 628.93675,510.54897 L 628.93675,509.03627 L 627.61596,509.03627 L 624.14889,506.17894 L 618.20535,505.33855 L 614.90338,502.98546 L 616.05907,500.12814 L 618.37045,500.46429 L 618.53554,499.79198 L 616.71946,498.78351 L 616.71946,498.27928 L 620.02143,498.27928 L 621.83751,495.0858 L 620.51673,493.06886 L 620.18653,490.21154 L 618.70064,490.37962 L 616.71946,492.56463 L 616.05907,495.25388 L 612.9222,494.58156 L 611.93161,492.73271 L 613.74769,490.71577 L 615.81141,488.86693 L 616.71945,488.11058 z " id="LA" style="fill:#de8787" />
+ <ns0:path d="M 817.62464,258.28441 L 818.55858,256.38283 L 820.84673,258.09426 L 819.7727,260.66139 L 818.09161,259.04505 L 817.62464,258.28441 z " id="path6656" style="fill:#0000ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <ns0:g id="g8180" style="fill:#cccccc">
+ <ns0:path d="M 738.7284,305.32516 L 740.70958,309.02287 L 742.03037,310.36749 L 745.00215,311.2079 L 747.64373,310.53557 L 750.12022,308.18248 C 750.12022,308.18248 751.9363,309.69518 752.59669,309.52711 C 753.25709,309.35903 757.05436,308.51864 757.05436,308.51864 L 758.87045,303.64438 L 761.51202,304.65285 L 764.814,302.13167 L 766.29989,302.46782 L 768.44617,300.45089 L 768.61127,297.59356 L 767.78577,296.24894 L 772.90384,284.9877 L 775.05012,278.09649 L 775.38031,273.55838 L 777.1964,273.3903 L 778.84739,276.24763 L 780.16818,277.08802 L 782.80976,277.08802 L 783.96545,271.37336 L 784.29564,268.17988 L 787.76272,267.84373 L 788.25802,265.49063 L 791.39489,262.96946 L 792.05529,261.28868 L 793.70628,258.43134 L 794.20157,256.24633 L 794.36667,250.69975 L 798.98943,252.5486 L 804.7679,255.7421 L 805.59338,250.44764 L 809.88595,252.5486 L 809.88595,255.57402 L 815.49931,256.91864 L 817.48049,258.26326 L 818.47108,256.24633 L 820.78247,257.92711 L 819.29657,261.28868 L 818.96637,264.146 L 817.15029,266.83525 L 817.15029,269.02027 L 817.81068,270.86913 L 822.98233,272.27864 L 824.90863,273.89525 L 830.19178,274.23141 L 832.83336,276.5845 L 836.13533,277.25681 L 837.45611,278.60143 L 836.96082,283.30762 L 837.95141,284.31608 L 838.11651,286.66917 L 839.43729,288.85419 L 839.2722,290.70305 L 835.97023,289.5265 L 835.97023,290.53497 L 837.95141,292.21575 L 837.95141,293.39229 L 839.43729,294.56884 L 840.75808,296.24962 L 840.92318,298.60271 L 838.6118,300.11541 L 838.942,300.61964 L 841.58358,300.11541 L 844.88554,299.4431 L 846.04123,299.27502 L 850.20356,306.58097 L 845.21707,308.35056 L 833.49506,311.37597 L 813.92151,315.35219 L 792.88078,319.27565 L 775.38031,321.96489 L 759.22415,323.98183 L 751.9363,325.32646 L 747.32856,324.68385 L 745.16725,324.65414 L 742.69078,326.67109 L 734.60093,326.83916 L 722.3505,328.8153 L 712.13922,329.78475 L 714.95419,328.35186 L 720.73264,324.82222 L 724.69501,322.6372 L 724.69501,320.45219 L 726.51109,318.60333 L 731.13386,313.05675 L 735.42643,309.35903 L 738.7284,305.32516 z " id="VA" style="fill:#d35f5f" />
+ <ns0:path d="M 845.69487,293.77543 L 844.35758,290.76684 L 844.75381,282.95122 L 847.26331,278.51396 L 847.13123,274.21116 L 853.07478,271.9253 L 852.41438,274.21116 L 849.24449,278.3795 L 848.92256,286.54808 L 848.17135,289.94326 L 846.33876,293.85948 L 845.69487,293.77543 z " id="path3106" style="fill:#d35f5f" ns1:nodetypes="cccccccccccc" />
+ </ns0:g>
+ <ns0:path d="M 467.38967,586.18345 L 466.81183,578.788 L 464.00514,571.30851 L 463.42729,563.99709 L 464.99573,555.42509 L 468.38027,548.28176 L 471.92989,542.65112 L 475.14933,538.95339 L 475.80972,539.20552 L 470.9393,546.09673 L 466.48163,552.90391 L 464.41788,559.79513 L 464.08769,565.17365 L 464.99573,571.56063 L 467.63732,579.04012 L 468.13261,584.41863 L 468.29771,585.93134 L 467.38967,586.18345 z M 465.65436,588.7052 L 466.64495,587.1925 L 464.82887,584.33517 L 463.83828,577.94821 L 462.0222,570.72086 L 461.1967,568.19969 L 462.0222,563.49351 L 463.17788,559.45964 L 464.49867,554.75346 L 465.98456,549.03881 L 463.67318,547.02188 L 464.66377,545.00494 L 468.79123,544.66878 L 472.7536,538.95414 L 476.22066,538.28182 L 482.16421,534.58411 L 483.98029,533.07141 L 490.41913,529.54177 L 496.19758,527.0206 L 501.64583,523.65904 L 504.4525,521.47403 L 510.23095,515.92746 L 511.55174,515.08707 L 513.69802,513.57437 L 516.33959,511.55743 L 517.33018,509.5405 L 527.40119,504.83432 L 534.17024,502.98546" id="TX_Gulf" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 900.52373,145.31564 L 900.85393,143.29871 L 902.00961,139.60099" id="NH_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 919.55232,177.09192 L 921.77043,176.37882 L 922.23741,174.59609 L 923.28809,174.71493 L 924.33877,177.09192 L 923.0546,177.56732 L 919.08535,177.68617 L 919.55232,177.09192 z M 909.97943,177.92387 L 912.31427,175.19033 L 913.94868,175.19033 L 915.81656,176.73537 L 913.36497,177.80501 L 911.14686,178.87466 L 909.97943,177.92387 z M 903.66061,177.08236 L 906.63237,175.56967 L 906.13708,173.21658 L 906.96257,171.70388 L 909.93434,170.19118 L 910.75983,173.38466 L 910.26454,175.23351 L 907.78806,176.74621 L 907.78806,177.75468 L 909.76924,176.24198 L 913.73161,171.5358 L 917.69397,169.51886 L 921.98653,168.00616 L 921.65633,165.48499 L 920.66574,162.45959 L 918.68456,159.93842 L 916.86848,159.09803 L 914.7222,159.26611 L 914.2269,159.77034 L 915.21749,161.11497 L 916.70338,160.27458 L 918.84966,161.95536 L 919.67515,164.81268 L 917.85907,166.66154 L 915.54769,167.67001 L 911.91552,167.16577 L 907.95316,160.94689 L 905.64178,158.25764 L 903.8257,158.25764 L 902.67001,159.09803 L 900.68883,156.40879 L 901.01902,154.89608 L 903.4955,149.51759 L 900.35862,144.81139" id="MA_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 771.67854,458.60093 L 771.08653,452.05785 L 773.39791,441.63702 L 774.88379,437.26699 L 774.3885,434.57775 L 778.51596,427.3504 L 777.85557,425.66962" id="GA_Atlantic" style="fill:#6666e6;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 777.85557,425.66962 L 780.00185,424.32499 L 785.1199,418.61034 L 784.12931,415.24879 L 787.10108,415.08071 L 790.73325,411.55107 L 792.38423,410.71068 L 794.69561,407.18104 L 797.50228,404.32372 L 799.64856,400.62601 L 802.12504,399.95369 L 803.28073,397.09637 L 804.93171,396.25598 L 805.42701,389.70094 L 808.06859,383.31398 L 813.51684,377.43125" id="SC_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 813.18663,377.59933 L 819.46038,375.41432 L 824.24824,374.91008 L 824.74353,372.38892 L 826.72471,365.6658 L 830.19178,360.79154 L 836.79572,355.24497 L 842.07887,352.7238 L 844.88554,352.05149 L 846.04123,352.55572 L 847.36202,352.55572 L 850.49889,347.51338 L 852.48007,343.81567 L 851.15929,344.3199 L 848.84791,346.67299 L 848.18751,344.99221 L 843.89495,344.99221 L 845.87614,338.43717 L 845.05064,337.09255 L 843.06946,337.09255 L 843.06946,336.08408 L 842.73926,334.73946 L 844.39025,336.08408 L 845.87614,336.25216 L 848.35261,336.58832 L 852.14988,334.90754 L 853.47066,331.88214 L 854.13106,329.69712 L 856.77263,328.3525 L 857.10283,323.98247 L 856.27734,323.31016 L 858.75382,323.14208 L 858.09342,320.78899 L 855.61694,318.26782 L 851.98478,311.54471 L 850.1687,306.50237 M 854.21672,340.95692 L 856.85831,338.3517 L 860.07773,335.66244 L 861.64617,334.99013 L 861.81127,332.88915 L 861.15088,326.50217 L 859.66499,324.06503 L 859.00459,322.13213 L 859.74753,321.88001 L 862.55422,327.59468 L 862.96697,332.21684 L 862.80187,335.74649 L 859.33479,337.34323 L 856.44555,339.86441 L 855.28987,341.125 L 854.21672,340.95692 z " id="NC_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 850.1687,306.50237 L 846.04123,299.27502 L 844.88554,299.4431 L 841.58358,300.11541 L 838.942,300.61964 L 838.6118,300.11541 L 840.92318,298.60271 L 840.75808,296.24962 L 839.43729,294.56884 L 837.95141,293.39229 L 837.95141,292.21575 L 835.97023,290.53497 L 835.97023,289.5265 L 839.2722,290.70305 L 839.43729,288.85419 L 838.11651,286.66917 L 837.95141,284.31608 L 836.96082,283.30762 L 837.45611,278.60143 L 836.13533,277.25681 L 832.83336,276.5845 L 830.19178,274.23141 L 824.90863,273.89525 L 822.59725,272.21447" id="VA_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 893.09433,183.30123 L 896.06607,182.29279 L 898.54255,180.27585 L 899.69824,178.42699 L 901.01902,178.59507 L 903.99082,176.91428" id="RI_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 865.2752,198.17615 L 870.47581,194.73055 L 874.10797,191.36899 L 876.08916,189.18398 L 876.91465,189.85629 L 879.72132,188.34359 L 885.00447,187.16705 L 893.58963,183.30123" id="CT_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 840.75808,236.41389 L 841.91377,238.76697 L 845.21574,241.79237 L 850.1687,244.14546 L 854.29616,244.81777 L 854.46126,246.33047 L 853.63576,247.33894 L 853.96596,250.19627 L 854.79145,250.19627 L 856.93773,247.6751 L 857.76322,242.63276 L 860.5699,238.43081 L 863.70677,231.70769 L 864.86246,225.99305 L 864.20207,224.8165 L 864.03697,215.06798 L 862.38598,211.53834 L 861.23029,212.37873 L 858.42362,212.71489 L 857.92832,212.21066 L 859.08401,211.20219 L 861.23029,209.18525 L 861.06519,207.84063" id="NJ_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 861.06519,207.84063 L 863.04638,208.51294 L 867.17384,207.3364 L 873.11738,205.31946 L 875.75896,204.31099 L 883.02329,198.76442 L 886.98565,195.73902 L 890.45272,192.0413 L 886.16016,190.36053 L 884.83937,191.87323 L 881.8676,194.73055 L 873.77778,198.76442 L 871.4664,198.59634 L 869.81541,197.92403 L 868.65972,198.59634 L 866.34835,201.28559 L 864.86246,202.63021 L 863.54167,202.96637 L 863.21147,201.62175 L 865.19266,199.77289 L 865.52285,197.75595" id="NY_Atlantic" style="fill:#0000ff;fill-opacity:0;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 854.95655,260.95325 L 852.64517,253.38975 L 851.65458,253.89398 L 848.02242,251.37281 L 846.20633,246.49855 L 844.22515,242.80084 L 841.91377,241.79237 L 839.76749,238.09466 L 840.59298,235.90964" id="DE_Atlantic" style="fill:#cccccc;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 29.464207,149.85375 L 27.813223,154.89608 L 27.648124,162.62767 L 22.364973,174.89736 L 19.228102,177.5866 L 18.897905,178.76315 L 17.081822,179.60354 L 15.595936,183.97356 L 14.770444,187.33512 L 17.577118,191.70515 L 19.228102,196.07518 L 20.383791,199.77289 L 20.053595,206.49601 L 18.237511,209.68949 L 17.577118,215.74029 L 16.586527,219.60608 L 18.40261,223.63995 L 21.209284,228.34614 L 23.520662,233.38847 L 24.84145,237.59042 L 24.511253,240.95198 L 24.181056,241.45621 L 24.181056,243.64123 L 29.959503,250.19627 L 29.464207,252.71743 L 28.803813,255.07053 L 28.14342,257.08746 L 28.308518,265.65943 L 30.454798,269.52523 L 32.43598,272.21447 L 35.242654,272.71871 L 36.233244,275.57603 L 35.077555,279.27375 L 32.931275,280.95453 L 31.775586,280.95453 L 30.950093,284.9884 L 31.445389,288.0138 L 34.747358,292.5519 L 36.398343,298.09847 L 37.884229,302.97273 L 39.205017,306.16621 L 42.672085,312.21702 L 44.157971,314.90627 L 44.653266,317.93167 L 46.304251,318.94014 L 46.304251,321.4613 L 45.478759,323.47824 L 43.662676,330.87367 L 43.16738,332.8906 L 45.643857,335.74793 L 49.936417,336.25216 L 54.559175,338.10102 L 58.521538,340.28603 L 61.49331,340.28603 L 64.465083,343.47951 L 67.106658,348.52185 L 68.262347,350.87494 L 72.224711,353.05995 L 77.177665,353.90034 L 78.663551,356.08536 L 79.323945,359.44692 L 77.838059,360.11923 L 78.168256,361.12769 L 81.470225,361.96808 L 84.276899,362.13616 L 87.248671,367.01042 L 91.211035,371.38045 L 92.036527,373.73354 L 94.678102,378.10356 L 95.008299,381.46512 L 95.008299,391.21364 L 95.503595,393.0625 M 50.266945,346.75563 L 51.587737,348.35237 L 51.422639,349.697 L 48.120658,349.61296 L 47.542811,348.35237 L 46.882415,346.83966 L 50.266945,346.75563 z M 52.248133,346.75563 L 53.486376,346.08332 L 57.118555,348.26833 L 60.255437,349.52892 L 59.347393,350.20124 L 54.724619,349.94912 L 53.073629,348.26833 L 52.248133,346.75563 z M 73.380812,367.34524 L 75.196895,369.78238 L 76.022398,370.79086 L 77.590839,371.37912 L 78.168678,369.86642 L 77.178087,368.01756 L 74.453957,365.91658 L 73.380812,366.08465 L 73.380812,367.34524 z M 71.894915,376.33744 L 73.711009,379.61497 L 74.949253,381.63192 L 73.463356,381.88403 L 72.142568,380.62344 C 72.142568,380.62344 71.39962,379.11074 71.39962,378.69054 C 71.39962,378.27035 71.39962,376.42148 71.39962,376.42148 L 71.894915,376.33744 z " id="CA_Pacific" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 95.99889,2.9536428 L 94.843201,3.7940324 L 94.513004,4.634422 L 96.989481,9.6767597 L 98.14517,12.366006 L 96.329087,16.063721 L 96.329087,18.416812 L 96.989481,19.929513 L 96.163989,21.77837 L 96.659284,25.139928 L 97.649875,26.484552 L 97.484776,27.829175 L 95.99889,27.997253 L 95.338496,25.980318 L 94.182807,23.459149 L 92.366724,21.946448 L 92.696921,19.761435 L 95.008299,19.257201 L 94.678102,17.408344 L 94.347905,16.231799 L 92.201625,17.576422 L 90.880838,18.752967 L 90.880838,21.274136 L 88.569459,21.442214 L 85.102391,20.433747 L 82.130619,18.921045 L 78.993748,18.248734 L 74.370991,16.063721 L 71.069021,14.046786 L 68.262347,11.357539 L 65.78587,8.3321363 L 63.63959,7.8279025 L 61.328212,17.576422 L 63.144295,20.93798 L 63.144295,29.173799 L 62.483901,32.199201 L 63.969787,39.59463 L 66.776461,42.451954 L 62.318803,43.124266 L 62.153704,46.990058 L 64.79528,48.166604 L 63.144295,52.368552 L 60.337621,52.704708 L 60.007424,55.73011 L 62.318803,58.755513 L 64.134886,57.747045 L 66.446264,59.427825 M 86.341089,9.169955 L 88.404826,9.001877 L 88.900121,10.430545 L 90.468562,8.7497548 L 92.862495,8.7497548 L 93.687987,10.3465 L 92.119546,12.111324 L 92.779951,12.951724 L 92.037002,15.052704 L 90.63366,15.472893 C 90.63366,15.472893 89.725613,15.556938 89.725613,15.220782 C 89.725613,14.884626 91.21151,12.531524 91.21151,12.531524 L 89.477971,11.943246 L 89.147774,13.455958 L 88.404826,14.12827 L 86.836385,11.775168 L 86.341089,9.169955 z " id="WA_Pacific" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 66.446264,59.427825 L 60.172522,61.44476 L 57.861144,68.167876 L 54.559175,78.588708 L 51.257205,85.311824 L 46.139153,99.934604 L 39.535214,114.05315 L 31.28029,127.16323 L 29.299109,130.18863 L 28.473616,139.09676 L 27.152829,145.31564 L 29.464207,149.85375" id="OR_Pacific" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 639.33795,481.63956 L 642.14462,481.63956 L 642.80502,481.80764 L 644.12581,478.95032 L 645.61169,474.41221 L 647.92307,475.08453 L 651.05994,481.30341 L 651.05994,482.31188 L 648.25327,484.32881 L 651.05994,484.66497 L 658.02586,481.83647" id="AL_Gulf" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 616.38926,488.36268 L 617.71005,487.52229 L 620.35163,483.15227 L 622.16771,483.99266 L 629.10185,481.97572 L 631.24813,482.31188 L 632.73401,483.15227 L 638.01716,483.15227 L 639.33795,481.63956" id="MS_Gulf" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:path d="M 533.67494,503.06949 L 538.62788,501.47276 L 546.88281,501.1366 L 557.44911,504.83432 L 564.05305,506.01086 L 567.85031,504.49816 L 571.15228,505.67471 L 574.45425,506.68317 L 575.27974,504.49816 L 571.97778,503.32162 L 569.3362,503.82585 L 566.52953,502.14507 C 566.52953,502.14507 566.69462,500.80045 567.35502,500.63237 C 568.01541,500.46429 570.49189,499.6239 570.49189,499.6239 L 572.30797,501.1366 L 574.12406,500.12814 L 577.42603,500.80045 L 578.91191,503.32162 L 579.24211,505.67471 L 583.86487,506.01086 L 585.68095,507.85972 L 584.85546,509.5405 L 583.53467,510.38089 L 585.18565,512.06167 L 593.77077,515.75938 L 597.40294,514.41476 L 598.39353,511.89359 L 601.03511,511.22128 L 602.85119,509.70858 L 604.17198,510.71704 L 604.99747,513.74245 L 602.68609,514.58284 L 603.34648,515.25515 L 606.81355,513.91053 L 609.12493,510.38089 L 609.95042,509.87666 L 607.80414,509.5405 L 608.62964,507.85972 L 608.46454,506.34702 L 610.61082,505.84279 L 611.76651,504.49816 L 612.4269,505.33855 C 612.4269,505.33855 612.2618,508.53203 613.08729,508.53203 C 613.91279,508.53203 617.37985,509.20434 617.37985,509.20434 L 621.50732,511.22128 L 622.49791,512.73398 L 625.46968,512.73398 L 626.62537,513.74245 L 628.93675,510.54897 L 628.93675,509.03627 L 627.61596,509.03627 L 624.14889,506.17894 L 618.20535,505.33855 L 614.90338,502.98546 L 616.05907,500.12814 L 618.37045,500.46429 L 618.53554,499.79198 L 616.71946,498.78351 L 616.71946,498.27928 L 620.02143,498.27928 L 621.83751,495.0858 L 620.51673,493.06886 L 620.18653,490.21154 L 618.70064,490.37962 L 616.71946,492.56463 L 616.05907,495.25388 L 612.9222,494.58156 L 611.93161,492.73271 L 613.74769,490.71577 L 616.38926,488.36268 L 617.2973,487.77441" id="LA_Gulf" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1.06612186pt" />
+ <ns0:g id="Great_Lakes_borders" style="fill:none;stroke:#80b0f0" transform="matrix(1.0566302,0,0,1.0756987,-45.325399,-166.80506)">
+ <ns0:path d="M 652.1875,357.8125 L 649.84375,359.21875 L 647.8125,361.09375 L 646.71875,361.40625 L 645.3125,360.46875 L 642.18749,359.53125" id="IN_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 712.53906,338.43751 L 712.8125,334.6875 L 714.29687,331.75782 L 715.50782,330.39062 M 717.85156,323.16407 L 714.6875,315 L 712.5,306.25 L 710.15625,303.125 L 707.65625,301.40625 L 706.09375,302.5 L 702.34375,304.21875 L 700.46875,309.0625 L 697.8125,312.65625 L 696.71875,313.28125 L 695.3125,312.65625 C 695.3125,312.65625 692.8125,311.25 692.96875,310.625 C 693.125,310 693.4375,305.78125 693.4375,305.78125 L 696.71875,304.53125 L 697.5,301.25 L 698.125,298.75 L 700.46875,297.1875 L 700.15625,287.5 L 698.59375,285.3125 L 697.34375,284.53125 L 696.5625,282.5 L 697.34375,281.71875 L 698.90625,282.03125 L 699.0625,280.46875 L 696.71875,278.28125 L 695.46875,275.78125 L 692.96875,275.78125 L 688.59375,274.375 L 683.28125,271.09375 L 680.625,271.09375 L 680,271.71875 L 679.0625,271.25 L 676.09375,269.0625 L 673.28125,270.78125 L 670.46875,272.96875 L 670.78125,276.40625 L 671.71875,276.71875 L 673.75,277.1875 L 674.21875,277.96875 L 671.71875,278.75 L 669.21875,279.0625 L 667.8125,280.78125 L 667.5,282.8125 L 667.8125,284.375 L 668.125,289.6875 L 664.6875,291.71875 L 664.0625,291.5625 L 664.0625,287.5 L 665.3125,285.15625 L 665.9375,282.8125 L 665.15625,282.03125 L 663.28125,282.8125 L 662.34375,286.875 L 659.6875,287.96875 L 657.96875,289.84375 L 657.8125,290.78125 L 658.4375,291.5625 L 657.8125,294.0625 L 655.625,294.53125 L 655.625,295.625 L 656.40625,297.96875 L 655.3125,303.90625 L 653.75,307.8125 L 654.375,312.34375 L 654.84375,313.4375 L 654.0625,315.78125 L 653.75,316.5625 L 653.4375,319.21875 L 656.875,325 L 659.6875,331.25 L 661.09375,335.9375 L 660.3125,340.46875 L 659.375,346.25 L 657.03125,351.25 L 656.71875,353.90625 L 654.84375,356.25 L 652.1875,357.8125 M 605.4621,230.97629 L 607.22987,228.98755 L 609.3291,228.21415 L 614.52193,224.45763 L 616.73164,223.9052 L 617.17359,224.34715 L 612.20173,229.31901 L 608.99764,231.19726 L 607.0089,232.08115 L 605.4621,230.97629 z M 634.68749,287.50003 L 638.28125,279.6875 L 639.21875,275.78125 L 641.09375,271.5625 L 641.875,271.40625 L 642.96875,272.96875 L 643.59375,272.96875 L 647.96875,270.625 L 649.375,272.1875 L 649.84375,272.34375 L 651.09375,271.25 L 652.1875,268.28125 L 654.53125,267.5 L 661.25,266.875 L 663.125,264.375 L 668.125,264.21875 L 673.75,265.46875 L 675.46875,265.46875 L 678.59375,264.0625 L 680.78125,264.21875 L 682.8125,263.59375 L 686.40625,264.0625 L 687.1875,264.375 L 688.4375,264.0625 L 687.1875,263.125 L 685.9375,262.5 L 682.8125,259.53125 L 682.8125,252.8125 L 681.40625,252.34375 L 680.3125,253.4375 L 674.375,255 L 672.5,255.46875 L 669.6875,254.6875 L 669.21875,254.375 L 669.21875,248.90625 L 667.8125,248.75 L 665.3125,250 L 660.9375,251.875 L 654.53125,252.1875 L 651.25,253.28125 L 647.34375,256.71875 L 645.78125,257.65625 L 644.6875,257.65625 L 643.4375,258.4375 L 641.875,257.96875 L 640.3125,256.71875 L 638.90625,257.65625 L 635.15625,257.8125 L 632.5,255.15625 L 631.09375,252.1875 L 629.6875,251.09375 L 626.5625,250.15625 L 624.375,250.15625 L 623.125,248.90625 L 619.6875,251.71875 L 618.75,252.8125 L 617.96875,252.34375 L 618.28125,249.84375 L 620.625,246.71875 L 621.09375,244.375 L 623.28125,243.59375 L 624.6875,240.625 L 628.28125,239.6875 L 628.59375,238.75 L 627.5,237.65625 L 622.96875,238.125 L 618.75,240.46875 L 616.5625,242.65625 L 615.3125,244.375 L 613.59375,245.15625 L 611.71875,247.96875 L 611.5625,249.21875 L 607.34375,251.25 L 605,253.125 L 599.21875,254.0625 L 598.59375,254.6875 L 598.59375,255.625 L 595.15625,257.8125 L 592.5,258.59375 L 590.9375,259.53125 M 688.75238,262.0292 L 689.37738,264.45108 L 692.50239,264.60733 L 693.7524,263.43545 C 693.7524,263.43545 693.67427,262.0292 693.36177,261.87295 C 693.04927,261.7167 691.79927,260.07607 691.79927,260.07607 L 689.68989,260.31044 L 688.12738,260.46669 L 687.81488,261.56045 L 688.75238,262.0292 z M 707.34375,352.8125 L 706.09375,351.5625 L 706.25,350.15625 L 708.28125,346.5625 L 710.42969,344.60937" id="MI_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 762.03126,331.40624 L 756.5625,336.875 L 755.3125,337.34375 L 751.25001,340.46875" id="PA_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 773.4375,318.28125 L 773.59375,319.21875 L 772.5,320.15625 L 770.46875,322.8125 L 770,324.375 L 768.125,326.09375 L 766.40625,327.1875 L 765.46875,328.75 L 764.21875,329.84375 L 761.56251,331.71874 M 823.75,260.46875 L 821.25,262.34375 L 819.21875,264.6875 L 816.5625,268.28125 L 813.75,272.65625 L 812.34375,275.46875 L 811.71875,276.25 L 806.09375,281.5625 L 806.25,284.0625 L 807.03125,285.15625 L 808.75,285.9375 L 810.46875,285.9375 L 810.46875,287.34375 L 809.375,289.375 L 809.6875,290.78125 L 811.09375,292.8125 L 810.9375,295 L 809.0625,296.09375 L 807.03125,296.09375 L 805.46875,297.96875 L 803.75,301.09375 L 801.71875,302.8125 L 796.71875,303.28125 L 794.21875,304.375 L 792.1875,305.625 L 790.625,305.46875 L 788.75,304.21875 L 782.65625,304.375 L 779.53125,304.84375 L 775.625,306.09375 L 771.40625,307.5 L 768.59375,309.21875" id="NY_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 751.40626,340.46875 L 744.375,344.0625 L 740.625,346.25 L 737.34375,349.84375 L 733.4375,353.59375 L 730.3125,354.375 L 727.5,354.84375 L 722.1875,357.34375 L 720.15625,357.5 L 716.875,354.53125 L 711.875,355.15625 L 709.375,353.75 L 706.71874,352.34374" id="OH_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 642.5,359.6875 L 641.25,358.90625 L 640.46875,356.40625 L 639.21875,352.8125 L 637.65625,351.09375 L 636.25,348.59375 L 636.09375,343.12504" id="IL_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 590.9375,259.53125 L 590.3125,260.78125 L 589.21875,260.625 L 588.59375,259.53125 L 585.9375,258.75 L 584.84375,258.90625 L 583.125,259.84375 L 582.1875,259.21875 L 582.8125,257.34375 L 584.6875,254.375 L 585.78125,253.28125 L 583.90625,251.875 L 581.875,252.65625 L 579.0625,254.53125 L 571.875,257.65625 L 569.0625,258.28125 L 566.25,257.8125 L 565.46875,256.875 M 636.25,343.43744 L 636.09375,339.375 L 634.53125,335 L 633.90625,329.0625 L 632.8125,326.71875 L 633.75,323.75 L 634.53125,320.9375 L 635.9375,318.4375 L 635.3125,315.15625 L 634.6875,311.71875 L 635.15625,310 L 637.03125,307.65625 L 637.1875,305 L 636.40625,303.75 L 637.03125,301.25 L 637.5,298.125 L 640.15625,292.65625 L 642.96875,286.09375 L 643.125,283.90625 L 642.8125,282.96875 L 642.03125,283.4375 L 637.96875,289.53125 L 635.3125,293.4375 L 633.4375,295.15625 L 632.65625,297.34375 L 631.25,298.125 L 630.15625,300 L 628.75,299.6875 L 628.59375,297.96875 L 629.84375,295.625 L 631.875,291.09375 L 633.59375,289.53125 L 634.68749,287.03127" id="WI_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:path d="M 565.9375,257.34374 L 565.3125,256.5625 L 568.59375,253.59375 L 569.84375,253.4375 L 574.21875,248.59375 L 575.9375,247.8125 L 578.125,244.0625 L 580.46875,240.625 L 583.4375,238.125 L 587.5,236.40625 L 595,232.8125 L 597.8125,232.03125 C 597.8125,232.03125 600.78125,230.3125 601.09375,229.6875 C 601.40625,229.0625 601.71875,228.28125 601.71875,228.28125" id="MN_Great_Lakes" style="fill:none;stroke:#80b0f0;stroke-width:1pt" />
+ </ns0:g>
+ <ns0:path d="M 152.15345,458.16063 L 151.84095,540.66102 L 153.40345,541.59852 L 156.37222,541.75477 L 157.77847,540.66102 L 160.27848,540.66102 L 160.43474,543.47353 L 167.15352,550.03606 L 167.62227,552.53607 L 170.90353,550.66106 L 171.52854,550.50481 L 171.84104,547.53605 L 173.24729,545.97354 L 174.34105,545.81729 L 176.21606,544.41103 L 179.18482,546.44229 L 179.80983,549.25481 L 181.68483,550.34856 L 182.77859,552.69232 L 186.52861,554.41108 L 189.80987,560.19236 L 192.46613,563.94237 L 194.65364,566.59864 L 196.0599,570.1924 L 200.90367,571.91116 L 205.9037,573.94242 L 206.8412,578.16119 L 207.30995,581.12995 L 206.37245,584.41122 L 204.65369,586.59873 L 203.09118,585.81748 L 201.68493,582.84871 L 199.02866,581.44246 L 197.30991,580.3487 L 196.52865,581.12995 L 197.93491,583.78622 L 198.09116,587.37998 L 196.9974,587.84873 L 195.1224,585.97373 L 193.09114,584.72372 L 193.55989,586.28623 L 194.80989,588.00499 L 194.02864,588.78624 C 194.02864,588.78624 193.24739,588.47374 192.77864,587.84873 C 192.30988,587.22373 190.74738,584.56747 190.74738,584.56747 L 189.80987,582.37996 C 189.80987,582.37996 189.49737,583.62997 188.87237,583.31746 C 188.24736,583.00496 187.62236,581.91121 187.62236,581.91121 L 189.34112,580.0362 L 187.93486,578.62994 L 187.93486,573.78617 L 187.15361,573.78617 L 186.37236,577.06743 L 185.2786,577.53619 L 184.3411,573.94242 L 183.71609,570.34865 L 182.93484,569.8799 L 183.24734,575.34868 L 183.24734,576.44243 L 181.84108,575.19243 L 178.40357,569.41115 L 176.37231,568.9424 L 175.74731,565.34863 L 174.1848,562.53612 L 172.62229,561.44236 L 172.62229,559.25485 L 174.65355,558.00485 L 174.1848,557.69235 L 171.68479,558.31735 L 168.40352,555.97359 L 165.90351,553.16107 L 161.21599,550.66106 L 157.30972,548.16105 L 158.55973,545.03604 L 158.55973,543.47353 L 156.84097,545.03604 L 154.02846,546.12979 L 150.43469,545.03604 L 144.96591,542.69228 L 139.65339,542.69228 L 139.02839,543.16103 L 132.77836,539.41101 L 130.7471,539.09851 L 128.09084,533.47348 L 124.65332,533.78598 L 121.2158,535.19224 L 121.68456,539.56726 L 122.77831,536.75475 L 123.71582,537.06725 L 122.30956,541.28602 L 125.43457,538.62976 L 126.05958,540.19226 L 122.30956,544.41103 L 121.05955,544.09853 L 120.5908,542.22352 L 119.3408,541.44227 L 118.09079,542.53603 L 115.43453,540.81727 L 112.46576,542.84853 L 110.74701,544.87979 L 107.46574,546.91105 L 102.93447,546.75479 L 102.46572,544.72354 L 106.05948,544.09853 L 106.05948,542.84853 L 103.87197,542.22352 L 104.80948,539.87976 L 106.99699,536.12975 L 106.99699,534.41099 L 107.15324,533.62973 L 111.37201,531.44222 L 112.30951,532.69223 L 114.96578,532.69223 L 113.71577,530.19222 L 110.122,529.87972 L 105.27823,532.53598 L 102.93447,535.81724 L 101.21571,538.31726 L 100.12196,540.50477 L 96.059441,541.91102 L 93.090671,544.41103 L 92.778171,545.97354 L 94.965681,546.91105 L 95.746941,548.9423 L 93.090671,552.06732 L 86.840651,556.12984 L 79.340608,560.19236 L 77.309348,561.28611 L 72.153076,562.37987 L 66.996796,564.56738 L 68.715556,565.81738 L 67.309296,567.22364 L 66.840546,568.31739 L 64.184286,567.37989 L 61.059276,567.53614 L 60.278016,569.72365 L 59.340516,569.72365 L 59.653016,567.37989 L 56.215496,568.6299 L 53.402986,569.5674 L 50.121721,568.31739 L 47.309208,570.1924 L 44.184194,570.1924 L 42.152934,571.44241 L 40.590427,572.22366 L 38.559168,571.91116 L 36.059156,570.81741 L 33.871646,571.44241 L 32.934142,572.37991 L 31.371634,571.28616 L 31.371634,569.41115 L 34.340398,568.16114 L 40.434176,568.78615 L 44.652946,567.22364 L 46.684205,565.19238 L 49.496718,564.56738 L 51.215476,563.78612 L 53.871739,563.94237 L 55.434246,565.19238 L 56.371746,564.87988 L 58.559256,562.22362 L 61.528026,561.28611 L 64.809286,560.66111 L 66.059296,560.34861 L 66.684296,560.81736 L 67.465556,560.81736 L 68.715556,557.22359 L 72.621826,555.81734 L 74.496836,552.22357 L 76.684348,547.84855 L 78.246858,546.44229 L 78.559358,543.94228 L 76.996848,545.19229 L 73.715576,545.81729 L 73.090576,543.47353 L 71.840576,543.16103 L 70.903066,544.09853 L 70.746816,546.91105 L 69.340556,546.75479 L 67.934306,541.12977 L 66.684296,542.37977 L 65.590546,541.91102 L 65.278046,540.03601 L 61.371776,540.19226 L 59.340516,541.28602 L 56.840506,540.97352 L 58.246756,539.56726 L 58.715506,537.06725 L 58.090506,535.19224 L 59.496766,534.25474 L 60.746766,534.09849 L 60.121766,532.37973 L 60.121766,528.16096 L 59.184266,527.22345 L 58.403006,528.62971 L 52.465482,528.62971 L 51.059226,527.3797 L 50.434223,523.62969 L 48.402963,520.19217 L 48.402963,519.25467 L 50.434223,518.47341 L 50.590473,516.44215 L 51.684228,515.3484 L 50.902975,514.87965 L 49.652969,515.3484 L 48.559214,512.69214 L 49.496718,507.84836 L 53.871739,504.72335 L 56.371746,503.16084 L 58.246756,499.56708 L 60.903026,498.31707 L 63.403036,499.41083 L 63.715536,501.75459 L 66.059296,501.44208 L 69.184306,499.09832 L 70.746816,499.72333 L 71.684316,500.34833 L 73.246826,500.34833 L 75.434338,499.09832 L 76.215598,494.87955 C 76.215598,494.87955 76.528098,492.06704 77.153098,491.59829 C 77.778098,491.12954 78.090598,490.66079 78.090598,490.66079 L 76.996848,488.78578 L 74.496836,489.56703 L 71.371816,490.34828 L 69.496806,489.87953 L 66.059296,488.16077 L 61.215526,488.00452 L 57.778006,484.41076 L 58.246756,480.66074 L 58.871766,478.31698 L 56.840506,476.59822 L 54.965494,473.00445 L 55.434246,472.2232 L 61.996776,471.75445 L 64.028036,471.75445 L 64.965536,472.69195 L 65.590546,472.69195 L 65.434296,471.12944 L 69.184306,470.50444 L 71.684316,470.81694 L 73.090576,471.9107 L 71.684316,473.94196 L 71.215566,475.34821 L 73.871836,476.91072 L 78.715608,478.62948 L 80.434368,477.69198 L 78.246858,473.47321 L 77.309348,470.34819 L 78.246858,469.56694 L 74.965588,467.69193 L 74.496836,466.59817 L 74.965588,465.03567 L 74.184336,461.28565 L 71.371816,456.75438 L 69.028056,452.69186 L 71.840576,450.81685 L 74.965588,450.81685 L 76.684348,451.44185 L 80.746868,451.2856 L 84.340631,447.84809 L 85.434391,444.87932 L 89.028161,442.53556 L 90.590661,443.47307 L 93.246921,442.84806 L 96.840691,440.8168 L 97.934451,440.66055 L 98.871951,441.44181 L 103.24697,441.28556 L 105.90323,438.31679 L 106.99699,438.31679 L 110.4345,440.66055 L 112.30951,442.69181 L 111.84076,443.78557 L 112.46576,444.87932 L 114.02827,443.31682 L 117.77829,443.62932 L 118.09079,447.22308 L 119.9658,448.62934 L 126.84083,449.25434 L 132.93461,453.31686 L 134.34086,452.37936 L 139.34089,454.87937 L 141.37215,454.25437 L 143.24716,453.47311 L 147.93468,455.34812 L 152.15345,458.16063 z M 40.902929,486.12951 L 42.934188,491.28579 L 42.777937,492.22329 L 39.965424,491.91079 L 38.246666,488.00452 L 36.527908,486.59827 L 34.184147,486.59827 L 34.027897,484.09825 L 35.746655,481.75449 L 36.84041,484.09825 L 38.246666,485.50451 L 40.902929,486.12951 z M 38.402917,518.47341 L 41.996684,519.25467 L 45.59045,520.19217 L 46.371704,521.12968 L 44.809197,524.72344 L 41.840433,524.56719 L 38.559168,521.12968 L 38.402917,518.47341 z M 18.402824,504.8796 L 19.49658,507.37961 L 20.590335,508.94212 L 19.49658,509.72337 L 17.46532,506.75461 L 17.46532,504.8796 L 18.402824,504.8796 z M 5.1215129,575.50493 L 8.4027779,573.31742 L 11.684043,572.37991 L 14.184055,572.69241 L 14.652807,574.25492 L 16.527816,574.72367 L 18.402824,572.84867 L 18.090323,571.28616 L 20.746585,570.66116 L 23.559098,573.16117 L 22.465343,574.87992 L 18.246574,575.97368 L 15.590311,575.50493 L 11.996545,574.41117 L 7.7777749,575.81743 L 6.2152679,576.12993 L 5.1215129,575.50493 z M 52.465482,571.12991 L 54.027989,573.00492 L 56.059246,571.44241 L 54.652992,570.1924 L 52.465482,571.12991 z M 55.277995,574.09867 L 56.371746,571.91116 L 58.403006,572.22366 L 57.621756,574.09867 L 55.277995,574.09867 z M 78.090598,572.22366 L 79.496858,573.94242 L 80.434368,572.84867 L 79.653108,570.97366 L 78.090598,572.22366 z M 86.528141,560.19236 L 87.621901,565.81738 L 90.434411,566.59864 L 95.278181,563.78612 L 99.496951,561.28611 L 97.934451,558.94235 L 98.403201,556.59859 L 96.371941,557.8486 L 93.559431,557.06734 L 95.121931,555.97359 L 96.996941,556.75484 L 100.74696,555.03608 L 101.21571,553.62983 L 98.871951,552.84857 L 99.653201,550.97356 L 96.996941,552.84857 L 92.465671,556.28609 L 87.778151,559.0986 L 86.528141,560.19236 z M 127.46583,540.97352 L 129.80959,539.56726 L 128.87209,537.8485 L 127.15333,538.78601 L 127.46583,540.97352 z " id="AK" style="fill:#f4d7d7" />
+ <ns0:g id="g16325" style="stroke:#000000;stroke-opacity:1">
+ <ns0:g id="g5778" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 816.92419,258.09425 C 817.71859,258.70718 818.14466,259.56702 819.77271,260.56631 L 819.77271,260.61385" id="path6654" style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:1.33265233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" ns1:nodetypes="ccc" />
+ <ns0:g id="g4679" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:g id="g3580" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:g id="State_borders_old" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1" transform="matrix(1.0566302,0,0,1.0756987,-45.325399,-166.80506)">
+ <ns0:path d="M 389.29574,462.33445 L 395.75915,462.99736 L 406.8077,463.54979" id="CO_OK" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 389.79293,462.72114 L 389.2405,473.88019" id="NM_OK" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 389.90342,473.82495 L 388.24613,473.82494 L 386.69931,491.94483 L 382.94283,545.64053 L 380.73312,568.62152 L 352.66979,567.07472 L 324.60654,564.42309 L 316.87248,563.76016 L 317.5354,568.62152" id="NM_TX" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 299.63678,367.31684 L 319.96612,369.74752 L 355.98408,373.72497 L 379.62831,375.71374" id="WY_CO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 379.62831,375.71374 L 411.89008,378.36539 L 410.34328,400.02056" id="NE_CO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 410.34328,400.02056 L 406.36581,463.66023" id="CO_KS" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 379.62831,375.71374 L 383.82676,332.84535" id="WY_NE" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 383.82676,332.84535 L 385.37355,308.31756 L 386.63467,290.78272" id="WY_SD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 386.7128,291.15996 L 388.23277,275.63418 L 388.81756,270.31054" id="MT_SD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 388.81755,270.85742 L 389.59881,259.06782 L 391.78171,234.95517 L 393.10754,220.37107 L 394.43337,206.67087" id="MT_ND" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 389.13006,270.75248 L 443.26797,274.28802 L 499.6156,275.83481" id="ND_SD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 490.55578,211.09029 L 492.32355,216.17262 L 491.66064,219.92913 L 491.66064,229.87283 L 493.20744,234.73419 L 494.97521,238.04876 L 495.41715,247.55052 L 497.18492,260.58781 L 498.95269,267.65888 L 499.39463,275.83481" id="ND_MN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 499.39463,275.83481 L 499.17366,278.92841 L 497.84783,280.25424 L 495.85909,281.80103 L 495.85909,283.34783 L 496.96395,285.1156 L 500.94143,288.43017 L 501.38337,291.30279 L 501.60434,320.69194 L 501.1624,327.32107" id="SD_MN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 501.1624,327.32107 L 499.39463,327.32107 L 498.51074,329.75176 L 498.95269,332.40341 L 501.60434,334.39215 L 500.27851,339.91643 L 498.51074,344.11488 L 500.05754,346.76653 L 501.60434,348.97624" id="SD_IA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 501.60434,348.97624 L 499.83657,348.97624 L 498.0688,348.97624 L 497.18492,346.9875 L 496.74297,345.4407 L 493.87035,343.89391 L 489.00899,342.34711 L 487.24122,341.02128 L 484.58957,341.24225 L 480.83306,341.68419 L 476.63461,343.01002 L 475.52975,342.12614 L 470.88936,339.25351 L 468.90062,337.0438 L 458.51498,337.48574 L 435.53399,336.38089 L 418.29824,335.27603 L 398.85279,334.17118 L 383.82676,333.50826" id="SD_NE" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 500.94143,327.32107 L 512.21095,327.32107 L 560.16167,326.65816 L 577.61839,325.99525 L 581.59587,325.99525" id="MN_IA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 581.59587,325.99525 L 580.71198,318.7032 L 578.72324,316.05155 L 571.87314,312.07407 L 568.11663,306.54979 L 564.80207,306.10785 L 563.0343,303.01426 L 559.71973,303.01426 L 556.84711,300.14163 L 556.40516,293.73347 L 556.40516,289.97696 L 558.17293,286.88337 L 557.51002,283.78977 L 555.07934,281.80103 L 554.6374,281.58006 L 556.84711,275.83481 L 561.92944,272.07831 L 563.0343,270.53151 L 563.0343,262.35558 L 563.25527,259.70393 L 566.01741,256.8313" id="MN_WI" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 590.71092,259.59344 L 594.85413,264.78626 L 602.80909,265.89112 L 610.76405,268.10083 L 613.19473,269.20568 L 625.34814,271.85734 L 626.67397,274.06705 L 630.2095,275.1719 L 631.7563,285.1156 L 633.08213,286.44143 L 634.62893,287.60152" id="WI_MI" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 581.59587,325.77428 L 582.25878,329.08884 L 584.46849,330.63564 L 584.68946,331.96147 L 582.70072,335.27603 L 582.92169,338.36963 L 585.35238,342.12614 L 587.78306,343.23099 L 590.65568,343.67293 L 591.92627,346.3246" id="IA_WI" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 591.76054,345.88265 L 601.04136,346.10361 L 626.453,344.55682 L 635.95475,343.45196" id="WI_IL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 591.76054,345.71692 L 591.98151,348.09236 L 594.19122,348.75527 L 595.0751,349.86012 L 595.51704,351.62789 L 599.27355,354.94246 L 599.93647,357.15217 L 599.27355,360.46674 L 597.50578,364.00227 L 596.84287,366.43295 L 594.63316,368.20072 L 592.86539,368.86364 L 587.78306,370.18946 L 587.12014,371.95723 L 586.45723,373.94597 L 587.12014,375.2718 L 588.88791,376.8186 L 588.66694,380.79607 L 586.89917,382.34287 L 586.23626,383.88967 L 586.23626,386.54132 L 584.46849,386.98326 L 582.92169,388.08812 L 582.70072,389.41395 L 582.92169,391.40269 L 581.15393,393.05996" id="IA_IL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 581.3749,393.17045 L 578.06033,389.85589 L 576.95547,387.64618 L 569.44246,388.30909 L 559.9407,388.75103 L 535.41291,389.63492 L 522.37562,389.85589 L 513.31581,390.07686 L 511.98998,390.07686" id="IA_MO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 512.53686,390.31124 L 510.66415,384.99453 L 510.44318,378.58636 L 508.89638,374.60888 L 508.23347,369.52655 L 506.02376,365.99101 L 505.13988,361.35062 L 502.48822,354.05857 L 501.1624,348.75527" id="NE_IA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 410.12231,399.79959 L 431.33554,400.68347 L 470.44713,402.00929 L 513.09483,402.45124 L 519.06105,402.45124" id="NE_KS" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 519.06105,402.45124 L 515.96746,398.47376 L 513.53678,394.71725 L 513.75775,392.50754 L 512.43192,390.07686" id="NE_MO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 518.84008,402.45124 L 521.93368,405.10289 L 524.14339,405.32386 L 525.46921,406.20775 L 525.46921,409.08037 L 523.70145,410.62717 L 523.2595,412.83688 L 525.24824,416.15145 L 527.67893,419.02407 L 530.10961,420.79184 L 531.43543,432.06136 L 530.77252,466.53285" id="KS_MO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 530.77252,466.53285 L 517.95614,467.19576 L 473.09935,466.75383 L 429.56781,464.76507 L 406.08959,463.43925" id="KS_OK" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 388.4119,473.88015 L 410.34328,474.92975 L 440.39535,476.03461 L 439.06952,499.0156 L 438.62758,516.25134 L 438.84855,517.79814 L 443.047,521.33368 L 445.03574,522.43853 L 445.69866,522.21756 L 446.36157,520.22882 L 447.6874,521.99659 L 449.67614,521.99659 L 449.67614,520.67076 L 452.32779,521.99659 L 451.88585,525.7531 L 455.86333,525.97407 L 458.29401,527.07893 L 462.27149,527.74184 L 464.70217,529.50961 L 466.91188,527.52087 L 470.22645,528.18378 L 472.65713,531.49835 L 473.54101,531.49835 L 473.54101,533.70806 L 475.75072,534.37097 L 477.96043,532.16126 L 479.7282,532.82417 L 482.15888,532.82417 L 483.04277,535.25486 L 487.68316,537.02262 L 489.00899,536.35971 L 490.77676,532.38223 L 491.88161,532.38223 L 492.98647,534.37097 L 496.96395,535.03388 L 500.49948,536.35971 L 503.37211,537.2436 L 505.13988,536.35971 L 505.80279,533.92903 L 510.00124,533.92903 L 511.98998,534.81291 L 514.64163,532.82417 L 515.74649,532.82417 L 516.4094,534.37097 L 520.38688,534.37097 L 521.93368,532.38223 L 523.70145,532.82417 L 525.69019,535.25486 L 528.78378,537.02262 L 531.87738,537.90651 L 534.52903,539.45331" id="OK_TX" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 534.52903,539.45331 L 534.30806,502.55125 L 532.98222,491.94454 L 532.98223,483.98957 L 531.43543,477.36043" id="OK_AR" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 531.43543,477.36043 L 530.99349,470.7313 L 530.77252,465.86994" id="OK_MO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 531.43543,477.36043 L 553.97448,476.91849 L 576.51353,476.25558 L 596.6219,475.37169 L 607.22851,474.92975 L 608.99628,477.80238 L 608.55434,480.01209 L 605.46074,482.66374 L 604.79783,485.53636 L 610.76405,485.97831 L 615.62541,485.31539" id="MO_AR" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 615.62541,485.31539 L 617.61415,479.1282 L 617.61415,473.38295" id="MO_TN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 617.33793,473.99063 L 620.26581,472.2781 L 621.59163,470.7313 L 623.58037,469.62645 L 623.80134,466.53285 L 624.68523,464.76508 L 623.13842,462.27915" id="MO_KY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 623.58037,462.55537 L 620.48678,462.77634 L 618.71901,460.7876 L 617.17221,456.81012 L 618.05609,453.9375 L 616.06735,450.84391 L 614.07862,445.98254 L 609.65919,445.31963 L 603.472,440.23729 L 601.26229,436.48079 L 601.92521,433.38719 L 604.13492,427.64194 L 604.79783,424.10641 L 602.14618,422.78058 L 595.95899,422.33864 L 595.0751,421.01281 L 595.29607,416.81436 L 589.99277,413.27882 L 582.92169,405.98678 L 580.71198,398.9157 L 580.27004,395.38017 L 581.3749,392.28657" id="MO_IL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 534.52903,538.79039 L 536.73874,541.0001 L 539.61136,539.67428 L 542.26302,540.77913 L 542.92593,551.38574" id="TX_AR" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 542.92593,551.38574 L 542.92593,560.8875 L 543.58884,569.94731 L 544.25176,573.70382 L 546.68244,577.6813 L 547.56632,582.54267 L 551.76477,587.84597 L 551.98574,590.93957 L 552.64866,591.60248 L 551.98574,599.77841 L 549.11312,604.63977 L 550.65992,606.62851 L 549.997,609.05919 L 549.33409,616.13027 L 548.00826,619.22386 L 548.28448,622.70416" id="TX_LA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 542.87069,551.88293 L 564.36012,551.60672 L 582.92169,550.72283 L 593.30733,550.72283" id="AR_LA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 593.52831,550.72283 L 593.74928,556.02614 L 595.73802,560.44556 L 595.51704,562.21333 L 597.72676,563.9811 L 597.06384,567.07469 L 595.95899,567.07469 L 596.84287,569.06343 L 591.76054,577.90227 L 588.44597,583.86849 L 587.78306,591.60248 L 588.225,593.14928 L 611.64793,592.48636 L 619.60289,591.82345 L 621.37066,591.60248 L 622.03357,592.48636 L 620.92872,599.77841 L 624.24328,602.872 L 625.34814,606.62851 L 626.61872,609.00395" id="LA_MS" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 593.41781,550.99905 L 594.79888,548.29215 L 594.41219,544.0937 L 593.08636,541.22107 L 594.41219,539.89525 L 593.08636,537.90651 L 593.52831,536.13874 L 594.41219,530.17252 L 597.28481,527.52087 L 596.6219,525.53213 L 600.15744,520.44979 L 602.80909,519.56591 L 602.80909,517.13523 L 602.14618,515.8094 L 604.79783,510.72707 L 607.44948,509.62221 L 607.44948,506.08667" id="AR_MS" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 642.50096,359.68676 L 643.28221,370.93678 L 644.53221,385.78055 L 645.78222,401.24932 L 646.25097,407.96808 L 645.46972,413.28059 L 645.78222,416.40559 L 647.50097,419.06185 L 647.96972,424.68686 L 645.46972,428.74936 L 643.75096,432.49937 L 641.56346,435.31187 L 641.09471,439.68688 L 641.09471,443.28063" id="IL_IN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 641.09471,443.28063 L 640.15721,446.56189 L 639.06346,447.34314 L 639.68846,449.21814 L 640.46971,450.9369 L 638.90721,451.5619 L 636.4072,452.1869 L 634.5322,453.59315 L 634.2197,455.78065 L 635.4697,458.12441 L 635.31345,460.31191 L 634.2197,460.62441 L 630.78219,459.37441 L 628.12594,458.12441 L 626.09469,458.74941 L 623.90718,460.46816 L 623.12593,462.34316" id="IL_KY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 641.25096,443.43688 L 644.06346,441.71813 L 649.68847,440.78063 L 653.12598,440.31188 L 654.53223,442.18688 L 656.25098,442.96813 L 657.96973,439.84313 L 660.78224,438.43688 L 662.65724,439.99938 L 663.43849,441.09313 L 665.46975,440.62438 L 665.31349,437.34313 L 668.126,435.78062 L 669.21975,434.99937 L 670.3135,436.56187 L 674.84476,436.56187 L 675.62601,434.53062 L 675.31351,432.34312 L 678.12601,428.90561 L 682.65727,425.15561 L 683.12602,420.7806 L 685.78228,420.4681 L 689.53228,418.74935 L 692.18854,416.87434 L 691.87603,414.99934 L 690.46978,413.59309 L 690.93853,411.40559" id="IN_KY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 690.93853,411.40559 L 690.46978,405.93683 L 687.96978,383.28054 L 686.25103,369.99927 L 684.84477,355.7805" id="IN_OH" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 684.84477,355.7805 L 684.68852,354.68675 L 678.75102,355.31175 L 657.50098,357.49926 L 652.96973,357.49926" id="IN_MI" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 615.46967,485.07202 L 614.58577,488.10878 L 613.28217,492.34321 L 612.96967,494.84321 L 609.06341,497.03071 L 610.46966,500.46822 L 609.53216,504.99948 L 606.87591,506.09323" id="AR_TN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 607.65716,506.56198 L 615.93842,506.24948 L 639.21971,504.37448 L 642.96971,504.21823" id="TN_MS" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 644.51654,503.77628 L 644.53221,510.31198 L 644.68846,526.40576 L 643.90721,556.4058 L 643.75096,569.99957 L 646.40722,588.1246 L 647.9524,603.33611" id="MS_AL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 642.65721,504.21823 L 652.18848,503.74947 L 679.06352,501.24947 L 689.03673,500.46822" id="TN_AL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 199.86827,240.25868 L 200.31021,231.08839 L 203.84575,215.17847 L 208.15468,195.07011 L 211.8007,182.03283 L 212.5741,178.27632" id="WA_ID" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 105.11516,210.08273 L 107.72336,210.64857 L 108.38627,213.07925 L 112.36375,213.63168 L 114.57346,217.38818 L 114.35249,224.9012 L 113.68958,225.6746 L 119.43482,229.65208 L 125.40104,229.98353 L 127.27929,228.21576 L 131.47774,229.65208 L 132.25114,229.54159 L 137.33348,231.53033 L 138.99076,232.85615 L 143.52066,233.07713 L 144.736,232.41421 L 147.0562,233.96101 L 151.36513,234.51344 L 156.66844,232.5247 L 157.11038,233.85052 L 172.02592,233.74004 L 186.61001,237.16509 L 200.38279,240.66827" id="WA_OR" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 199.97875,240.59014 L 201.19409,244.6781 L 204.50866,247.21927 L 204.28769,250.86529 L 200.53118,255.06374 L 196.77467,260.6985 L 195.89079,261.36141 L 195.33836,264.45501 L 194.23351,265.55986 L 192.46574,266.0018 L 188.1568,271.30511 L 187.82535,274.28822 L 187.38341,275.39307 L 188.1568,276.38744 L 190.58749,276.27696 L 191.80283,278.48667 L 189.37215,284.23191 L 188.04632,288.31988 L 183.84787,305.44513 L 179.53894,322.90184" id="OR_ID" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 134.1294,312.29523 L 126.28493,342.89971 L 119.54531,367.97992 L 117.96384,374.32659 L 129.93095,392.28673 L 151.91756,424.99044 L 170.7001,453.05375 L 184.73175,475.04037 L 187.28633,478.33598" id="CA_NV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 179.25879,323.29247 L 199.53681,327.65271 L 208.92808,329.53097 L 217.8774,331.29873 L 225.72187,333.17699" id="ID_NV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 225.5009,333.28747 L 237.87528,335.49718 L 249.47626,337.48592 L 259.41995,339.25369 L 267.48539,340.57952 L 272.23627,341.24243" id="ID_UT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 187.05195,478.02348 L 186.61001,481.33804 L 189.26166,486.08892 L 190.477,491.72368 L 191.2504,492.71805 L 192.24477,493.27048 L 192.13428,495.48019 L 190.58749,496.80601 L 187.27292,498.46329 L 185.39467,500.34155 L 183.95836,503.87708 L 183.40593,508.62796 L 180.64379,511.27961 L 178.65505,511.94253 L 178.54457,517.57729 L 178.10262,519.23457 L 178.54457,520.00797 L 182.0801,520.56039 L 181.52767,523.21205 L 180.09136,525.31127 L 176.44534,526.19515" id="CA_AZ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 187.27292,478.24445 L 188.59875,476.03474 L 189.15117,463.88133 L 189.37215,462.22405 L 189.48263,455.48444 L 191.36088,454.49007 L 192.24477,453.93764 L 193.12865,453.93764 L 194.01254,455.04249 L 196.66419,455.37395 L 197.87953,458.0256 L 200.31021,458.13609 L 201.96749,455.59492 L 202.40943,455.15298 L 205.40595,437.74758" id="NV_AZ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 205.28206,438.35918 L 207.16031,427.86306 L 210.2539,413.16849 L 213.45798,397.14809 L 215.55721,384.00032 L 217.43546,375.38245 L 221.08148,355.60554 L 224.50653,338.70126 L 225.61139,333.50844" id="NV_UT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 70.710722,294.61753 L 77.560823,296.82724 L 88.609373,299.69986 L 95.901416,301.6886 L 108.05482,305.66608 L 121.09211,308.98065 L 134.1294,312.73715" id="OR_CA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 133.68746,312.68584 L 166.39117,320.47114 L 179.64943,323.34376" id="OR_NV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 226.27435,180.81733 L 221.85493,201.58861 L 225.1695,208.88065 L 223.84367,213.30007 L 225.61144,217.71949 L 228.70504,219.04532 L 232.24057,228.98902 L 235.77611,232.52455 L 236.21805,233.62941 L 239.53262,234.73427 L 239.97456,236.723 L 233.12446,253.73778 L 233.12446,256.16846 L 235.55514,259.26205 L 236.43902,259.26205 L 241.07941,256.38943 L 241.74233,255.28457 L 243.28913,255.94749 L 243.06815,261.02982 L 245.71981,273.18323 L 248.59243,275.61391 L 249.47631,276.27682 L 251.24408,278.48653 L 250.80214,281.8011 L 251.46505,285.11566 L 252.56991,285.99955 L 254.77962,283.78984 L 257.43127,283.78984 L 260.52487,285.33664 L 262.95555,284.45275 L 266.93303,284.45275 L 270.46856,285.99955 L 273.12022,285.55761 L 273.56216,282.68498 L 276.43478,282.02207 L 277.76061,283.3479 L 278.20255,286.44149 L 280.63323,288.6512" id="ID_MT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 280.76267,289.04183 L 282.18003,277.82362 L 301.40451,280.69624 L 328.80492,284.67372 L 344.49387,286.66246 L 375.50794,289.84759 L 386.47836,290.86091" id="MT_WY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 280.8542,288.20926 L 277.0977,312.07413 L 272.01536,341.46328" id="ID_WY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 272.01536,341.46328 L 270.5356,351.6275 L 268.92177,363.11844 L 275.2273,364.01574 L 291.55004,366.22545 L 300.53404,367.40842" id="UT_WY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 300.29966,367.31689 L 297.42703,388.75109 L 294.33344,410.40625 L 290.70637,437.45625 L 289.2511,448.1923 L 288.58819,452.16978" id="UT_CO" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 205.06113,437.58569 L 237.54388,443.77288 L 263.83943,448.1923 L 288.80916,451.85728" id="UT_AZ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 288.58819,451.94881 L 284.5839,481.67727 L 277.85214,533.09881 L 274.22507,559.11977 L 272.4573,571.93609" id="AZ_NM" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 288.80916,451.72784 L 321.95482,455.92629 L 357.65689,460.20517 L 389.35099,462.33445" id="CO_NM" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 685.15727,355.93675 L 692.50104,354.68675 L 702.3448,353.12425 L 707.42849,352.54501" id="MI_OH" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 691.09478,411.56184 L 695.00104,411.24934 L 697.34479,410.46809 L 700.1573,412.03059 L 701.7198,416.24934 L 707.34481,416.56184 L 709.06356,418.2806 L 711.09481,418.43685 L 713.43857,417.0306 L 716.40732,417.49935 L 717.65732,418.9056 L 720.31358,416.40559 L 722.03233,415.15559 L 723.59483,415.15559 L 724.21983,417.81185 L 725.93859,418.74935 L 729.37609,420.7806" id="OH_KY" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 729.37609,420.7806 L 729.53234,426.09311 L 730.31359,427.65561 L 732.8136,429.06186 L 733.4386,431.24937 L 736.2511,434.84312 L 738.7511,437.49938 L 741.92187,439.62379" id="KY_WV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 742.03236,438.90563 L 738.90736,442.65563 L 734.84485,446.09314 L 730.46984,451.2494 L 728.75109,452.96815 L 728.75109,454.9994 L 725.00108,457.03065 L 719.53233,460.31191 L 716.70413,461.72601" id="KY_VA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 717.31181,461.61552 L 666.876,466.40567 L 651.64388,468.12442 L 647.17734,468.61997 L 643.43846,468.59317 L 643.43846,472.34318 L 635.31345,472.81193 L 628.59469,473.43693 L 618.12592,473.59318" id="KY_TN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 665.15724,603.28087 L 664.84474,595.46836 L 662.34474,593.59336 L 660.62599,591.87461 L 660.93849,588.90585 L 670.78225,587.65585 L 695.46979,584.84335 L 702.0323,584.21835 L 708.12606,584.21835" id="AL_FL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 708.12606,584.21835 L 707.34481,580.78084 L 705.93856,579.99959 L 705.31355,579.21834 L 706.09481,576.87458 L 705.78231,571.71833 L 704.2198,567.49957 L 704.37605,565.62457 L 704.8448,562.49956 L 706.56356,557.81206 L 706.40731,555.7808 L 704.5323,554.99955 L 704.06355,551.7183 L 701.56355,548.43704 L 699.5323,542.34328 L 698.12604,535.62452 L 696.56354,530.93702 L 695.15729,524.99951 L 692.81354,515.46824 L 689.53228,507.81198 L 688.90728,504.53073 L 688.75103,502.49947 L 688.75103,500.31197" id="AL_GA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 707.81356,583.90584 L 710.46981,587.8121 L 711.87606,589.21835 L 719.53233,589.3746 L 729.98996,588.7496 L 750.78237,587.4996 L 756.04583,586.8478 L 760.46989,586.8746 L 760.62614,589.6871 L 763.12614,590.46835 L 763.43864,586.2496 L 761.87614,581.87459 L 762.96989,580.31209 L 768.5949,581.09334 L 773.59491,581.40584" id="GA_FL" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 688.75103,500.46822 L 697.03229,499.53072 L 705.1573,498.43697 L 709.84481,497.65572" id="TN_GA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 709.84481,497.65572 L 716.40732,497.18696 L 723.12608,496.40571 L 728.75109,495.46821 L 730.46984,495.31196" id="NC_GA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 730.46984,495.31196 L 730.62609,497.18696 L 728.43859,498.12447 L 727.65734,499.99947 L 727.96984,501.71822 L 729.21984,502.96822 L 733.1261,505.31198 L 736.40735,505.15573 L 739.68861,510.15573 L 740.15736,511.56199 L 742.50111,514.37449 L 742.96986,515.78074 L 747.34487,517.4995 L 750.31362,519.687 L 752.50113,522.4995 L 754.68863,523.7495 L 756.71988,525.62451 L 757.96988,528.12451 L 760.00114,529.99951 L 764.06364,531.87452 L 766.7199,537.65578 L 768.2824,542.34328 L 770.7824,542.96828 L 772.96991,544.99954 L 774.21991,548.43704 L 774.84491,550.46829 L 777.34491,551.7183 L 779.37617,550.7808" id="GA_SC" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 730.15734,494.99946 L 736.2511,492.65571 L 745.00111,488.2807 L 752.03237,487.49945 L 767.9699,487.0307 L 770.1574,488.9057 L 771.7199,492.03071 L 775.93866,491.56196 L 788.12618,490.1557 L 790.93868,490.93696 L 803.1262,498.28072 L 812.97945,506.32368" id="NC_SC" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 709.84481,497.65572 L 710.15731,492.96821 L 711.87606,491.56196 L 714.53232,490.93696 L 715.15732,487.3432 L 719.21983,484.68695 L 722.96983,483.28069 L 727.03234,479.84319 L 731.25109,477.81194 L 731.87609,474.84318 L 735.6261,471.09318 L 736.2511,470.93693 C 736.2511,470.93693 736.2511,472.03068 737.03235,472.03068 C 737.8136,472.03068 738.90736,472.34318 738.90736,472.34318 L 741.09486,468.90567 L 743.12611,468.28067 L 745.31361,468.59317 L 746.87612,465.15567 L 749.68862,462.65566 L 750.15737,460.62441 L 750.31362,456.71815" id="TN_NC" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 717.03232,461.56191 L 726.53223,460.74273 L 738.1261,458.90566 L 745.78237,458.74941 L 748.12612,456.8744 L 750.34206,456.92964" id="VA_TN" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 750.00112,456.8744 L 754.53238,457.49941 L 761.42964,456.2494 L 776.71991,454.3744 L 793.28244,451.8744 L 813.19549,448.22704 L 831.71999,444.53064 L 842.81376,441.71813 L 847.56599,440.14615" id="VA_NC" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 741.65514,439.21813 L 743.90736,442.34313 L 745.15736,443.59313 L 747.96987,444.37439 L 750.46987,443.74938 L 752.81363,441.56188 C 752.81363,441.56188 754.53238,442.96813 755.15738,442.81188 C 755.78238,442.65563 759.37614,441.87438 759.37614,441.87438 L 761.09489,437.34313 L 763.59489,438.28063 L 766.7199,435.93687 L 768.12615,436.24937 L 770.1574,434.37437 L 770.31365,431.71812 L 769.5324,430.46812 L 774.37616,419.99935 L 776.40741,413.59309 L 776.71991,409.37433 L 778.43866,409.21808 L 780.00117,411.87434 L 781.25117,412.65559 L 783.75117,412.65559 L 784.84492,407.34308 L 785.15742,404.37433 L 788.43868,404.06183 L 788.90743,401.87432 L 791.87618,399.53057 L 792.50119,397.96807 L 794.06369,395.31181 L 794.53244,393.28056 L 794.68869,388.1243 L 799.06369,389.84305 L 804.53246,392.81181 L 805.46995,387.65555" id="WV_VA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 805.15745,388.1243 L 809.37621,389.84305 L 809.37621,392.65556 L 814.68872,393.90556 L 816.56372,395.15556 L 817.50122,393.28056 L 819.68873,394.84306 L 818.28247,397.96807 L 817.96997,400.62432 L 816.25122,403.12432 L 816.25122,405.15558 L 816.87622,406.87433 L 822.13512,408.2443" id="VA_MD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 805.6262,388.28055 L 804.21995,386.71805 L 802.34495,384.9993 L 799.53245,383.59305 L 797.94314,382.50092 L 796.15966,382.96967 L 794.21994,384.3743 L 791.71993,386.40555 L 788.90743,386.71805 L 787.65743,386.09305 L 785.93868,388.59305 L 784.53242,389.9993 L 782.18867,390.15555 L 780.00117,393.12431 L 777.96991,395.46806 L 777.65741,395.78056 L 776.71991,390.31181 L 775.31366,385.3118" id="WV_MD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 775.64512,385.53277 L 763.62333,387.03055 L 759.14731,387.75656 L 755.60717,368.59302" id="PA_WV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 755.93863,368.59302 L 754.53238,369.84302 L 753.59488,371.24928 L 754.68863,374.21803 L 754.68863,378.74929 L 754.21988,383.59305 L 753.90738,389.0618 L 751.71987,392.81181 L 748.75112,396.09306 L 746.56362,397.65557 L 744.53236,397.18682 L 743.28236,398.59307 L 741.09486,401.87432 L 740.15736,403.12432 L 740.15736,405.46808 L 741.25111,407.18683 L 740.78236,408.74933 L 739.06361,409.68683 L 738.59485,407.96808 L 737.34485,406.87433 L 736.09485,407.49933 L 735.15735,411.24934 L 735.0011,416.09309 L 733.4386,417.49935 L 733.28235,420.1556 L 731.40734,420.93685 L 729.21984,421.24935" id="OH_WV" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 751.25169,340.03513 L 754.06419,358.74889 L 755.78295,369.68641" id="OH_PA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 761.7015,331.46032 L 762.34546,338.28009 L 770.93923,336.87384 L 812.50182,328.43632 L 830.47061,324.53006 L 832.72285,326.59924 L 835.62687,327.49882 L 837.97063,332.65508 L 840.15813,334.53008 L 842.65814,334.68633 L 843.90814,335.78009" id="NY_PA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 843.75189,335.62383 L 847.34565,337.34259 L 851.09566,338.43634 L 855.47067,339.37384 L 857.97067,340.4676 L 858.12692,342.49885 L 857.65817,345.15511 L 858.24689,348.66681" id="NY_NJ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 844.06439,335.46758 L 841.72064,337.96759 L 841.72064,340.93635 L 839.84563,343.9051 L 839.68938,345.46761 L 840.93939,346.71761 L 840.78314,349.06137 L 838.59563,350.15512 L 839.37688,352.81137 L 839.53313,353.90513 L 842.18939,354.21763 L 843.12689,356.71763 L 846.5644,359.06139 L 848.90815,360.62389 L 848.90815,361.40514 L 845.78315,364.3739 L 844.22064,366.5614 L 842.81439,369.21766 L 840.62689,370.46766 L 839.53313,371.24891" id="PA_NJ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 839.37688,371.09266 L 839.22063,372.34267 L 838.66983,374.88059" id="DE_NJ" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 839.53313,371.09266 L 837.50188,371.09266 L 835.47062,372.65517 L 834.06437,374.06142" id="NY_DE" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 834.06437,374.06142 L 835.47062,378.12393 L 837.65813,383.59269 L 839.68938,392.96771 L 841.25189,399.06148 L 846.09565,398.90523 L 852.03316,397.81147" id="MD_DE" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 834.38635,373.6842 L 826.20281,375.44087 L 811.82448,378.1697 L 774.68924,385.62395" id="PA_MD" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 862.06162,339.46537 L 860.15818,337.34259 L 861.25193,335.15508 L 861.25193,327.34257 L 859.84568,320.3113 L 859.06443,316.87379" id="NY_CT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 859.06443,316.87379 L 858.28318,311.56128 L 858.75193,301.09251" id="NY_MA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 858.75193,301.09251 L 858.28318,296.0925 L 855.31442,285.46747 L 854.68942,285.15497 L 851.87691,283.90497 L 852.65816,281.09246 L 851.87691,279.06121 L 849.37691,274.6862 L 850.31441,270.93619 L 849.53316,265.93618 L 847.1894,259.68616 L 846.5644,254.8424" id="NY_VT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 872.65821,247.81114 L 872.97071,253.5924 L 874.84571,256.24866 L 874.84571,260.15492 L 871.25195,264.06117 L 868.75195,265.15493 L 868.75195,266.24868 L 869.8457,267.96743 L 869.8457,276.2487 L 869.06445,285.15497 L 868.9082,289.84248 L 869.8457,291.09249 L 869.68945,295.46749 L 869.2207,297.18625 L 870.7832,299.06125" id="VT_NH" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 859.06443,301.56126 L 863.90819,300.46751 L 870.4707,299.2175" id="VT_MA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 870.4707,299.2175 L 887.34574,295.15499 L 889.53325,294.52999 L 891.5645,291.40499 L 895.50868,289.5947" id="NH_MA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 896.48246,285.35698 L 894.06451,284.21747 L 893.59575,281.24871 L 889.84575,280.15496 L 889.53325,277.4987 L 882.50198,254.8424 L 877.81447,240.46737" id="NH_ME" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 859.37693,317.34254 L 880.31447,312.49878 L 885.31449,311.40503" id="MA_CT" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 885.31449,311.40503 L 886.87699,317.18629 L 887.65824,321.40505 L 888.28324,325.46756" id="CT_RI" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ <ns0:path d="M 885.15824,311.56128 L 890.78325,309.99878 L 892.34575,311.09253 L 895.62701,315.31129 L 898.43952,319.6863" id="RI_MA" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:1pt;stroke-opacity:1" />
+ </ns0:g>
+ <ns0:g id="g3561" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 242.11104,448.52821 L 258.12559,450.54515 L 259.44637,440.12432 L 276.45152,442.81356 L 290.31977,444.66242" id="NM_Mexico" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 289.98957,444.32625 L 292.13584,444.99855 L 294.77744,448.02394 L 296.26332,452.56207 L 301.05119,454.91517 L 302.37198,458.27673 L 309.63631,466.51255 L 310.9571,468.19333 L 316.07515,470.37834 L 317.23084,472.56336 L 318.88182,473.57182 L 319.37712,476.42915 L 322.67909,483.15227 L 322.67909,491.55616 L 324.99047,496.43042 L 332.585,504.49816 L 337.86815,506.68317 L 339.68423,508.70011 L 339.68423,509.37242 L 343.64659,511.72551 L 345.62777,512.39782 L 347.44386,513.57437 L 350.08543,514.58284 L 352.56191,512.06167 L 357.01957,505.67471 L 358.01016,501.80891 L 360.32154,498.44736 L 363.9537,496.93466 L 368.57646,495.0858 L 371.71333,497.43889 L 379.30786,498.1112 L 386.242,499.28775 L 388.88357,501.47276 L 388.88357,502.6493 L 391.52515,505.84279 L 397.63379,511.38936 L 397.79889,512.90206 L 399.61497,514.91899 L 400.44047,519.28902 L 405.88872,532.06294 L 405.72362,534.07988 L 410.01618,536.76912 L 413.64834,543.66032 L 417.11541,548.19842 L 420.41738,549.54304 L 422.06837,551.89614 L 420.74758,556.43424 L 421.40797,557.44271 L 422.72876,558.11502 L 422.39856,561.64466 L 421.73817,562.31697 L 422.39856,564.67006 L 425.70053,566.68699 L 427.02132,573.41011 L 429.1676,577.44398 L 436.92723,580.97362 L 442.21038,582.15016 L 446.50294,585.34364 L 449.80491,586.01595 L 451.1257,585.51172 L 456.73904,586.68827 L 462.51749,590.72214 L 465.65436,588.7052" id="TX_Mexico" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 95.503595,393.0625 L 105.7397,394.5752 L 125.88171,397.43253 L 140.74058,399.1133" id="CA_Mexico" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 140.74058,399.1133 L 138.4292,401.4664 L 138.099,402.9791 L 138.5943,403.98756 L 157.91082,415.08071 L 170.2932,422.98037 L 185.31716,431.8885 L 202.4874,442.30933 L 215.03489,444.8305 L 242.11104,448.52821" id="AZ_Mexico" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:g id="g3547" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 876.74955,100.10268 L 848.85548,107.51359" id="VT_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 943.28423,76.73985 L 940.47756,76.907928 L 940.97285,78.084474 L 940.80775,78.588708 L 939.98226,78.588708 L 937.01049,76.235617 L 936.84539,71.193279 L 936.0199,69.176344 L 929.91126,69.176344 L 920.66574,38.081928 L 918.68456,37.073461 L 912.57592,34.552292 L 911.09003,34.384214 L 909.27395,36.233071 L 905.14649,39.258474 L 905.14649,40.266941 L 904.32099,41.107331 L 901.51432,40.435019 L 900.19353,38.081928 L 900.19353,36.905383 L 898.87274,36.737305 L 897.55196,36.737305 L 895.40568,41.107331 L 892.4339,50.351617 L 890.61782,55.393954 L 890.78292,60.436292 L 890.94802,61.948993 L 890.12252,64.806318 L 889.29703,65.814786 L 889.29703,72.033669 L 891.27821,74.554837 L 889.79233,78.756786 L 887.15075,83.631045 L 886.32526,89.345695 L 886.32526,92.034941 L 884.77927,91.608771 L 881.71077,91.733617" id="ME_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 882.59051,91.36264 L 881.5374,92.203019 L 880.87701,93.883799 L 880.21662,93.379565 L 879.22603,92.371097 L 877.74014,94.388032 L 875.84148,100.77501" id="NH_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:g id="g3537" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 710.49539,188.67975 L 711.48598,189.52014 L 710.49539,190.69668 L 710.66049,191.36899 L 711.48598,191.53707 L 713.46716,190.5286 L 713.13697,180.61201 M 704.88204,204.47907 L 704.88204,198.9325 L 706.86322,196.91556 L 707.68872,196.57941" id="MI_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 766.79397,165.82115 L 768.77515,172.20811 L 770.59123,172.37619 L 771.91202,175.56967 M 849.4392,107.39474 L 841.58358,109.51505 L 836.96082,111.02775 L 833.65885,110.85967 L 828.0455,112.20429 L 824.7235,113.61855" id="NY_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-opacity:1" />
+ <ns0:g id="g3530" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 179.70368,24.971818 L 163.02887,21.106058 L 139.58489,15.223331 L 119.11268,9.3406038 L 110.36246,7.3236687 L 100.45655,4.4663441 L 95.99889,2.9536428" id="WA_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 193.57209,27.997253 L 179.20868,25.139971" id="ID_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 371.21804,55.393954 L 338.69364,52.032396 L 308.81082,48.334682 L 278.92799,44.132734 L 245.9083,38.586162 L 227.08707,35.056526 L 193.57209,27.997253" id="MT_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 472.75352,59.76398 L 442.87077,59.427825 L 423.88445,58.755513 L 396.8083,57.410889 L 371.21804,55.393954" id="ND_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ <ns0:path d="M 590.4688,78.756786 L 585.35075,79.261019 L 584.85546,80.269487 L 584.19506,80.269487 L 582.37898,77.076006 L 573.29856,77.412162 L 572.30797,78.252552 L 571.31738,78.252552 L 570.82209,76.907928 L 569.99659,75.059071 L 567.35502,75.563305 L 564.05305,78.924863 L 562.40206,79.765253 L 559.26519,79.765253 L 556.62362,78.756786 L 556.62362,76.571773 L 555.30283,76.403695 L 554.80753,76.907928 L 552.16596,75.563305 L 551.67066,72.537902 L 550.18478,73.042136 L 549.68948,74.050604 L 547.21301,73.54637 L 541.76476,71.025201 L 537.80239,68.335954 L 534.83062,68.335954 L 533.50983,67.327487 L 531.19845,67.999799 L 530.04276,69.176344 L 529.71257,70.520967 L 524.75961,70.520967 L 524.75961,68.335954 L 518.32077,67.999799 L 517.99058,66.487097 L 513.03762,66.487097 L 511.38664,64.806318 L 509.90075,58.419357 L 509.07526,52.704708 L 507.09408,51.864318 L 504.7827,51.360084 L 504.1223,51.528162 L 503.79211,60.100136 L 472.09313,60.100136" id="MN_Canada" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-width:2.66530466;stroke-dasharray:none;stroke-opacity:1" />
+ </ns0:g>
+ </ns0:g>
+ </ns0:g>
+ </ns0:g>
+ </ns0:g>
+ <ns0:g id="g4675" style="fill:#000000;fill-opacity:0;stroke:#000000;stroke-opacity:1">
+ <ns0:path d="M 846.52085,274.72594 L 847.80814,273.69012 L 850.29359,272.77823 L 852.1404,272.10064 L 852.89317,271.82446 L 853.40684,271.87199" id="path3995" style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:1.33265233;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" ns1:nodetypes="cccccc" />
+ <ns0:path d="M 817.25107,257.90409 C 818.2317,258.85489 818.79206,259.61552 818.79206,259.61552 L 819.39913,260.85156 L 819.53922,260.70894" id="path5767" style="fill:#000000;fill-opacity:0;fill-rule:evenodd;stroke:#000000;stroke-width:0.41898587;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" ns1:nodetypes="cccc" />
+ </ns0:g>
+ </ns0:g>
+ </ns0:g>
+ <ns0:path d="M 152.15345,458.16063 L 151.84095,540.66102 L 153.40345,541.59852 L 156.37222,541.75477 L 157.77847,540.66102 L 160.27848,540.66102 L 160.43474,543.47353 L 167.15352,550.03606 L 167.62227,552.53607 L 170.90353,550.66106 L 171.52854,550.50481 L 171.84104,547.53605 L 173.24729,545.97354 L 174.34105,545.81729 L 176.21606,544.41103 L 179.18482,546.44229 L 179.80983,549.25481 L 181.68483,550.34856 L 182.77859,552.69232 L 186.52861,554.41108 L 189.80987,560.19236 L 192.46613,563.94237 L 194.65364,566.59864 L 196.0599,570.1924 L 200.90367,571.91116 L 205.9037,573.94242 L 206.8412,578.16119 L 207.30995,581.12995 L 206.37245,584.41122 L 204.34118,587.14562" id="AK_Canada" style="fill:none;stroke:#000000;stroke-width:2.5;stroke-dasharray:none;stroke-opacity:1" />
+ </ns0:g>
+ <ns0:path d="M 127.46583,540.97352 L 129.80959,539.56726 L 128.87209,537.8485 L 127.15333,538.78601 L 127.46583,540.97352 z M 86.528141,560.19236 L 87.621901,565.81738 L 90.434411,566.59864 L 95.278181,563.78612 L 99.496951,561.28611 L 97.934451,558.94235 L 98.403201,556.59859 L 96.371941,557.8486 L 93.559431,557.06734 L 95.121931,555.97359 L 96.996941,556.75484 L 100.74696,555.03608 L 101.21571,553.62983 L 98.871951,552.84857 L 99.653201,550.97356 L 96.996941,552.84857 L 92.465671,556.28609 L 87.778151,559.0986 L 86.528141,560.19236 z M 78.090601,572.22366 L 79.496861,573.94242 L 80.434371,572.84867 L 79.653111,570.97366 L 78.090601,572.22366 z M 55.278,574.09867 L 56.371751,571.91116 L 58.403011,572.22366 L 57.621761,574.09867 L 55.278,574.09867 z M 52.465487,571.12991 L 54.027994,573.00492 L 56.059251,571.44241 L 54.652997,570.1924 L 52.465487,571.12991 z M 5.1215179,575.50493 L 8.4027829,573.31742 L 11.684048,572.37991 L 14.18406,572.69241 L 14.652812,574.25492 L 16.527821,574.72367 L 18.402829,572.84867 L 18.090328,571.28616 L 20.74659,570.66116 L 23.559103,573.16117 L 22.465348,574.87992 L 18.246579,575.97368 L 15.590316,575.50493 L 11.99655,574.41117 L 7.7777799,575.81743 L 6.2152729,576.12993 L 5.1215179,575.50493 z M 18.402829,504.8796 L 19.496585,507.37961 L 20.59034,508.94212 L 19.496585,509.72337 L 17.465325,506.75461 L 17.465325,504.8796 L 18.402829,504.8796 z M 38.402922,518.47341 L 41.996689,519.25467 L 45.590455,520.19217 L 46.371709,521.12968 L 44.809202,524.72344 L 41.840438,524.56719 L 38.559173,521.12968 L 38.402922,518.47341 z M 40.902934,486.12951 L 42.934193,491.28579 L 42.777942,492.22329 L 39.965429,491.91079 L 38.246671,488.00452 L 36.527913,486.59827 L 34.184152,486.59827 L 34.027902,484.09825 L 35.74666,481.75449 L 36.840415,484.09825 L 38.246671,485.50451 L 40.902934,486.12951 z M 204.65369,586.59873 L 203.09118,585.81748 L 201.68493,582.84871 L 199.02866,581.44246 L 197.30991,580.3487 L 196.52865,581.12995 L 197.93491,583.78622 L 198.09116,587.37998 L 196.9974,587.84873 L 195.1224,585.97373 L 193.09114,584.72372 L 193.55989,586.28623 L 194.80989,588.00499 L 194.02864,588.78624 C 194.02864,588.78624 193.24739,588.47374 192.77864,587.84873 C 192.30988,587.22373 190.74738,584.56747 190.74738,584.56747 L 189.80987,582.37996 C 189.80987,582.37996 189.49737,583.62997 188.87237,583.31746 C 188.24736,583.00496 187.62236,581.91121 187.62236,581.91121 L 189.34112,580.0362 L 187.93486,578.62994 L 187.93486,573.78617 L 187.15361,573.78617 L 186.37236,577.06743 L 185.2786,577.53619 L 184.3411,573.94242 L 183.71609,570.34865 L 182.93484,569.8799 L 183.24734,575.34868 L 183.24734,576.44243 L 181.84108,575.19243 L 178.40357,569.41115 L 176.37231,568.9424 L 175.74731,565.34863 L 174.1848,562.53612 L 172.62229,561.44236 L 172.62229,559.25485 L 174.65355,558.00485 L 174.1848,557.69235 L 171.68479,558.31735 L 168.40352,555.97359 L 165.90351,553.16107 L 161.21599,550.66106 L 157.30972,548.16105 L 158.55973,545.03604 L 158.55973,543.47353 L 156.84097,545.03604 L 154.02846,546.12979 L 150.43469,545.03604 L 144.96591,542.69228 L 139.65339,542.69228 L 139.02839,543.16103 L 132.77836,539.41101 L 130.7471,539.09851 L 128.09084,533.47348 L 124.65332,533.78598 L 121.2158,535.19224 L 121.68456,539.56726 L 122.77831,536.75475 L 123.71582,537.06725 L 122.30956,541.28602 L 125.43457,538.62976 L 126.05958,540.19226 L 122.30956,544.41103 L 121.05955,544.09853 L 120.5908,542.22352 L 119.3408,541.44227 L 118.09079,542.53603 L 115.43453,540.81727 L 112.46576,542.84853 L 110.74701,544.87979 L 107.46574,546.91105 L 102.93447,546.75479 L 102.46572,544.72354 L 106.05948,544.09853 L 106.05948,542.84853 L 103.87197,542.22352 L 104.80948,539.87976 L 106.99699,536.12975 L 106.99699,534.41099 L 107.15324,533.62973 L 111.37201,531.44222 L 112.30951,532.69223 L 114.96578,532.69223 L 113.71577,530.19222 L 110.122,529.87972 L 105.27823,532.53598 L 102.93447,535.81724 L 101.21571,538.31726 L 100.12196,540.50477 L 96.059441,541.91102 L 93.090671,544.41103 L 92.778171,545.97354 L 94.965681,546.91105 L 95.746941,548.9423 L 93.090671,552.06732 L 86.840651,556.12984 L 79.340611,560.19236 L 77.309351,561.28611 L 72.153081,562.37987 L 66.996801,564.56738 L 68.715561,565.81738 L 67.309301,567.22364 L 66.840551,568.31739 L 64.184291,567.37989 L 61.059281,567.53614 L 60.278021,569.72365 L 59.340521,569.72365 L 59.653021,567.37989 L 56.215501,568.6299 L 53.402991,569.5674 L 50.121726,568.31739 L 47.309213,570.1924 L 44.184199,570.1924 L 42.152939,571.44241 L 40.590432,572.22366 L 38.559173,571.91116 L 36.059161,570.81741 L 33.871651,571.44241 L 32.934147,572.37991 L 31.371639,571.28616 L 31.371639,569.41115 L 34.340403,568.16114 L 40.434181,568.78615 L 44.652951,567.22364 L 46.68421,565.19238 L 49.496723,564.56738 L 51.215481,563.78612 L 53.871744,563.94237 L 55.434251,565.19238 L 56.371751,564.87988 L 58.559261,562.22362 L 61.528031,561.28611 L 64.809291,560.66111 L 66.059301,560.34861 L 66.684301,560.81736 L 67.465561,560.81736 L 68.715561,557.22359 L 72.621831,555.81734 L 74.496841,552.22357 L 76.684351,547.84855 L 78.246861,546.44229 L 78.559361,543.94228 L 76.996851,545.19229 L 73.715581,545.81729 L 73.090581,543.47353 L 71.840581,543.16103 L 70.903071,544.09853 L 70.746821,546.91105 L 69.340561,546.75479 L 67.934311,541.12977 L 66.684301,542.37977 L 65.590551,541.91102 L 65.278051,540.03601 L 61.371781,540.19226 L 59.340521,541.28602 L 56.840511,540.97352 L 58.246761,539.56726 L 58.715511,537.06725 L 58.090511,535.19224 L 59.496771,534.25474 L 60.746771,534.09849 L 60.121771,532.37973 L 60.121771,528.16096 L 59.184271,527.22345 L 58.403011,528.62971 L 52.465487,528.62971 L 51.059231,527.3797 L 50.434228,523.62969 L 48.402968,520.19217 L 48.402968,519.25467 L 50.434228,518.47341 L 50.590478,516.44215 L 51.684233,515.3484 L 50.90298,514.87965 L 49.652974,515.3484 L 48.559219,512.69214 L 49.496723,507.84836 L 53.871744,504.72335 L 56.371751,503.16084 L 58.246761,499.56708 L 60.903031,498.31707 L 63.403041,499.41083 L 63.715541,501.75459 L 66.059301,501.44208 L 69.184311,499.09832 L 70.746821,499.72333 L 71.684321,500.34833 L 73.246831,500.34833 L 75.434341,499.09832 L 76.215601,494.87955 C 76.215601,494.87955 76.528101,492.06704 77.153101,491.59829 C 77.778101,491.12954 78.090601,490.66079 78.090601,490.66079 L 76.996851,488.78578 L 74.496841,489.56703 L 71.371821,490.34828 L 69.496811,489.87953 L 66.059301,488.16077 L 61.215531,488.00452 L 57.778011,484.41076 L 58.246761,480.66074 L 58.871771,478.31698 L 56.840511,476.59822 L 54.965499,473.00445 L 55.434251,472.2232 L 61.996781,471.75445 L 64.028041,471.75445 L 64.965541,472.69195 L 65.590551,472.69195 L 65.434301,471.12944 L 69.184311,470.50444 L 71.684321,470.81694 L 73.090581,471.9107 L 71.684321,473.94196 L 71.215571,475.34821 L 73.871841,476.91072 L 78.715611,478.62948 L 80.434371,477.69198 L 78.246861,473.47321 L 77.309351,470.34819 L 78.246861,469.56694 L 74.965591,467.69193 L 74.496841,466.59817 L 74.965591,465.03567 L 74.184341,461.28565 L 71.371821,456.75438 L 69.028061,452.69186 L 71.840581,450.81685 L 74.965591,450.81685 L 76.684351,451.44185 L 80.746871,451.2856 L 84.340631,447.84809 L 85.434391,444.87932 L 89.028161,442.53556 L 90.590661,443.47307 L 93.246921,442.84806 L 96.840691,440.8168 L 97.934451,440.66055 L 98.871951,441.44181 L 103.24697,441.28556 L 105.90323,438.31679 L 106.99699,438.31679 L 110.4345,440.66055 L 112.30951,442.69181 L 111.84076,443.78557 L 112.46576,444.87932 L 114.02827,443.31682 L 117.77829,443.62932 L 118.09079,447.22308 L 119.9658,448.62934 L 126.84083,449.25434 L 132.93461,453.31686 L 134.34086,452.37936 L 139.34089,454.87937 L 141.37215,454.25437 L 143.24716,453.47311 L 147.93468,455.34812 L 152.15345,458.16063" id="AK_Pacific" style="fill:none;fill-opacity:0.26666697;stroke:#80b0f0;stroke-width:1pt" />
+ <ns0:g id="Frames" transform="translate(-18.307669,-131.99439)">
+ <ns0:path d="M 229.21212,631.12334 L 229.68087,688.43625 L 264.68114,723.74902 M 18.429285,562.99783 L 161.86786,563.31033 L 229.52462,631.59209 L 314.68151,631.74834 L 370.43191,685.18624 L 370.11941,723.43652" id="Inset_border" style="fill:none;fill-opacity:0.75;stroke:#000000;stroke-width:1.875;stroke-dasharray:none" ns1:nodetypes="ccccccccc" />
+ <ns0:rect height="590.28674" id="Outer_border" style="fill:none;stroke:#000000;stroke-width:2.5;stroke-dasharray:none" width="955.48639" x="19.444839" y="133.89751" />
+ </ns0:g>
+ <ns0:path d="M 822.91849,258.28198 A 4.1274123,3.62712 0 1 1 814.66366,258.28198 A 4.1274123,3.62712 0 1 1 822.91849,258.28198 z" id="DC" style="opacity:1;fill:#a02c2c;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" transform="matrix(0.9707988,0,0,1.0018987,24.345102,-0.4278704)" ns1:cx="818.79108" ns1:cy="258.28198" ns1:rx="4.1274123" ns1:ry="3.62712" ns1:type="arc" ns2:label="#DC" />
+</ns0:svg> \ No newline at end of file
diff --git a/tests/phpunit/data/media/Wikimedia-logo.svg b/tests/phpunit/data/media/Wikimedia-logo.svg
new file mode 100644
index 00000000..1e17acbe
--- /dev/null
+++ b/tests/phpunit/data/media/Wikimedia-logo.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Wikimedia logo" viewBox="-599 -599 1198 1198" width="1024" height="1024">
+<defs>
+ <clipPath id="mask">
+ <path d="M 47.5,-87.5 v 425 h -95 v -425 l -552,-552 v 1250 h 1199 v -1250 z"/>
+ </clipPath>
+</defs>
+<g clip-path="url(#mask)">
+ <circle id="green parts" fill="#396" r="336.5"/>
+ <circle id="blue arc" fill="none" stroke="#069" r="480.25" stroke-width="135.5"/>
+</g>
+<circle fill="#900" cy="-379.5" r="184.5" id="red circle"/>
+</svg> \ No newline at end of file
diff --git a/tests/phpunit/data/media/Xmp-exif-multilingual_test.jpg b/tests/phpunit/data/media/Xmp-exif-multilingual_test.jpg
new file mode 100644
index 00000000..f7b23025
--- /dev/null
+++ b/tests/phpunit/data/media/Xmp-exif-multilingual_test.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/animated-xmp.gif b/tests/phpunit/data/media/animated-xmp.gif
new file mode 100644
index 00000000..fcba079d
--- /dev/null
+++ b/tests/phpunit/data/media/animated-xmp.gif
Binary files differ
diff --git a/tests/phpunit/data/media/animated.gif b/tests/phpunit/data/media/animated.gif
new file mode 100644
index 00000000..a8f248b3
--- /dev/null
+++ b/tests/phpunit/data/media/animated.gif
Binary files differ
diff --git a/tests/phpunit/data/media/broken_exif_date.jpg b/tests/phpunit/data/media/broken_exif_date.jpg
new file mode 100644
index 00000000..82f62f57
--- /dev/null
+++ b/tests/phpunit/data/media/broken_exif_date.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/exif-gps.jpg b/tests/phpunit/data/media/exif-gps.jpg
new file mode 100644
index 00000000..40137340
--- /dev/null
+++ b/tests/phpunit/data/media/exif-gps.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/exif-user-comment.jpg b/tests/phpunit/data/media/exif-user-comment.jpg
new file mode 100644
index 00000000..9f23966a
--- /dev/null
+++ b/tests/phpunit/data/media/exif-user-comment.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/greyscale-na-png.png b/tests/phpunit/data/media/greyscale-na-png.png
new file mode 100644
index 00000000..4a4b7452
--- /dev/null
+++ b/tests/phpunit/data/media/greyscale-na-png.png
Binary files differ
diff --git a/tests/phpunit/data/media/greyscale-png.png b/tests/phpunit/data/media/greyscale-png.png
new file mode 100644
index 00000000..340a67b4
--- /dev/null
+++ b/tests/phpunit/data/media/greyscale-png.png
Binary files differ
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
--- /dev/null
+++ b/tests/phpunit/data/media/iptc-invalid-psir.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/iptc-timetest-invalid.jpg b/tests/phpunit/data/media/iptc-timetest-invalid.jpg
new file mode 100644
index 00000000..b03e192a
--- /dev/null
+++ b/tests/phpunit/data/media/iptc-timetest-invalid.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/iptc-timetest.jpg b/tests/phpunit/data/media/iptc-timetest.jpg
new file mode 100644
index 00000000..db9932ba
--- /dev/null
+++ b/tests/phpunit/data/media/iptc-timetest.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-comment-binary.jpg b/tests/phpunit/data/media/jpeg-comment-binary.jpg
new file mode 100644
index 00000000..b467fe43
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-comment-binary.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-comment-iso8859-1.jpg b/tests/phpunit/data/media/jpeg-comment-iso8859-1.jpg
new file mode 100644
index 00000000..d9ffbac1
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-comment-iso8859-1.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-comment-multiple.jpg b/tests/phpunit/data/media/jpeg-comment-multiple.jpg
new file mode 100644
index 00000000..363c7385
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-comment-multiple.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-comment-utf.jpg b/tests/phpunit/data/media/jpeg-comment-utf.jpg
new file mode 100644
index 00000000..d6d35b4b
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-comment-utf.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-iptc-bad-hash.jpg b/tests/phpunit/data/media/jpeg-iptc-bad-hash.jpg
new file mode 100644
index 00000000..6464c5b8
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-iptc-bad-hash.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-iptc-good-hash.jpg b/tests/phpunit/data/media/jpeg-iptc-good-hash.jpg
new file mode 100644
index 00000000..ef970854
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-iptc-good-hash.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-padding-even.jpg b/tests/phpunit/data/media/jpeg-padding-even.jpg
new file mode 100644
index 00000000..c83c66bd
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-padding-even.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-padding-odd.jpg b/tests/phpunit/data/media/jpeg-padding-odd.jpg
new file mode 100644
index 00000000..25b93308
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-padding-odd.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-xmp-alt.jpg b/tests/phpunit/data/media/jpeg-xmp-alt.jpg
new file mode 100644
index 00000000..0e2c3f63
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-xmp-alt.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-xmp-psir.jpg b/tests/phpunit/data/media/jpeg-xmp-psir.jpg
new file mode 100644
index 00000000..4d19fcbe
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-xmp-psir.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/jpeg-xmp-psir.xmp b/tests/phpunit/data/media/jpeg-xmp-psir.xmp
new file mode 100644
index 00000000..fee6ee18
--- /dev/null
+++ b/tests/phpunit/data/media/jpeg-xmp-psir.xmp
@@ -0,0 +1,35 @@
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+ xmlns:dc='http://purl.org/dc/elements/1.1/'>
+ <dc:identifier>jpeg-xmp-psir.jpg</dc:identifier>
+ </rdf:Description>
+</rdf:RDF>
+</x:xmpmeta>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+<?xpacket end='w'?> \ No newline at end of file
diff --git a/tests/phpunit/data/media/landscape-plain.jpg b/tests/phpunit/data/media/landscape-plain.jpg
new file mode 100644
index 00000000..cf296555
--- /dev/null
+++ b/tests/phpunit/data/media/landscape-plain.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/nonanimated.gif b/tests/phpunit/data/media/nonanimated.gif
new file mode 100644
index 00000000..9e52a7f0
--- /dev/null
+++ b/tests/phpunit/data/media/nonanimated.gif
Binary files differ
diff --git a/tests/phpunit/data/media/portrait-rotated.jpg b/tests/phpunit/data/media/portrait-rotated.jpg
new file mode 100644
index 00000000..445feaed
--- /dev/null
+++ b/tests/phpunit/data/media/portrait-rotated.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/rgb-na-png.png b/tests/phpunit/data/media/rgb-na-png.png
new file mode 100644
index 00000000..2f2a5ca0
--- /dev/null
+++ b/tests/phpunit/data/media/rgb-na-png.png
Binary files differ
diff --git a/tests/phpunit/data/media/rgb-png.png b/tests/phpunit/data/media/rgb-png.png
new file mode 100644
index 00000000..6f40cc92
--- /dev/null
+++ b/tests/phpunit/data/media/rgb-png.png
Binary files differ
diff --git a/tests/phpunit/data/media/test.jpg b/tests/phpunit/data/media/test.jpg
new file mode 100644
index 00000000..cb084253
--- /dev/null
+++ b/tests/phpunit/data/media/test.jpg
Binary files differ
diff --git a/tests/phpunit/data/media/test.tiff b/tests/phpunit/data/media/test.tiff
new file mode 100644
index 00000000..6a36f760
--- /dev/null
+++ b/tests/phpunit/data/media/test.tiff
Binary files differ
diff --git a/tests/phpunit/data/media/xmp.png b/tests/phpunit/data/media/xmp.png
new file mode 100644
index 00000000..6b9f7a87
--- /dev/null
+++ b/tests/phpunit/data/media/xmp.png
Binary files differ
diff --git a/tests/phpunit/data/xmp/1.result.php b/tests/phpunit/data/xmp/1.result.php
new file mode 100644
index 00000000..beead1bd
--- /dev/null
+++ b/tests/phpunit/data/xmp/1.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+);
diff --git a/tests/phpunit/data/xmp/1.xmp b/tests/phpunit/data/xmp/1.xmp
new file mode 100644
index 00000000..66e15427
--- /dev/null
+++ b/tests/phpunit/data/xmp/1.xmp
@@ -0,0 +1,11 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:DigitalZoomRatio="0/10">
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/2.result.php b/tests/phpunit/data/xmp/2.result.php
new file mode 100644
index 00000000..beead1bd
--- /dev/null
+++ b/tests/phpunit/data/xmp/2.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+);
diff --git a/tests/phpunit/data/xmp/2.xmp b/tests/phpunit/data/xmp/2.xmp
new file mode 100644
index 00000000..0fa6a894
--- /dev/null
+++ b/tests/phpunit/data/xmp/2.xmp
@@ -0,0 +1,12 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:DigitalZoomRatio="0/10">
+<exif:Flash>
+<rdf:Description exif:Return="0">
+<exif:Fired>True</exif:Fired> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/3-invalid.result.php b/tests/phpunit/data/xmp/3-invalid.result.php
new file mode 100644
index 00000000..5741b2c9
--- /dev/null
+++ b/tests/phpunit/data/xmp/3-invalid.result.php
@@ -0,0 +1,7 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ )
+);
diff --git a/tests/phpunit/data/xmp/3-invalid.xmp b/tests/phpunit/data/xmp/3-invalid.xmp
new file mode 100644
index 00000000..2425e254
--- /dev/null
+++ b/tests/phpunit/data/xmp/3-invalid.xmp
@@ -0,0 +1,31 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<!--
+This file has an invalid flash compoenent (one of the values are a qualifier)
+-->
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+>
+<exif:DigitalZoomRatio>
+
+<rdf:Description>
+<rdf:value>
+0/10
+</rdf:value>
+<exif:foobarbaz>fred</exif:foobarbaz>
+
+</rdf:Description>
+
+</exif:DigitalZoomRatio>
+
+<exif:Flash>
+<rdf:Description exif:Return="0">
+<exif:Mode><rdf:Description>
+<rdf:value>1</rdf:value>
+<exif:Fired>False</exif:Fired> <!-- qualifier. should be ignored-->
+</rdf:Description>
+</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/3.result.php b/tests/phpunit/data/xmp/3.result.php
new file mode 100644
index 00000000..beead1bd
--- /dev/null
+++ b/tests/phpunit/data/xmp/3.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+);
diff --git a/tests/phpunit/data/xmp/3.xmp b/tests/phpunit/data/xmp/3.xmp
new file mode 100644
index 00000000..2cf19883
--- /dev/null
+++ b/tests/phpunit/data/xmp/3.xmp
@@ -0,0 +1,29 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+>
+<exif:DigitalZoomRatio>
+
+<rdf:Description>
+<rdf:value>
+0/10
+</rdf:value>
+<exif:foobarbaz>fred</exif:foobarbaz>
+
+</rdf:Description>
+
+</exif:DigitalZoomRatio>
+
+<exif:Flash>
+<rdf:Description exif:Return="0">
+<exif:Fired>True</exif:Fired>
+<exif:Mode><rdf:Description>
+<rdf:value>1</rdf:value>
+<exif:Fired>False</exif:Fired> <!-- qualifier. should be ignored-->
+</rdf:Description>
+</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></rdf:Description></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/4.result.php b/tests/phpunit/data/xmp/4.result.php
new file mode 100644
index 00000000..5741b2c9
--- /dev/null
+++ b/tests/phpunit/data/xmp/4.result.php
@@ -0,0 +1,7 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ )
+);
diff --git a/tests/phpunit/data/xmp/4.xmp b/tests/phpunit/data/xmp/4.xmp
new file mode 100644
index 00000000..29eb614b
--- /dev/null
+++ b/tests/phpunit/data/xmp/4.xmp
@@ -0,0 +1,22 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<!-- Valid output is just the DigitalZoomRatio
+as the flash is a qualifier
+-->
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/">
+ <exif:DigitalZoomRatio>
+<rdf:Description>
+<rdf:value>
+0/10
+</rdf:value>
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash>
+</rdf:Description>
+</exif:DigitalZoomRatio>
+</rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/5.result.php b/tests/phpunit/data/xmp/5.result.php
new file mode 100644
index 00000000..5741b2c9
--- /dev/null
+++ b/tests/phpunit/data/xmp/5.result.php
@@ -0,0 +1,7 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ )
+);
diff --git a/tests/phpunit/data/xmp/5.xmp b/tests/phpunit/data/xmp/5.xmp
new file mode 100644
index 00000000..3cc61d68
--- /dev/null
+++ b/tests/phpunit/data/xmp/5.xmp
@@ -0,0 +1,16 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/">
+ <exif:DigitalZoomRatio>
+<rdf:Description rdf:value="0/10">
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash>
+</rdf:Description>
+</exif:DigitalZoomRatio>
+</rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/6.result.php b/tests/phpunit/data/xmp/6.result.php
new file mode 100644
index 00000000..beead1bd
--- /dev/null
+++ b/tests/phpunit/data/xmp/6.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+);
diff --git a/tests/phpunit/data/xmp/6.xmp b/tests/phpunit/data/xmp/6.xmp
new file mode 100644
index 00000000..f435ab23
--- /dev/null
+++ b/tests/phpunit/data/xmp/6.xmp
@@ -0,0 +1,18 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/">
+<exif:DigitalZoomRatio>
+0/10
+</exif:DigitalZoomRatio>
+</rdf:Description>
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/">
+
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/7.result.php b/tests/phpunit/data/xmp/7.result.php
new file mode 100644
index 00000000..115cdc92
--- /dev/null
+++ b/tests/phpunit/data/xmp/7.result.php
@@ -0,0 +1,52 @@
+<?php
+$result = array(
+ 'xmp-exif' =>
+ array(
+ 'CameraOwnerName' => 'Me!',
+ ),
+ 'xmp-general' =>
+ array(
+ 'LicenseUrl' => 'http://creativecommons.com/cc-by-2.9',
+ 'ImageDescription' =>
+ array(
+ 'x-default' => 'Test image for the cc: xmp: xmpRights: namespaces in xmp',
+ '_type' => 'lang',
+ ),
+ 'ObjectName' =>
+ array(
+ 'x-default' => 'xmp core/xmp rights/cc ns test',
+ '_type' => 'lang',
+ ),
+ 'DateTimeDigitized' => '2005:04:03',
+ 'Software' => 'The one true editor: Vi (ok i used gimp)',
+ 'Identifier' =>
+ array(
+ 0 => 'http://example.com/identifierurl',
+ 1 => 'urn:sha1:342524abcdef',
+ '_type' => 'ul',
+ ),
+ 'Label' => 'Test image',
+ 'DateTimeMetadata' => '2011:05:12',
+ 'DateTime' => '2007:03:04 06:34:10',
+ 'Nickname' => 'My little xmp test image',
+ 'Rating' => '5',
+ 'RightsCertificate' => 'http://example.com/rights-certificate/',
+ 'Copyrighted' => 'True',
+ 'CopyrightOwner' =>
+ array(
+ 0 => 'Bawolff is copyright owner',
+ '_type' => 'ul',
+ ),
+ 'UsageTerms' =>
+ array(
+ 'x-default' => 'do whatever you want',
+ 'en-gb' => 'Do whatever you want in british english',
+ '_type' => 'lang',
+ ),
+ 'WebStatement' => 'http://example.com/web_statement',
+ ),
+ 'xmp-deprecated' =>
+ array(
+ 'Identifier' => 'http://example.com/identifierurl/wrong',
+ ),
+);
diff --git a/tests/phpunit/data/xmp/7.xmp b/tests/phpunit/data/xmp/7.xmp
new file mode 100644
index 00000000..e18e13d9
--- /dev/null
+++ b/tests/phpunit/data/xmp/7.xmp
@@ -0,0 +1,67 @@
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+ xmlns:aux='http://ns.adobe.com/exif/1.0/aux/'>
+ <aux:OwnerName>Me!</aux:OwnerName>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+ xmlns:cc='http://creativecommons.org/ns#'>
+ <cc:license>http://creativecommons.com/cc-by-2.9</cc:license>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+ xmlns:dc='http://purl.org/dc/elements/1.1/'>
+ <dc:description>
+ <rdf:Alt>
+ <rdf:li xml:lang='x-default'>Test image for the cc: xmp: xmpRights: namespaces in xmp</rdf:li>
+ </rdf:Alt>
+ </dc:description>
+ <dc:identifier>http://example.com/identifierurl/wrong</dc:identifier>
+ <dc:title>
+ <rdf:Alt>
+ <rdf:li xml:lang='x-default'>xmp core/xmp rights/cc ns test</rdf:li>
+ </rdf:Alt>
+ </dc:title>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+ xmlns:xmp='http://ns.adobe.com/xap/1.0/'>
+ <xmp:CreateDate>2005-04-03</xmp:CreateDate>
+ <xmp:CreatorTool>The one true editor: Vi (ok i used gimp)</xmp:CreatorTool>
+ <xmp:Identifier>
+ <rdf:Bag>
+ <rdf:li>http://example.com/identifierurl
+</rdf:li>
+ <rdf:li>urn:sha1:342524abcdef</rdf:li>
+ </rdf:Bag>
+ </xmp:Identifier>
+ <xmp:Label>Test image</xmp:Label>
+ <xmp:MetadataDate>2011-05-12</xmp:MetadataDate>
+ <xmp:ModifyDate>2007-03-04T12:34:10-06:00</xmp:ModifyDate>
+ <xmp:Nickname>My little xmp test image</xmp:Nickname>
+ <xmp:Rating>7</xmp:Rating>
+ </rdf:Description>
+
+ <rdf:Description rdf:about=''
+ xmlns:xmpRights='http://ns.adobe.com/xap/1.0/rights/'>
+ <xmpRights:Certificate>http://example.com/rights-certificate/</xmpRights:Certificate>
+ <xmpRights:Marked>True</xmpRights:Marked>
+ <xmpRights:Owner>
+ <rdf:Bag>
+ <rdf:li>Bawolff is copyright owner</rdf:li>
+ </rdf:Bag>
+ </xmpRights:Owner>
+ <xmpRights:UsageTerms>
+ <rdf:Alt>
+ <rdf:li xml:lang='x-default'>do whatever you want</rdf:li>
+ <rdf:li xml:lang='en-GB'>Do whatever you want in british english</rdf:li>
+ </rdf:Alt>
+ </xmpRights:UsageTerms>
+ <xmpRights:WebStatement>http://example.com/web_statement</xmpRights:WebStatement>
+ </rdf:Description>
+</rdf:RDF>
+</x:xmpmeta>
+<?xpacket end='r'?>
diff --git a/tests/phpunit/data/xmp/README b/tests/phpunit/data/xmp/README
new file mode 100644
index 00000000..bd949176
--- /dev/null
+++ b/tests/phpunit/data/xmp/README
@@ -0,0 +1,3 @@
+This directory contains a bunch of XMP files
+as well as a bunch of php files containing what the
+parsed version of the XMP looks like.
diff --git a/tests/phpunit/data/xmp/bag-for-seq.result.php b/tests/phpunit/data/xmp/bag-for-seq.result.php
new file mode 100644
index 00000000..b5244f88
--- /dev/null
+++ b/tests/phpunit/data/xmp/bag-for-seq.result.php
@@ -0,0 +1,10 @@
+<?php
+
+$result = array(
+ 'xmp-general' => array(
+ 'Artist' => array(
+ '_type' => 'ul',
+ 0 => 'The author',
+ )
+ )
+);
diff --git a/tests/phpunit/data/xmp/bag-for-seq.xmp b/tests/phpunit/data/xmp/bag-for-seq.xmp
new file mode 100644
index 00000000..c6ed5b7c
--- /dev/null
+++ b/tests/phpunit/data/xmp/bag-for-seq.xmp
@@ -0,0 +1 @@
+<?xpacket begin=""?> <x:xmpmeta xmlns:x="adobe:ns:meta/"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/"> <dc:creator> <rdf:Bag> <rdf:li>The author</rdf:li> </rdf:Bag> </dc:creator> </rdf:Description> </rdf:RDF> </x:xmpmeta>
diff --git a/tests/phpunit/data/xmp/flash.result.php b/tests/phpunit/data/xmp/flash.result.php
new file mode 100644
index 00000000..018c0ac1
--- /dev/null
+++ b/tests/phpunit/data/xmp/flash.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '127'
+ )
+);
diff --git a/tests/phpunit/data/xmp/flash.xmp b/tests/phpunit/data/xmp/flash.xmp
new file mode 100644
index 00000000..b1373cc2
--- /dev/null
+++ b/tests/phpunit/data/xmp/flash.xmp
@@ -0,0 +1,11 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:DigitalZoomRatio="0/10">
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>3</exif:Return> <exif:Mode>3</exif:Mode> <exif:Function>True</exif:Function> <exif:RedEyeMode>True</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/gps.result.php b/tests/phpunit/data/xmp/gps.result.php
new file mode 100644
index 00000000..8ea9c68c
--- /dev/null
+++ b/tests/phpunit/data/xmp/gps.result.php
@@ -0,0 +1,11 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'GPSAltitude' => -3.14159265301,
+ 'GPSDOP' => '5/1',
+ 'GPSLatitude' => 88.51805555,
+ 'GPSLongitude' => -21.12356945,
+ 'GPSVersionID' => '2.2.0.0'
+ )
+);
diff --git a/tests/phpunit/data/xmp/gps.xmp b/tests/phpunit/data/xmp/gps.xmp
new file mode 100644
index 00000000..e52d2c8a
--- /dev/null
+++ b/tests/phpunit/data/xmp/gps.xmp
@@ -0,0 +1,17 @@
+<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>
+<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 7.30'>
+<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
+
+ <rdf:Description rdf:about=''
+ xmlns:exif='http://ns.adobe.com/exif/1.0/'>
+ <exif:GPSAltitude>103993/33102</exif:GPSAltitude>
+ <exif:GPSAltitudeRef>1</exif:GPSAltitudeRef>
+ <exif:GPSDOP>5/1</exif:GPSDOP>
+ <exif:GPSLatitude>88,31.083333N</exif:GPSLatitude>
+ <exif:GPSLongitude>21,7.414167W</exif:GPSLongitude>
+ <exif:GPSVersionID>2.2.0.0</exif:GPSVersionID>
+ </rdf:Description>
+
+</rdf:RDF>
+</x:xmpmeta>
+<?xpacket end='w'?>
diff --git a/tests/phpunit/data/xmp/invalid-child-not-struct.result.php b/tests/phpunit/data/xmp/invalid-child-not-struct.result.php
new file mode 100644
index 00000000..5741b2c9
--- /dev/null
+++ b/tests/phpunit/data/xmp/invalid-child-not-struct.result.php
@@ -0,0 +1,7 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ )
+);
diff --git a/tests/phpunit/data/xmp/invalid-child-not-struct.xmp b/tests/phpunit/data/xmp/invalid-child-not-struct.xmp
new file mode 100644
index 00000000..6aa0c10b
--- /dev/null
+++ b/tests/phpunit/data/xmp/invalid-child-not-struct.xmp
@@ -0,0 +1,12 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:DigitalZoomRatio="0/10">
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode>
+
+ </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/no-namespace.result.php b/tests/phpunit/data/xmp/no-namespace.result.php
new file mode 100644
index 00000000..3ff69201
--- /dev/null
+++ b/tests/phpunit/data/xmp/no-namespace.result.php
@@ -0,0 +1,7 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'FNumber' => '28/10',
+ )
+);
diff --git a/tests/phpunit/data/xmp/no-namespace.xmp b/tests/phpunit/data/xmp/no-namespace.xmp
new file mode 100644
index 00000000..7d6cdb2f
--- /dev/null
+++ b/tests/phpunit/data/xmp/no-namespace.xmp
@@ -0,0 +1,11 @@
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<!-- Testing it handles random non-namespaced properties in files ok.
+ Some older photoshop's did not include the rdf: prefix on about. -->
+<rdf:Description
+ about=""
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:FNumber="28/10">
+</rdf:Description>
+</rdf:RDF>
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/no-recognized-props.result.php b/tests/phpunit/data/xmp/no-recognized-props.result.php
new file mode 100644
index 00000000..b3ca9f5a
--- /dev/null
+++ b/tests/phpunit/data/xmp/no-recognized-props.result.php
@@ -0,0 +1,2 @@
+<?php
+$result = array();
diff --git a/tests/phpunit/data/xmp/no-recognized-props.xmp b/tests/phpunit/data/xmp/no-recognized-props.xmp
new file mode 100644
index 00000000..54e80901
--- /dev/null
+++ b/tests/phpunit/data/xmp/no-recognized-props.xmp
@@ -0,0 +1,8 @@
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/not-exif-namespace"
+ exif:FNumber="2/10">
+</rdf:Description>
+</rdf:RDF>
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/utf16BE.result.php b/tests/phpunit/data/xmp/utf16BE.result.php
new file mode 100644
index 00000000..ac7ea506
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf16BE.result.php
@@ -0,0 +1,12 @@
+<?php
+
+$result = array(
+ 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ ),
+ 'xmp-general' =>
+ array(
+ 'Label' => '􊯍'
+ ),
+);
diff --git a/tests/phpunit/data/xmp/utf16BE.xmp b/tests/phpunit/data/xmp/utf16BE.xmp
new file mode 100644
index 00000000..0cf60d60
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf16BE.xmp
Binary files differ
diff --git a/tests/phpunit/data/xmp/utf16LE.result.php b/tests/phpunit/data/xmp/utf16LE.result.php
new file mode 100644
index 00000000..ac7ea506
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf16LE.result.php
@@ -0,0 +1,12 @@
+<?php
+
+$result = array(
+ 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ ),
+ 'xmp-general' =>
+ array(
+ 'Label' => '􊯍'
+ ),
+);
diff --git a/tests/phpunit/data/xmp/utf16LE.xmp b/tests/phpunit/data/xmp/utf16LE.xmp
new file mode 100644
index 00000000..66d71f4c
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf16LE.xmp
Binary files differ
diff --git a/tests/phpunit/data/xmp/utf32BE.result.php b/tests/phpunit/data/xmp/utf32BE.result.php
new file mode 100644
index 00000000..ac7ea506
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf32BE.result.php
@@ -0,0 +1,12 @@
+<?php
+
+$result = array(
+ 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ ),
+ 'xmp-general' =>
+ array(
+ 'Label' => '􊯍'
+ ),
+);
diff --git a/tests/phpunit/data/xmp/utf32BE.xmp b/tests/phpunit/data/xmp/utf32BE.xmp
new file mode 100644
index 00000000..06afdf92
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf32BE.xmp
Binary files differ
diff --git a/tests/phpunit/data/xmp/utf32LE.result.php b/tests/phpunit/data/xmp/utf32LE.result.php
new file mode 100644
index 00000000..ac7ea506
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf32LE.result.php
@@ -0,0 +1,12 @@
+<?php
+
+$result = array(
+ 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ ),
+ 'xmp-general' =>
+ array(
+ 'Label' => '􊯍'
+ ),
+);
diff --git a/tests/phpunit/data/xmp/utf32LE.xmp b/tests/phpunit/data/xmp/utf32LE.xmp
new file mode 100644
index 00000000..bf2097fe
--- /dev/null
+++ b/tests/phpunit/data/xmp/utf32LE.xmp
Binary files differ
diff --git a/tests/phpunit/data/xmp/xmpExt.result.php b/tests/phpunit/data/xmp/xmpExt.result.php
new file mode 100644
index 00000000..beead1bd
--- /dev/null
+++ b/tests/phpunit/data/xmp/xmpExt.result.php
@@ -0,0 +1,8 @@
+<?php
+
+$result = array( 'xmp-exif' =>
+ array(
+ 'DigitalZoomRatio' => '0/10',
+ 'Flash' => '9'
+ )
+);
diff --git a/tests/phpunit/data/xmp/xmpExt.xmp b/tests/phpunit/data/xmp/xmpExt.xmp
new file mode 100644
index 00000000..da0383f8
--- /dev/null
+++ b/tests/phpunit/data/xmp/xmpExt.xmp
@@ -0,0 +1,13 @@
+<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="Adobe XMP Core
+ 4.1.3-c001 49.282696, Mon Apr 02 2007 21:16:10 ">
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ xmlns:xmpNote="http://ns.adobe.com/xmp/note/"
+ exif:DigitalZoomRatio="0/10"
+ xmpNote:HasExtendedXMP="28C74E0AC2D796886759006FBE2E57B7">
+<exif:Flash rdf:parseType='Resource'>
+<exif:Fired>True</exif:Fired> <exif:Return>0</exif:Return> <exif:Mode>1</exif:Mode> <exif:Function>False</exif:Function> <exif:RedEyeMode>False</exif:RedEyeMode></exif:Flash> </rdf:Description> </rdf:RDF> </x:xmpmeta>
+
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/xmp/xmpExt2.xmp b/tests/phpunit/data/xmp/xmpExt2.xmp
new file mode 100644
index 00000000..060abb2c
--- /dev/null
+++ b/tests/phpunit/data/xmp/xmpExt2.xmp
@@ -0,0 +1,8 @@
+<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+<rdf:Description
+ rdf:about=""
+ xmlns:exif="http://ns.adobe.com/exif/1.0/"
+ exif:FNumber="2/10">
+</rdf:Description>
+</rdf:RDF>
+<?xpacket end="w"?>
diff --git a/tests/phpunit/data/zip/cd-gap.zip b/tests/phpunit/data/zip/cd-gap.zip
new file mode 100644
index 00000000..b5ae6ccd
--- /dev/null
+++ b/tests/phpunit/data/zip/cd-gap.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/cd-truncated.zip b/tests/phpunit/data/zip/cd-truncated.zip
new file mode 100644
index 00000000..4d40d7d4
--- /dev/null
+++ b/tests/phpunit/data/zip/cd-truncated.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/class-trailing-null.zip b/tests/phpunit/data/zip/class-trailing-null.zip
new file mode 100644
index 00000000..31dcf3d8
--- /dev/null
+++ b/tests/phpunit/data/zip/class-trailing-null.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/class-trailing-slash.zip b/tests/phpunit/data/zip/class-trailing-slash.zip
new file mode 100644
index 00000000..9eb1f037
--- /dev/null
+++ b/tests/phpunit/data/zip/class-trailing-slash.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/class.zip b/tests/phpunit/data/zip/class.zip
new file mode 100644
index 00000000..98a625b7
--- /dev/null
+++ b/tests/phpunit/data/zip/class.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/empty.zip b/tests/phpunit/data/zip/empty.zip
new file mode 100644
index 00000000..15cb0ecb
--- /dev/null
+++ b/tests/phpunit/data/zip/empty.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/looks-like-zip64.zip b/tests/phpunit/data/zip/looks-like-zip64.zip
new file mode 100644
index 00000000..7428cddd
--- /dev/null
+++ b/tests/phpunit/data/zip/looks-like-zip64.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/nosig.zip b/tests/phpunit/data/zip/nosig.zip
new file mode 100644
index 00000000..a22c73a4
--- /dev/null
+++ b/tests/phpunit/data/zip/nosig.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/split.zip b/tests/phpunit/data/zip/split.zip
new file mode 100644
index 00000000..6984ae6d
--- /dev/null
+++ b/tests/phpunit/data/zip/split.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/trail.zip b/tests/phpunit/data/zip/trail.zip
new file mode 100644
index 00000000..50bcea12
--- /dev/null
+++ b/tests/phpunit/data/zip/trail.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/wrong-cd-start-disk.zip b/tests/phpunit/data/zip/wrong-cd-start-disk.zip
new file mode 100644
index 00000000..59b45938
--- /dev/null
+++ b/tests/phpunit/data/zip/wrong-cd-start-disk.zip
Binary files differ
diff --git a/tests/phpunit/data/zip/wrong-central-entry-sig.zip b/tests/phpunit/data/zip/wrong-central-entry-sig.zip
new file mode 100644
index 00000000..05329b43
--- /dev/null
+++ b/tests/phpunit/data/zip/wrong-central-entry-sig.zip
Binary files differ
diff --git a/tests/phpunit/docs/ExportDemoTest.php b/tests/phpunit/docs/ExportDemoTest.php
new file mode 100644
index 00000000..b09487a6
--- /dev/null
+++ b/tests/phpunit/docs/ExportDemoTest.php
@@ -0,0 +1,39 @@
+<?php
+/**
+ * Test for the demo xml
+ *
+ * @group Dump
+ */
+class ExportDemoTest extends DumpTestCase {
+
+ /**
+ * @group large
+ */
+ function testExportDemo() {
+ $this->validateXmlFileAgainstXsd( "../../docs/export-demo.xml" );
+ }
+
+ /**
+ * Validates a xml file against the xsd.
+ *
+ * The validation is slow, because php has to read the xsd on each call.
+ *
+ * @param $fname string: name of file to validate
+ */
+ protected function validateXmlFileAgainstXsd( $fname ) {
+ $version = WikiExporter::schemaVersion();
+
+ $dom = new DomDocument();
+ $dom->load( $fname );
+
+ // Ensure, the demo is for the current version
+ $this->assertEquals( $dom->documentElement->getAttribute( 'version' ), $version, 'export-demo.xml should have the current version' );
+
+ try {
+ $this->assertTrue( $dom->schemaValidate( "../../docs/export-" . $version . ".xsd" ),
+ "schemaValidate has found an error" );
+ } catch ( Exception $e ) {
+ $this->fail( "xml not valid against xsd: " . $e->getMessage() );
+ }
+ }
+}
diff --git a/tests/phpunit/includes/ArticleTablesTest.php b/tests/phpunit/includes/ArticleTablesTest.php
new file mode 100644
index 00000000..469d1d19
--- /dev/null
+++ b/tests/phpunit/includes/ArticleTablesTest.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @group Database
+ */
+class ArticleTablesTest extends MediaWikiLangTestCase {
+
+ public function testbug14404() {
+ 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' );
+ $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', 0, false, $user );
+ $templates1 = $title->getTemplateLinksFrom();
+
+ $wgLang = Language::factory( 'de' );
+ $page = WikiPage::factory( $title ); // In order to force the rerendering of the same wikitext
+
+ // We need an edit, a purge is not enough to regenerate the tables
+ $page->doEditContent( new WikitextContent( '{{:{{int:history}}}}' ), 'Test code for bug 14404', EDIT_UPDATE, false, $user );
+ $templates2 = $title->getTemplateLinksFrom();
+
+ $this->assertEquals( $templates1, $templates2 );
+ $this->assertEquals( $templates1[0]->getFullText(), 'Historial' );
+ }
+}
diff --git a/tests/phpunit/includes/ArticleTest.php b/tests/phpunit/includes/ArticleTest.php
new file mode 100644
index 00000000..b4d6dca6
--- /dev/null
+++ b/tests/phpunit/includes/ArticleTest.php
@@ -0,0 +1,92 @@
+<?php
+
+class ArticleTest extends MediaWikiTestCase {
+
+ /**
+ * @var Title
+ */
+ private $title;
+ /**
+ * @var Article
+ */
+ private $article;
+
+ /** creates a title object and its article object */
+ protected function setUp() {
+ parent::setUp();
+ $this->title = Title::makeTitle( NS_MAIN, 'SomePage' );
+ $this->article = new Article( $this->title );
+ }
+
+ /** cleanup title object and its article object */
+ protected function tearDown() {
+ parent::tearDown();
+ $this->title = null;
+ $this->article = null;
+ }
+
+ public function testImplementsGetMagic() {
+ $this->assertEquals( false, $this->article->mLatest, "Article __get magic" );
+ }
+
+ /**
+ * @depends testImplementsGetMagic
+ */
+ public function testImplementsSetMagic() {
+ $this->article->mLatest = 2;
+ $this->assertEquals( 2, $this->article->mLatest, "Article __set magic" );
+ }
+
+ /**
+ * @depends testImplementsSetMagic
+ */
+ public function testImplementsCallMagic() {
+ $this->article->mLatest = 33;
+ $this->article->mDataLoaded = true;
+ $this->assertEquals( 33, $this->article->getLatest(), "Article __call magic" );
+ }
+
+ public function testGetOrSetOnNewProperty() {
+ $this->article->ext_someNewProperty = 12;
+ $this->assertEquals( 12, $this->article->ext_someNewProperty,
+ "Article get/set magic on new field" );
+
+ $this->article->ext_someNewProperty = -8;
+ $this->assertEquals( -8, $this->article->ext_someNewProperty,
+ "Article get/set magic on update to new field" );
+ }
+
+ /**
+ * Checks for the existence of the backwards compatibility static functions (forwarders to WikiPage class)
+ */
+ public function testStaticFunctions() {
+ $this->hideDeprecated( 'Article::getAutosummary' );
+ $this->hideDeprecated( 'WikiPage::getAutosummary' );
+ $this->hideDeprecated( 'CategoryPage::getAutosummary' ); // Inherited from Article
+
+ $this->assertEquals( WikiPage::selectFields(), Article::selectFields(),
+ "Article static functions" );
+ $this->assertEquals( true, is_callable( "Article::onArticleCreate" ),
+ "Article static functions" );
+ $this->assertEquals( true, is_callable( "Article::onArticleDelete" ),
+ "Article static functions" );
+ $this->assertEquals( true, is_callable( "ImagePage::onArticleEdit" ),
+ "Article static functions" );
+ $this->assertTrue( is_string( CategoryPage::getAutosummary( '', '', 0 ) ),
+ "Article static functions" );
+ }
+
+ public function testWikiPageFactory() {
+ $title = Title::makeTitle( NS_FILE, 'Someimage.png' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( 'WikiFilePage', get_class( $page ) );
+
+ $title = Title::makeTitle( NS_CATEGORY, 'SomeCategory' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( 'WikiCategoryPage', get_class( $page ) );
+
+ $title = Title::makeTitle( NS_MAIN, 'SomePage' );
+ $page = WikiPage::factory( $title );
+ $this->assertEquals( 'WikiPage', get_class( $page ) );
+ }
+}
diff --git a/tests/phpunit/includes/BlockTest.php b/tests/phpunit/includes/BlockTest.php
new file mode 100644
index 00000000..21de0985
--- /dev/null
+++ b/tests/phpunit/includes/BlockTest.php
@@ -0,0 +1,354 @@
+<?php
+
+/**
+ * @group Database
+ * @group Blocking
+ */
+class BlockTest extends MediaWikiLangTestCase {
+
+ private $block, $madeAt;
+
+ /* variable used to save up the blockID we insert in this test suite */
+ private $blockId;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => Language::factory( 'en' )
+ ) );
+ }
+
+ function addDBData() {
+
+ $user = User::newFromName( 'UTBlockee' );
+ if ( $user->getID() == 0 ) {
+ $user->addToDatabase();
+ $user->setPassword( 'UTBlockeePassword' );
+
+ $user->saveSettings();
+ }
+
+ // Delete the last round's block if it's still there
+ $oldBlock = Block::newFromTarget( 'UTBlockee' );
+ if ( $oldBlock ) {
+ // An old block will prevent our new one from saving.
+ $oldBlock->delete();
+ }
+
+ $this->block = new Block( 'UTBlockee', $user->getID(), 0,
+ 'Parce que', 0, false, time() + 100500
+ );
+ $this->madeAt = wfTimestamp( TS_MW );
+
+ $this->block->insert();
+ // save up ID for use in assertion. Since ID is an autoincrement,
+ // its value might change depending on the order the tests are run.
+ // ApiBlockTest insert its own blocks!
+ $newBlockId = $this->block->getId();
+ if ( $newBlockId ) {
+ $this->blockId = $newBlockId;
+ } else {
+ throw new MWException( "Failed to insert block for BlockTest; old leftover block remaining?" );
+ }
+
+ $this->addXffBlocks();
+ }
+
+ /**
+ * debug function : dump the ipblocks table
+ */
+ function dumpBlocks() {
+ $v = $this->db->select( 'ipblocks', '*' );
+ print "Got " . $v->numRows() . " rows. Full dump follow:\n";
+ foreach ( $v as $row ) {
+ print_r( $row );
+ }
+ }
+
+ public function testInitializerFunctionsReturnCorrectBlock() {
+ // $this->dumpBlocks();
+
+ $this->assertTrue( $this->block->equals( Block::newFromTarget( 'UTBlockee' ) ), "newFromTarget() returns the same block as the one that was made" );
+
+ $this->assertTrue( $this->block->equals( Block::newFromID( $this->blockId ) ), "newFromID() returns the same block as the one that was made" );
+ }
+
+ /**
+ * per bug 26425
+ */
+ public function testBug26425BlockTimestampDefaultsToTime() {
+ // delta to stop one-off errors when things happen to go over a second mark.
+ $delta = abs( $this->madeAt - $this->block->mTimestamp );
+ $this->assertLessThan( 2, $delta, "If no timestamp is specified, the block is recorded as time()" );
+ }
+
+ /**
+ * This is the method previously used to load block info in CheckUser etc
+ * passing an empty value (empty string, null, etc) as the ip parameter bypasses IP lookup checks.
+ *
+ * This stopped working with r84475 and friends: regression being fixed for bug 29116.
+ *
+ * @dataProvider provideBug29116Data
+ */
+ public function testBug29116LoadWithEmptyIp( $vagueTarget ) {
+ $this->hideDeprecated( 'Block::load' );
+
+ $uid = User::idFromName( 'UTBlockee' );
+ $this->assertTrue( ( $uid > 0 ), 'Must be able to look up the target user during tests' );
+
+ $block = new Block();
+ $ok = $block->load( $vagueTarget, $uid );
+ $this->assertTrue( $ok, "Block->load() with empty IP and user ID '$uid' should return a block" );
+
+ $this->assertTrue( $this->block->equals( $block ), "Block->load() returns the same block as the one that was made when given empty ip param " . var_export( $vagueTarget, true ) );
+ }
+
+ /**
+ * CheckUser since being changed to use Block::newFromTarget started failing
+ * because the new function didn't accept empty strings like Block::load()
+ * had. Regression bug 29116.
+ *
+ * @dataProvider provideBug29116Data
+ */
+ public function testBug29116NewFromTargetWithEmptyIp( $vagueTarget ) {
+ $block = Block::newFromTarget( 'UTBlockee', $vagueTarget );
+ $this->assertTrue( $this->block->equals( $block ), "newFromTarget() returns the same block as the one that was made when given empty vagueTarget param " . var_export( $vagueTarget, true ) );
+ }
+
+ public static function provideBug29116Data() {
+ return array(
+ array( null ),
+ array( '' ),
+ array( false )
+ );
+ }
+
+ public function testBlockedUserCanNotCreateAccount() {
+ $username = 'BlockedUserToCreateAccountWith';
+ $u = User::newFromName( $username );
+ $u->setPassword( 'NotRandomPass' );
+ $u->addToDatabase();
+ unset( $u );
+
+ // Sanity check
+ $this->assertNull(
+ Block::newFromTarget( $username ),
+ "$username should not be blocked"
+ );
+
+ // Reload user
+ $u = User::newFromName( $username );
+ $this->assertFalse(
+ $u->isBlockedFromCreateAccount(),
+ "Our sandbox user should be able to create account before being blocked"
+ );
+
+ // Foreign perspective (blockee not on current wiki)...
+ $block = new Block(
+ /* $address */ $username,
+ /* $user */ 14146,
+ /* $by */ 0,
+ /* $reason */ 'crosswiki block...',
+ /* $timestamp */ wfTimestampNow(),
+ /* $auto */ false,
+ /* $expiry */ $this->db->getInfinity(),
+ /* anonOnly */ false,
+ /* $createAccount */ true,
+ /* $enableAutoblock */ true,
+ /* $hideName (ipb_deleted) */ true,
+ /* $blockEmail */ true,
+ /* $allowUsertalk */ false,
+ /* $byName */ 'MetaWikiUser'
+ );
+ $block->insert();
+
+ // Reload block from DB
+ $userBlock = Block::newFromTarget( $username );
+ $this->assertTrue(
+ (bool)$block->prevents( 'createaccount' ),
+ "Block object in DB should prevents 'createaccount'"
+ );
+
+ $this->assertInstanceOf(
+ 'Block',
+ $userBlock,
+ "'$username' block block object should be existent"
+ );
+
+ // Reload user
+ $u = User::newFromName( $username );
+ $this->assertTrue(
+ (bool)$u->isBlockedFromCreateAccount(),
+ "Our sandbox user '$username' should NOT be able to create account"
+ );
+ }
+
+ public function testCrappyCrossWikiBlocks() {
+ // Delete the last round's block if it's still there
+ $oldBlock = Block::newFromTarget( 'UserOnForeignWiki' );
+ if ( $oldBlock ) {
+ // An old block will prevent our new one from saving.
+ $oldBlock->delete();
+ }
+
+ // Foreign perspective (blockee not on current wiki)...
+ $block = new Block(
+ /* $address */ 'UserOnForeignWiki',
+ /* $user */ 14146,
+ /* $by */ 0,
+ /* $reason */ 'crosswiki block...',
+ /* $timestamp */ wfTimestampNow(),
+ /* $auto */ false,
+ /* $expiry */ $this->db->getInfinity(),
+ /* anonOnly */ false,
+ /* $createAccount */ true,
+ /* $enableAutoblock */ true,
+ /* $hideName (ipb_deleted) */ true,
+ /* $blockEmail */ true,
+ /* $allowUsertalk */ false,
+ /* $byName */ 'MetaWikiUser'
+ );
+
+ $res = $block->insert( $this->db );
+ $this->assertTrue( (bool)$res['id'], 'Block succeeded' );
+
+ // Local perspective (blockee on current wiki)...
+ $user = User::newFromName( 'UserOnForeignWiki' );
+ $user->addToDatabase();
+ // Set user ID to match the test value
+ $this->db->update( 'user', array( 'user_id' => 14146 ), array( 'user_id' => $user->getId() ) );
+ $user = null; // clear
+
+ $block = Block::newFromID( $res['id'] );
+ $this->assertEquals( 'UserOnForeignWiki', $block->getTarget()->getName(), 'Correct blockee name' );
+ $this->assertEquals( '14146', $block->getTarget()->getId(), 'Correct blockee id' );
+ $this->assertEquals( 'MetaWikiUser', $block->getBlocker(), 'Correct blocker name' );
+ $this->assertEquals( 'MetaWikiUser', $block->getByName(), 'Correct blocker name' );
+ $this->assertEquals( 0, $block->getBy(), 'Correct blocker id' );
+ }
+
+ protected function addXffBlocks() {
+ static $inited = false;
+
+ if ( $inited ) {
+ return;
+ }
+
+ $inited = true;
+
+ $blockList = array(
+ array( 'target' => '70.2.0.0/16',
+ 'type' => Block::TYPE_RANGE,
+ 'desc' => 'Range Hardblock',
+ 'ACDisable' => false,
+ 'isHardblock' => true,
+ 'isAutoBlocking' => false,
+ ),
+ array( 'target' => '2001:4860:4001::/48',
+ 'type' => Block::TYPE_RANGE,
+ 'desc' => 'Range6 Hardblock',
+ 'ACDisable' => false,
+ 'isHardblock' => true,
+ 'isAutoBlocking' => false,
+ ),
+ array( 'target' => '60.2.0.0/16',
+ 'type' => Block::TYPE_RANGE,
+ 'desc' => 'Range Softblock with AC Disabled',
+ 'ACDisable' => true,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ),
+ array( 'target' => '50.2.0.0/16',
+ 'type' => Block::TYPE_RANGE,
+ 'desc' => 'Range Softblock',
+ 'ACDisable' => false,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ),
+ array( 'target' => '50.1.1.1',
+ 'type' => Block::TYPE_IP,
+ 'desc' => 'Exact Softblock',
+ 'ACDisable' => false,
+ 'isHardblock' => false,
+ 'isAutoBlocking' => false,
+ ),
+ );
+
+ foreach ( $blockList as $insBlock ) {
+ $target = $insBlock['target'];
+
+ if ( $insBlock['type'] === Block::TYPE_IP ) {
+ $target = User::newFromName( IP::sanitizeIP( $target ), false )->getName();
+ } elseif ( $insBlock['type'] === Block::TYPE_RANGE ) {
+ $target = IP::sanitizeRange( $target );
+ }
+
+ $block = new Block();
+ $block->setTarget( $target );
+ $block->setBlocker( 'testblocker@global' );
+ $block->mReason = $insBlock['desc'];
+ $block->mExpiry = 'infinity';
+ $block->prevents( 'createaccount', $insBlock['ACDisable'] );
+ $block->isHardblock( $insBlock['isHardblock'] );
+ $block->isAutoblocking( $insBlock['isAutoBlocking'] );
+ $block->insert();
+ }
+ }
+
+ public static function providerXff() {
+ return array(
+ array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ),
+ array( 'xff' => '1.2.3.4, 50.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Softblock with AC Disabled'
+ ),
+ array( 'xff' => '1.2.3.4, 70.2.1.1, 50.1.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Exact Softblock'
+ ),
+ array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 50.1.1.1, 2.3.4.5',
+ 'count' => 3,
+ 'result' => 'Exact Softblock'
+ ),
+ array( 'xff' => '1.2.3.4, 70.2.1.1, 50.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ),
+ array( 'xff' => '1.2.3.4, 70.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Hardblock'
+ ),
+ array( 'xff' => '50.2.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range Softblock with AC Disabled'
+ ),
+ array( 'xff' => '1.2.3.4, 50.1.1.1, 60.2.1.1, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Exact Softblock'
+ ),
+ array( 'xff' => '1.2.3.4, <$A_BUNCH-OF{INVALID}TEXT\>, 60.2.1.1, 2.3.4.5',
+ 'count' => 1,
+ 'result' => 'Range Softblock with AC Disabled'
+ ),
+ array( 'xff' => '1.2.3.4, 50.2.1.1, 2001:4860:4001:802::1003, 2.3.4.5',
+ 'count' => 2,
+ 'result' => 'Range6 Hardblock'
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider providerXff
+ */
+ public function testBlocksOnXff( $xff, $exCount, $exResult ) {
+ $list = array_map( 'trim', explode( ',', $xff ) );
+ $xffblocks = Block::getBlocksForIPList( $list, true );
+ $this->assertEquals( $exCount, count( $xffblocks ), 'Number of blocks for ' . $xff );
+ $block = Block::chooseBlock( $xffblocks, $list );
+ $this->assertEquals( $exResult, $block->mReason, 'Correct block type for XFF header ' . $xff );
+ }
+}
diff --git a/tests/phpunit/includes/CdbTest.php b/tests/phpunit/includes/CdbTest.php
new file mode 100644
index 00000000..e3d9da7c
--- /dev/null
+++ b/tests/phpunit/includes/CdbTest.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Test the CDB reader/writer
+ */
+class CdbTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+ if ( !CdbReader::haveExtension() ) {
+ $this->markTestSkipped( 'Native CDB support is not available' );
+ }
+ }
+
+ /**
+ * @group medium
+ */
+ public function testCdb() {
+ $dir = wfTempDir();
+ if ( !is_writable( $dir ) ) {
+ $this->markTestSkipped( "Temp dir isn't writable" );
+ }
+
+ $phpcdbfile = $this->getNewTempFile();
+ $dbacdbfile = $this->getNewTempFile();
+
+ $w1 = new CdbWriter_PHP( $phpcdbfile );
+ $w2 = new CdbWriter_DBA( $dbacdbfile );
+
+ $data = array();
+ for ( $i = 0; $i < 1000; $i++ ) {
+ $key = $this->randomString();
+ $value = $this->randomString();
+ $w1->set( $key, $value );
+ $w2->set( $key, $value );
+
+ if ( !isset( $data[$key] ) ) {
+ $data[$key] = $value;
+ }
+ }
+
+ $w1->close();
+ $w2->close();
+
+ $this->assertEquals(
+ md5_file( $phpcdbfile ),
+ md5_file( $dbacdbfile ),
+ 'same hash'
+ );
+
+ $r1 = new CdbReader_PHP( $phpcdbfile );
+ $r2 = new CdbReader_DBA( $dbacdbfile );
+
+ foreach ( $data as $key => $value ) {
+ if ( $key === '' ) {
+ // Known bug
+ continue;
+ }
+ $v1 = $r1->get( $key );
+ $v2 = $r2->get( $key );
+
+ $v1 = $v1 === false ? '(not found)' : $v1;
+ $v2 = $v2 === false ? '(not found)' : $v2;
+
+ # cdbAssert( 'Mismatch', $key, $v1, $v2 );
+ $this->cdbAssert( "PHP error", $key, $v1, $value );
+ $this->cdbAssert( "DBA error", $key, $v2, $value );
+ }
+ }
+
+ private function randomString() {
+ $len = mt_rand( 0, 10 );
+ $s = '';
+ for ( $j = 0; $j < $len; $j++ ) {
+ $s .= chr( mt_rand( 0, 255 ) );
+ }
+
+ return $s;
+ }
+
+ private function cdbAssert( $msg, $key, $v1, $v2 ) {
+ $this->assertEquals(
+ $v2,
+ $v1,
+ $msg . ', k=' . bin2hex( $key )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/CollationTest.php b/tests/phpunit/includes/CollationTest.php
new file mode 100644
index 00000000..43bb3941
--- /dev/null
+++ b/tests/phpunit/includes/CollationTest.php
@@ -0,0 +1,111 @@
+<?php
+class CollationTest extends MediaWikiLangTestCase {
+ protected function setUp() {
+ parent::setUp();
+ if ( !extension_loaded( 'intl' ) ) {
+ $this->markTestSkipped( 'These tests require intl extension' );
+ }
+ }
+
+ /**
+ * Test to make sure, that if you
+ * have "X" and "XY", the binary
+ * sortkey also has "X" being a
+ * prefix of "XY". Our collation
+ * code makes this assumption.
+ *
+ * @param $lang String Language code for collator
+ * @param $base String Base string
+ * @param $extended String String containing base as a prefix.
+ *
+ * @dataProvider prefixDataProvider
+ */
+ public function testIsPrefix( $lang, $base, $extended ) {
+ $cp = Collator::create( $lang );
+ $cp->setStrength( Collator::PRIMARY );
+ $baseBin = $cp->getSortKey( $base );
+ // Remove sortkey terminator
+ $baseBin = rtrim( $baseBin, "\0" );
+ $extendedBin = $cp->getSortKey( $extended );
+ $this->assertStringStartsWith( $baseBin, $extendedBin, "$base is not a prefix of $extended" );
+ }
+
+ function prefixDataProvider() {
+ return array(
+ array( 'en', 'A', 'AA' ),
+ array( 'en', 'A', 'AAA' ),
+ array( 'en', 'Д', 'ДЂ' ),
+ array( 'en', 'Д', 'ДA' ),
+ // 'Ʒ' should expand to 'Z ' (note space).
+ array( 'fi', 'Z', 'Ʒ' ),
+ // 'Þ' should expand to 'th'
+ array( 'sv', 't', 'Þ' ),
+ // Javanese is a limited use alphabet, so should have 3 bytes
+ // per character, so do some tests with it.
+ array( 'en', 'ꦲ', 'ꦲꦤ' ),
+ array( 'en', 'ꦲ', 'ꦲД' ),
+ array( 'en', 'A', 'Aꦲ' ),
+ );
+ }
+
+ /**
+ * Opposite of testIsPrefix
+ *
+ * @dataProvider notPrefixDataProvider
+ */
+ public function testNotIsPrefix( $lang, $base, $extended ) {
+ $cp = Collator::create( $lang );
+ $cp->setStrength( Collator::PRIMARY );
+ $baseBin = $cp->getSortKey( $base );
+ // Remove sortkey terminator
+ $baseBin = rtrim( $baseBin, "\0" );
+ $extendedBin = $cp->getSortKey( $extended );
+ $this->assertStringStartsNotWith( $baseBin, $extendedBin, "$base is a prefix of $extended" );
+ }
+
+ function notPrefixDataProvider() {
+ return array(
+ array( 'en', 'A', 'B' ),
+ array( 'en', 'AC', 'ABC' ),
+ array( 'en', 'Z', 'Ʒ' ),
+ array( 'en', 'A', 'ꦲ' ),
+ );
+ }
+
+ /**
+ * Test correct first letter is fetched.
+ *
+ * @param $collation String Collation name (aka uca-en)
+ * @param $string String String to get first letter of
+ * @param $firstLetter String Expected first letter.
+ *
+ * @dataProvider firstLetterProvider
+ */
+ public function testGetFirstLetter( $collation, $string, $firstLetter ) {
+ $col = Collation::factory( $collation );
+ $this->assertEquals( $firstLetter, $col->getFirstLetter( $string ) );
+ }
+
+ function firstLetterProvider() {
+ return array(
+ array( 'uppercase', 'Abc', 'A' ),
+ array( 'uppercase', 'abc', 'A' ),
+ array( 'identity', 'abc', 'a' ),
+ array( 'uca-en', 'abc', 'A' ),
+ array( 'uca-en', ' ', ' ' ),
+ array( 'uca-en', 'Êveryone', 'E' ),
+ array( 'uca-vi', 'Êveryone', 'Ê' ),
+ // Make sure thorn is not a first letter.
+ array( 'uca-sv', 'The', 'T' ),
+ array( 'uca-sv', 'Å', 'Å' ),
+ array( 'uca-hu', 'dzsdo', 'Dzs' ),
+ array( 'uca-hu', 'dzdso', 'Dz' ),
+ array( 'uca-hu', 'CSD', 'Cs' ),
+ array( 'uca-root', 'CSD', 'C' ),
+ array( 'uca-fi', 'Ǥ', 'G' ),
+ array( 'uca-fi', 'Ŧ', 'T' ),
+ array( 'uca-fi', 'Ʒ', 'Z' ),
+ array( 'uca-fi', 'Ŋ', 'N' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/DiffHistoryBlobTest.php b/tests/phpunit/includes/DiffHistoryBlobTest.php
new file mode 100644
index 00000000..a4d5b91a
--- /dev/null
+++ b/tests/phpunit/includes/DiffHistoryBlobTest.php
@@ -0,0 +1,44 @@
+<?php
+
+class DiffHistoryBlobTest extends MediaWikiTestCase {
+ protected function setUp() {
+ if ( !extension_loaded( 'xdiff' ) ) {
+ $this->markTestSkipped( 'The xdiff extension is not available' );
+
+ return;
+ }
+ if ( !function_exists( 'xdiff_string_rabdiff' ) ) {
+ $this->markTestSkipped( 'The version of xdiff extension is lower than 1.5.0' );
+
+ return;
+ }
+ if ( !extension_loaded( 'hash' ) ) {
+ $this->markTestSkipped( 'The hash extension is not available' );
+
+ return;
+ }
+ parent::setUp();
+ }
+
+ /**
+ * Test for DiffHistoryBlob::xdiffAdler32()
+ * @dataProvider provideXdiffAdler32
+ */
+ public function testXdiffAdler32( $input ) {
+ $xdiffHash = substr( xdiff_string_rabdiff( $input, '' ), 0, 4 );
+ $dhb = new DiffHistoryBlob;
+ $myHash = $dhb->xdiffAdler32( $input );
+ $this->assertSame( bin2hex( $xdiffHash ), bin2hex( $myHash ),
+ "Hash of " . addcslashes( $input, "\0..\37!@\@\177..\377" ) );
+ }
+
+ public static function provideXdiffAdler32() {
+ return array(
+ array( '', 'Empty string' ),
+ array( "\0", 'Null' ),
+ array( "\0\0\0", "Several nulls" ),
+ array( "Hello", "An ASCII string" ),
+ array( str_repeat( "x", 6000 ), "A string larger than xdiff's NMAX (5552)" )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/EditPageTest.php b/tests/phpunit/includes/EditPageTest.php
new file mode 100644
index 00000000..87272a4c
--- /dev/null
+++ b/tests/phpunit/includes/EditPageTest.php
@@ -0,0 +1,491 @@
+<?php
+
+/**
+ * @group Editing
+ *
+ * @group Database
+ * ^--- tell jenkins this test needs the database
+ *
+ * @group medium
+ * ^--- tell phpunit that these test cases may take longer than 2 seconds.
+ */
+class EditPageTest extends MediaWikiLangTestCase {
+
+ /**
+ * @dataProvider provideExtractSectionTitle
+ */
+ public function testExtractSectionTitle( $section, $title ) {
+ $extracted = EditPage::extractSectionTitle( $section );
+ $this->assertEquals( $title, $extracted );
+ }
+
+ public static function provideExtractSectionTitle() {
+ 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"
+ ),
+ array(
+ "== Section== \t\r\n followed by whitespace (bug 35051)",
+ 'Section',
+ ),
+ );
+ }
+
+ protected function forceRevisionDate( WikiPage $page, $timestamp ) {
+ $dbw = wfGetDB( DB_MASTER );
+
+ $dbw->update( 'revision',
+ array( 'rev_timestamp' => $dbw->timestamp( $timestamp ) ),
+ array( 'rev_id' => $page->getLatest() ) );
+
+ $page->clear();
+ }
+
+ /**
+ * User input text is passed to rtrim() by edit page. This is a simple
+ * wrapper around assertEquals() which calls rrtrim() to normalize the
+ * expected and actual texts.
+ */
+ function assertEditedTextEquals( $expected, $actual, $msg = '' ) {
+ return $this->assertEquals( rtrim( $expected ), rtrim( $actual ), $msg );
+ }
+
+ /**
+ * Performs an edit and checks the result.
+ *
+ * @param String|Title $title The title of the page to edit
+ * @param String|null $baseText Some text to create the page with before attempting the edit.
+ * @param User|String|null $user The user to perform the edit as.
+ * @param array $edit An array of request parameters used to define the edit to perform.
+ * Some well known fields are:
+ * * wpTextbox1: the text to submit
+ * * wpSummary: the edit summary
+ * * wpEditToken: the edit token (will be inserted if not provided)
+ * * wpEdittime: timestamp of the edit's base revision (will be inserted if not provided)
+ * * wpStarttime: timestamp when the edit started (will be inserted if not provided)
+ * * wpSectionTitle: the section to edit
+ * * wpMinorEdit: mark as minor edit
+ * * wpWatchthis: whether to watch the page
+ * @param int|null $expectedCode The expected result code (EditPage::AS_XXX constants).
+ * Set to null to skip the check. Defaults to EditPage::AS_OK.
+ * @param String|null $expectedText The text expected to be on the page after the edit.
+ * Set to null to skip the check.
+ * @param String|null $message An optional message to show along with any error message.
+ *
+ * @return WikiPage The page that was just edited, useful for getting the edit's rev_id, etc.
+ */
+ protected function assertEdit( $title, $baseText, $user = null, array $edit,
+ $expectedCode = EditPage::AS_OK, $expectedText = null, $message = null
+ ) {
+ if ( is_string( $title ) ) {
+ $ns = $this->getDefaultWikitextNS();
+ $title = Title::newFromText( $title, $ns );
+ }
+
+ if ( is_string( $user ) ) {
+ $user = User::newFromName( $user );
+
+ if ( $user->getId() === 0 ) {
+ $user->addToDatabase();
+ }
+ }
+
+ $page = WikiPage::factory( $title );
+
+ if ( $baseText !== null ) {
+ $content = ContentHandler::makeContent( $baseText, $title );
+ $page->doEditContent( $content, "base text for test" );
+ $this->forceRevisionDate( $page, '20120101000000' );
+
+ //sanity check
+ $page->clear();
+ $currentText = ContentHandler::getContentText( $page->getContent() );
+
+ # EditPage rtrim() the user input, so we alter our expected text
+ # to reflect that.
+ $this->assertEditedTextEquals( $baseText, $currentText );
+ }
+
+ if ( $user == null ) {
+ $user = $GLOBALS['wgUser'];
+ } else {
+ $this->setMwGlobals( 'wgUser', $user );
+ }
+
+ if ( !isset( $edit['wpEditToken'] ) ) {
+ $edit['wpEditToken'] = $user->getEditToken();
+ }
+
+ if ( !isset( $edit['wpEdittime'] ) ) {
+ $edit['wpEdittime'] = $page->exists() ? $page->getTimestamp() : '';
+ }
+
+ if ( !isset( $edit['wpStarttime'] ) ) {
+ $edit['wpStarttime'] = wfTimestampNow();
+ }
+
+ $req = new FauxRequest( $edit, true ); // session ??
+
+ $ep = new EditPage( new Article( $title ) );
+ $ep->setContextTitle( $title );
+ $ep->importFormData( $req );
+
+ $bot = isset( $edit['bot'] ) ? (bool)$edit['bot'] : false;
+
+ // this is where the edit happens!
+ // Note: don't want to use EditPage::AttemptSave, because it messes with $wgOut
+ // and throws exceptions like PermissionsError
+ $status = $ep->internalAttemptSave( $result, $bot );
+
+ if ( $expectedCode !== null ) {
+ // check edit code
+ $this->assertEquals( $expectedCode, $status->value,
+ "Expected result code mismatch. $message" );
+ }
+
+ $page = WikiPage::factory( $title );
+
+ if ( $expectedText !== null ) {
+ // check resulting page text
+ $content = $page->getContent();
+ $text = ContentHandler::getContentText( $content );
+
+ # EditPage rtrim() the user input, so we alter our expected text
+ # to reflect that.
+ $this->assertEditedTextEquals( $expectedText, $text,
+ "Expected article text mismatch. $message" );
+ }
+
+ return $page;
+ }
+
+ public function testCreatePage() {
+ $this->assertEdit(
+ 'EditPageTest_testCreatePage',
+ null,
+ null,
+ array(
+ 'wpTextbox1' => "Hello World!",
+ ),
+ EditPage::AS_SUCCESS_NEW_ARTICLE,
+ "Hello World!",
+ "expected article being created"
+ )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
+
+ $this->assertEdit(
+ 'EditPageTest_testCreatePage',
+ null,
+ null,
+ array(
+ 'wpTextbox1' => "",
+ ),
+ EditPage::AS_BLANK_ARTICLE,
+ null,
+ "expected article not being created if empty"
+ );
+
+
+ $this->assertEdit(
+ 'MediaWiki:January',
+ null,
+ 'UTSysop',
+ array(
+ 'wpTextbox1' => "Not January",
+ ),
+ EditPage::AS_SUCCESS_NEW_ARTICLE,
+ "Not January",
+ "expected MediaWiki: page being created"
+ )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
+
+ $this->assertEdit(
+ 'MediaWiki:EditPageTest_testCreatePage',
+ null,
+ 'UTSysop',
+ array(
+ 'wpTextbox1' => "",
+ ),
+ EditPage::AS_BLANK_ARTICLE,
+ null,
+ "expected not-registered MediaWiki: page not being created if empty"
+ );
+
+ $this->assertEdit(
+ 'MediaWiki:January',
+ null,
+ 'UTSysop',
+ array(
+ 'wpTextbox1' => "",
+ ),
+ EditPage::AS_SUCCESS_NEW_ARTICLE,
+ "",
+ "expected registered MediaWiki: page being created even if empty"
+ )->doDeleteArticleReal( 'EditPageTest_testCreatePage' );
+
+ $this->assertEdit(
+ 'MediaWiki:Ipb-default-expiry',
+ null,
+ 'UTSysop',
+ array(
+ 'wpTextbox1' => "",
+ ),
+ EditPage::AS_BLANK_ARTICLE,
+ "",
+ "expected registered MediaWiki: page whose default content is empty not being created if empty"
+ );
+
+ $this->assertEdit(
+ 'MediaWiki:January',
+ null,
+ 'UTSysop',
+ array(
+ 'wpTextbox1' => "January",
+ ),
+ EditPage::AS_BLANK_ARTICLE,
+ null,
+ "expected MediaWiki: page not being created if text equals default message"
+ );
+ }
+
+ public function testUpdatePage() {
+ $text = "one";
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => 'first update',
+ );
+
+ $page = $this->assertEdit( 'EditPageTest_testUpdatePage', "zero", null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $text,
+ "expected successfull update with given text" );
+
+ $this->forceRevisionDate( $page, '20120101000000' );
+
+ $text = "two";
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => 'second update',
+ );
+
+ $this->assertEdit( 'EditPageTest_testUpdatePage', null, null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $text,
+ "expected successfull update with given text" );
+ }
+
+ public static function provideSectionEdit() {
+ $text = 'Intro
+
+== one ==
+first section.
+
+== two ==
+second section.
+';
+
+ $sectionOne = '== one ==
+hello
+';
+
+ $newSection = '== new section ==
+
+hello
+';
+
+ $textWithNewSectionOne = preg_replace(
+ '/== one ==.*== two ==/ms',
+ "$sectionOne\n== two ==", $text
+ );
+
+ $textWithNewSectionAdded = "$text\n$newSection";
+
+ return array(
+ array( #0
+ $text,
+ '',
+ 'hello',
+ 'replace all',
+ 'hello'
+ ),
+
+ array( #1
+ $text,
+ '1',
+ $sectionOne,
+ 'replace first section',
+ $textWithNewSectionOne,
+ ),
+
+ array( #2
+ $text,
+ 'new',
+ 'hello',
+ 'new section',
+ $textWithNewSectionAdded,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideSectionEdit
+ */
+ public function testSectionEdit( $base, $section, $text, $summary, $expected ) {
+ $edit = array(
+ 'wpTextbox1' => $text,
+ 'wpSummary' => $summary,
+ 'wpSection' => $section,
+ );
+
+ $this->assertEdit( 'EditPageTest_testSectionEdit', $base, null, $edit,
+ EditPage::AS_SUCCESS_UPDATE, $expected,
+ "expected successfull update of section" );
+ }
+
+ public static function provideAutoMerge() {
+ $tests = array();
+
+ $tests[] = array( #0: plain conflict
+ "Elmo", # base edit user
+ "one\n\ntwo\n\nthree\n",
+ array( #adam's edit
+ 'wpStarttime' => 1,
+ 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
+ ),
+ array( #berta's edit
+ 'wpStarttime' => 2,
+ 'wpTextbox1' => "(one)\n\ntwo\n\nthree\n",
+ ),
+ EditPage::AS_CONFLICT_DETECTED, # expected code
+ "ONE\n\ntwo\n\nthree\n", # expected text
+ 'expected edit conflict', # message
+ );
+
+ $tests[] = array( #1: successful merge
+ "Elmo", # base edit user
+ "one\n\ntwo\n\nthree\n",
+ array( #adam's edit
+ 'wpStarttime' => 1,
+ 'wpTextbox1' => "ONE\n\ntwo\n\nthree\n",
+ ),
+ array( #berta's edit
+ 'wpStarttime' => 2,
+ 'wpTextbox1' => "one\n\ntwo\n\nTHREE\n",
+ ),
+ EditPage::AS_SUCCESS_UPDATE, # expected code
+ "ONE\n\ntwo\n\nTHREE\n", # expected text
+ 'expected automatic merge', # message
+ );
+
+ $text = "Intro\n\n";
+ $text .= "== first section ==\n\n";
+ $text .= "one\n\ntwo\n\nthree\n\n";
+ $text .= "== second section ==\n\n";
+ $text .= "four\n\nfive\n\nsix\n\n";
+
+ // extract the first section.
+ $section = preg_replace( '/.*(== first section ==.*)== second section ==.*/sm', '$1', $text );
+
+ // generate expected text after merge
+ $expected = str_replace( 'one', 'ONE', str_replace( 'three', 'THREE', $text ) );
+
+ $tests[] = array( #2: merge in section
+ "Elmo", # base edit user
+ $text,
+ array( #adam's edit
+ 'wpStarttime' => 1,
+ 'wpTextbox1' => str_replace( 'one', 'ONE', $section ),
+ 'wpSection' => '1'
+ ),
+ array( #berta's edit
+ 'wpStarttime' => 2,
+ 'wpTextbox1' => str_replace( 'three', 'THREE', $section ),
+ 'wpSection' => '1'
+ ),
+ EditPage::AS_SUCCESS_UPDATE, # expected code
+ $expected, # expected text
+ 'expected automatic section merge', # message
+ );
+
+ // see whether it makes a difference who did the base edit
+ $testsWithAdam = array_map( function ( $test ) {
+ $test[0] = 'Adam'; // change base edit user
+ return $test;
+ }, $tests );
+
+ $testsWithBerta = array_map( function ( $test ) {
+ $test[0] = 'Berta'; // change base edit user
+ return $test;
+ }, $tests );
+
+ return array_merge( $tests, $testsWithAdam, $testsWithBerta );
+ }
+
+ /**
+ * @dataProvider provideAutoMerge
+ */
+ public function testAutoMerge( $baseUser, $text, $adamsEdit, $bertasEdit,
+ $expectedCode, $expectedText, $message = null
+ ) {
+ $this->checkHasDiff3();
+
+ //create page
+ $ns = $this->getDefaultWikitextNS();
+ $title = Title::newFromText( 'EditPageTest_testAutoMerge', $ns );
+ $page = WikiPage::factory( $title );
+
+ if ( $page->exists() ) {
+ $page->doDeleteArticle( "clean slate for testing" );
+ }
+
+ $baseEdit = array(
+ 'wpTextbox1' => $text,
+ );
+
+ $page = $this->assertEdit( 'EditPageTest_testAutoMerge', null,
+ $baseUser, $baseEdit, null, null, __METHOD__ );
+
+ $this->forceRevisionDate( $page, '20120101000000' );
+
+ $edittime = $page->getTimestamp();
+
+ // start timestamps for conflict detection
+ if ( !isset( $adamsEdit['wpStarttime'] ) ) {
+ $adamsEdit['wpStarttime'] = 1;
+ }
+
+ if ( !isset( $bertasEdit['wpStarttime'] ) ) {
+ $bertasEdit['wpStarttime'] = 2;
+ }
+
+ $starttime = wfTimestampNow();
+ $adamsTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$adamsEdit['wpStarttime'] );
+ $bertasTime = wfTimestamp( TS_MW, (int)wfTimestamp( TS_UNIX, $starttime ) + (int)$bertasEdit['wpStarttime'] );
+
+ $adamsEdit['wpStarttime'] = $adamsTime;
+ $bertasEdit['wpStarttime'] = $bertasTime;
+
+ $adamsEdit['wpSummary'] = 'Adam\'s edit';
+ $bertasEdit['wpSummary'] = 'Bertas\'s edit';
+
+ $adamsEdit['wpEdittime'] = $edittime;
+ $bertasEdit['wpEdittime'] = $edittime;
+
+ // first edit
+ $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Adam', $adamsEdit,
+ EditPage::AS_SUCCESS_UPDATE, null, "expected successfull update" );
+
+ // second edit
+ $this->assertEdit( 'EditPageTest_testAutoMerge', null, 'Berta', $bertasEdit,
+ $expectedCode, $expectedText, $message );
+ }
+}
diff --git a/tests/phpunit/includes/ExternalStoreTest.php b/tests/phpunit/includes/ExternalStoreTest.php
new file mode 100644
index 00000000..fcffcbc2
--- /dev/null
+++ b/tests/phpunit/includes/ExternalStoreTest.php
@@ -0,0 +1,81 @@
+<?php
+/**
+ * External Store tests
+ */
+
+class ExternalStoreTest extends MediaWikiTestCase {
+
+ public function testExternalFetchFromURL() {
+ $this->setMwGlobals( 'wgExternalStores', false );
+
+ $this->assertFalse(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+ 'Deny if wgExternalStores is not set to a non-empty array'
+ );
+
+ $this->setMwGlobals( 'wgExternalStores', array( 'FOO' ) );
+
+ $this->assertEquals(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/200' ),
+ 'Hello',
+ 'Allow FOO://cluster1/200'
+ );
+ $this->assertEquals(
+ ExternalStore::fetchFromURL( 'FOO://cluster1/300/0' ),
+ 'Hello',
+ 'Allow FOO://cluster1/300/0'
+ );
+ # Assertions for r68900
+ $this->assertFalse(
+ ExternalStore::fetchFromURL( 'ftp.example.org' ),
+ 'Deny domain ftp.example.org'
+ );
+ $this->assertFalse(
+ ExternalStore::fetchFromURL( '/example.txt' ),
+ 'Deny path /example.txt'
+ );
+ $this->assertFalse(
+ ExternalStore::fetchFromURL( 'http://' ),
+ 'Deny protocol http://'
+ );
+ }
+}
+
+class ExternalStoreFOO {
+
+ protected $data = array(
+ 'cluster1' => array(
+ '200' => 'Hello',
+ '300' => array(
+ 'Hello', 'World',
+ ),
+ ),
+ );
+
+ /**
+ * Fetch data from given URL
+ * @param $url String: an url of the form FOO://cluster/id or FOO://cluster/id/itemid.
+ * @return mixed
+ */
+ function fetchFromURL( $url ) {
+ // Based on ExternalStoreDB
+ $path = explode( '/', $url );
+ $cluster = $path[2];
+ $id = $path[3];
+ if ( isset( $path[4] ) ) {
+ $itemID = $path[4];
+ } else {
+ $itemID = false;
+ }
+
+ if ( !isset( $this->data[$cluster][$id] ) ) {
+ return null;
+ }
+
+ if ( $itemID !== false && is_array( $this->data[$cluster][$id] ) && isset( $this->data[$cluster][$id][$itemID] ) ) {
+ return $this->data[$cluster][$id][$itemID];
+ }
+
+ return $this->data[$cluster][$id];
+ }
+}
diff --git a/tests/phpunit/includes/ExtraParserTest.php b/tests/phpunit/includes/ExtraParserTest.php
new file mode 100644
index 00000000..6c67beb1
--- /dev/null
+++ b/tests/phpunit/includes/ExtraParserTest.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * Parser-related tests that don't suit for parserTests.txt
+ */
+class ExtraParserTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $contLang = Language::factory( 'en' );
+ $this->setMwGlobals( array(
+ 'wgShowDBErrorBacktrace' => true,
+ 'wgLanguageCode' => 'en',
+ 'wgContLang' => $contLang,
+ 'wgLang' => Language::factory( 'en' ),
+ 'wgMemc' => new EmptyBagOStuff,
+ 'wgAlwaysUseTidy' => false,
+ 'wgCleanSignatures' => true,
+ ) );
+
+ $this->options = ParserOptions::newFromUserAndLang( new User, $contLang );
+ $this->options->setTemplateCallback( array( __CLASS__, 'statelessFetchTemplate' ) );
+ $this->parser = new Parser;
+
+ MagicWord::clearCache();
+ }
+
+ // Bug 8689 - Long numeric lines kill the parser
+ public function testBug8689() {
+ global $wgUser;
+ $longLine = '1.' . str_repeat( '1234567890', 100000 ) . "\n";
+
+ $t = Title::newFromText( 'Unit test' );
+ $options = ParserOptions::newFromUser( $wgUser );
+ $this->assertEquals( "<p>$longLine</p>",
+ $this->parser->parse( $longLine, $t, $options )->getText() );
+ }
+
+ /* Test the parser entry points */
+ public function testParse() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $parserOutput = $this->parser->parse( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
+ $this->assertEquals( "<p>Test\nContent of <i>Template:Foo</i>\nContent of <i>Template:Bar</i>\n</p>", $parserOutput->getText() );
+ }
+
+ public function testPreSaveTransform() {
+ global $wgUser;
+ $title = Title::newFromText( __FUNCTION__ );
+ $outputText = $this->parser->preSaveTransform( "Test\r\n{{subst:Foo}}\n{{Bar}}", $title, $wgUser, $this->options );
+
+ $this->assertEquals( "Test\nContent of ''Template:Foo''\n{{Bar}}", $outputText );
+ }
+
+ public function testPreprocess() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $outputText = $this->parser->preprocess( "Test\n{{Foo}}\n{{Bar}}", $title, $this->options );
+
+ $this->assertEquals( "Test\nContent of ''Template:Foo''\nContent of ''Template:Bar''", $outputText );
+ }
+
+ /**
+ * cleanSig() makes all templates substs and removes tildes
+ */
+ public function testCleanSig() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $this->assertEquals( "{{SUBST:Foo}} ", $outputText );
+ }
+
+ /**
+ * cleanSig() should do nothing if disabled
+ */
+ public function testCleanSigDisabled() {
+ $this->setMwGlobals( 'wgCleanSignatures', false );
+
+ $title = Title::newFromText( __FUNCTION__ );
+ $outputText = $this->parser->cleanSig( "{{Foo}} ~~~~" );
+
+ $this->assertEquals( "{{Foo}} ~~~~", $outputText );
+ }
+
+ /**
+ * cleanSigInSig() just removes tildes
+ * @dataProvider provideStringsForCleanSigInSig
+ */
+ public function testCleanSigInSig( $in, $out ) {
+ $this->assertEquals( Parser::cleanSigInSig( $in ), $out );
+ }
+
+ public static function provideStringsForCleanSigInSig() {
+ return array(
+ array( "{{Foo}} ~~~~", "{{Foo}} " ),
+ array( "~~~", "" ),
+ array( "~~~~~", "" ),
+ );
+ }
+
+ public function testGetSection() {
+ $outputText2 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 2 );
+ $outputText1 = $this->parser->getSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1 );
+
+ $this->assertEquals( "=== Heading 2 ===\nSection 2", $outputText2 );
+ $this->assertEquals( "== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2", $outputText1 );
+ }
+
+ public function testReplaceSection() {
+ $outputText = $this->parser->replaceSection( "Section 0\n== Heading 1 ==\nSection 1\n=== Heading 2 ===\nSection 2\n== Heading 3 ==\nSection 3\n", 1, "New section 1" );
+
+ $this->assertEquals( "Section 0\nNew section 1\n\n== Heading 3 ==\nSection 3", $outputText );
+ }
+
+ /**
+ * Templates and comments are not affected, but noinclude/onlyinclude is.
+ */
+ public function testGetPreloadText() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $outputText = $this->parser->getPreloadText( "{{Foo}}<noinclude> censored</noinclude> information <!-- is very secret -->", $title, $this->options );
+
+ $this->assertEquals( "{{Foo}} information <!-- is very secret -->", $outputText );
+ }
+
+ static function statelessFetchTemplate( $title, $parser = false ) {
+ $text = "Content of ''" . $title->getFullText() . "''";
+ $deps = array();
+
+ return array(
+ 'text' => $text,
+ 'finalTitle' => $title,
+ 'deps' => $deps );
+ }
+
+ /**
+ * @group Database
+ */
+ public function testTrackingCategory() {
+ $title = Title::newFromText( __FUNCTION__ );
+ $catName = wfMessage( 'broken-file-category' )->inContentLanguage()->text();
+ $cat = Title::makeTitleSafe( NS_CATEGORY, $catName );
+ $expected = array( $cat->getDBkey() );
+ $parserOutput = $this->parser->parse( "[[file:nonexistent]]", $title, $this->options );
+ $result = $parserOutput->getCategoryLinks();
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * @group Database
+ */
+ public 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/FallbackTest.php b/tests/phpunit/includes/FallbackTest.php
new file mode 100644
index 00000000..f408f471
--- /dev/null
+++ b/tests/phpunit/includes/FallbackTest.php
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * @covers Fallback
+ */
+class FallbackTest extends MediaWikiTestCase {
+
+ public function testFallbackMbstringFunctions() {
+
+ if ( !extension_loaded( 'mbstring' ) ) {
+ $this->markTestSkipped( "The mb_string functions must be installed to test the fallback functions" );
+ }
+
+ $sampleUTF = "Östergötland_coat_of_arms.png";
+
+ //mb_substr
+ $substr_params = array(
+ array( 0, 0 ),
+ array( 5, -4 ),
+ array( 33 ),
+ array( 100, -5 ),
+ array( -8, 10 ),
+ array( 1, 1 ),
+ array( 2, -1 )
+ );
+
+ foreach ( $substr_params as $param_set ) {
+ $old_param_set = $param_set;
+ array_unshift( $param_set, $sampleUTF );
+
+ $this->assertEquals(
+ call_user_func_array( 'mb_substr', $param_set ),
+ call_user_func_array( 'Fallback::mb_substr', $param_set ),
+ 'Fallback mb_substr with params ' . implode( ', ', $old_param_set )
+ );
+ }
+
+ //mb_strlen
+ $this->assertEquals(
+ mb_strlen( $sampleUTF ),
+ Fallback::mb_strlen( $sampleUTF ),
+ 'Fallback mb_strlen'
+ );
+
+ //mb_str(r?)pos
+ $strpos_params = array(
+ //array( 'ter' ),
+ //array( 'Ö' ),
+ //array( 'Ö', 3 ),
+ //array( 'oat_', 100 ),
+ //array( 'c', -10 ),
+ //Broken for now
+ );
+
+ foreach ( $strpos_params as $param_set ) {
+ $old_param_set = $param_set;
+ array_unshift( $param_set, $sampleUTF );
+
+ $this->assertEquals(
+ call_user_func_array( 'mb_strpos', $param_set ),
+ call_user_func_array( 'Fallback::mb_strpos', $param_set ),
+ 'Fallback mb_strpos with params ' . implode( ', ', $old_param_set )
+ );
+
+ $this->assertEquals(
+ call_user_func_array( 'mb_strrpos', $param_set ),
+ call_user_func_array( 'Fallback::mb_strrpos', $param_set ),
+ 'Fallback mb_strrpos with params ' . implode( ', ', $old_param_set )
+ );
+ }
+ }
+
+} \ No newline at end of file
diff --git a/tests/phpunit/includes/FauxRequestTest.php b/tests/phpunit/includes/FauxRequestTest.php
new file mode 100644
index 00000000..9f3aa11d
--- /dev/null
+++ b/tests/phpunit/includes/FauxRequestTest.php
@@ -0,0 +1,15 @@
+<?php
+
+class FauxRequestTest extends MediaWikiTestCase {
+
+ public function testGetSetHeader() {
+ $value = 'test/test';
+
+ $request = new FauxRequest();
+ $request->setHeader( 'Content-Type', $value );
+
+ $this->assertEquals( $request->getHeader( 'Content-Type' ), $value );
+ $this->assertEquals( $request->getHeader( 'CONTENT-TYPE' ), $value );
+ $this->assertEquals( $request->getHeader( 'content-type' ), $value );
+ }
+}
diff --git a/tests/phpunit/includes/FauxResponseTest.php b/tests/phpunit/includes/FauxResponseTest.php
new file mode 100644
index 00000000..f9ba1b3b
--- /dev/null
+++ b/tests/phpunit/includes/FauxResponseTest.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * Tests for the FauxResponse class
+ *
+ * Copyright @ 2011 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+class FauxResponseTest extends MediaWikiTestCase {
+ var $response;
+
+ protected function setUp() {
+ parent::setUp();
+ $this->response = new FauxResponse;
+ }
+
+ public function testCookie() {
+ $this->assertEquals( null, $this->response->getcookie( 'key' ), 'Non-existing cookie' );
+ $this->response->setcookie( 'key', 'val' );
+ $this->assertEquals( 'val', $this->response->getcookie( 'key' ), 'Existing cookie' );
+ }
+
+ public function testHeader() {
+ $this->assertEquals( null, $this->response->getheader( 'Location' ), 'Non-existing header' );
+
+ $this->response->header( 'Location: http://localhost/' );
+ $this->assertEquals( 'http://localhost/', $this->response->getheader( 'Location' ), 'Set header' );
+
+ $this->response->header( 'Location: http://127.0.0.1/' );
+ $this->assertEquals( 'http://127.0.0.1/', $this->response->getheader( 'Location' ), 'Same header' );
+
+ $this->response->header( 'Location: http://127.0.0.2/', false );
+ $this->assertEquals( 'http://127.0.0.1/', $this->response->getheader( 'Location' ), 'Same header with override disabled' );
+
+ $this->response->header( 'Location: http://localhost/' );
+ $this->assertEquals( 'http://localhost/', $this->response->getheader( 'LOCATION' ), 'Get header case insensitive' );
+ }
+
+ public function testResponseCode() {
+ $this->response->header( 'HTTP/1.1 200' );
+ $this->assertEquals( 200, $this->response->getStatusCode(), 'Header with no message' );
+
+ $this->response->header( 'HTTP/1.x 201' );
+ $this->assertEquals( 201, $this->response->getStatusCode(), 'Header with no message and protocol 1.x' );
+
+ $this->response->header( 'HTTP/1.1 202 OK' );
+ $this->assertEquals( 202, $this->response->getStatusCode(), 'Normal header' );
+
+ $this->response->header( 'HTTP/1.x 203 OK' );
+ $this->assertEquals( 203, $this->response->getStatusCode(), 'Normal header with no message and protocol 1.x' );
+
+ $this->response->header( 'HTTP/1.x 204 OK', false, 205 );
+ $this->assertEquals( 205, $this->response->getStatusCode(), 'Third parameter overrides the HTTP/... header' );
+
+ $this->response->header( 'Location: http://localhost/', false, 206 );
+ $this->assertEquals( 206, $this->response->getStatusCode(), 'Third parameter with another header' );
+ }
+}
diff --git a/tests/phpunit/includes/FormOptionsInitializationTest.php b/tests/phpunit/includes/FormOptionsInitializationTest.php
new file mode 100644
index 00000000..fb2304dc
--- /dev/null
+++ b/tests/phpunit/includes/FormOptionsInitializationTest.php
@@ -0,0 +1,84 @@
+<?php
+/**
+ * This file host two test case classes for the MediaWiki FormOptions class:
+ * - FormOptionsInitializationTest : tests initialization of the class.
+ * - FormOptionsTest : tests methods an on instance
+ *
+ * The split let us take advantage of setting up a fixture for the methods
+ * tests.
+ */
+
+/**
+ * Dummy class to makes FormOptions::$options public.
+ * Used by FormOptionsInitializationTest which need to verify the $options
+ * array is correctly set through the FormOptions::add() function.
+ */
+class FormOptionsExposed extends FormOptions {
+ public function getOptions() {
+ return $this->options;
+ }
+}
+
+/**
+ * Test class for FormOptions initialization
+ * Ensure the FormOptions::add() does what we want it to do.
+ *
+ * Generated by PHPUnit on 2011-02-28 at 20:46:27.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsInitializationTest extends MediaWikiTestCase {
+ /**
+ * @var FormOptions
+ */
+ protected $object;
+
+
+ /**
+ * A new fresh and empty FormOptions object to test initialization
+ * with.
+ */
+ protected function setUp() {
+ parent::setUp();
+ $this->object = new FormOptionsExposed();
+ }
+
+ public function testAddStringOption() {
+ $this->object->add( 'foo', 'string value' );
+ $this->assertEquals(
+ array(
+ 'foo' => array(
+ 'default' => 'string value',
+ 'consumed' => false,
+ 'type' => FormOptions::STRING,
+ 'value' => null,
+ )
+ ),
+ $this->object->getOptions()
+ );
+ }
+
+ public function testAddIntegers() {
+ $this->object->add( 'one', 1 );
+ $this->object->add( 'negone', -1 );
+ $this->assertEquals(
+ array(
+ 'negone' => array(
+ 'default' => -1,
+ 'value' => null,
+ 'consumed' => false,
+ 'type' => FormOptions::INT,
+ ),
+ 'one' => array(
+ 'default' => 1,
+ 'value' => null,
+ 'consumed' => false,
+ 'type' => FormOptions::INT,
+ )
+ ),
+ $this->object->getOptions()
+ );
+ }
+}
diff --git a/tests/phpunit/includes/FormOptionsTest.php b/tests/phpunit/includes/FormOptionsTest.php
new file mode 100644
index 00000000..0a13cfec
--- /dev/null
+++ b/tests/phpunit/includes/FormOptionsTest.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ * This file host two test case classes for the MediaWiki FormOptions class:
+ * - FormOptionsInitializationTest : tests initialization of the class.
+ * - FormOptionsTest : tests methods an on instance
+ *
+ * The split let us take advantage of setting up a fixture for the methods
+ * tests.
+ */
+
+/**
+ * Test class for FormOptions methods.
+ * Generated by PHPUnit on 2011-02-28 at 20:46:27.
+ *
+ * Copyright © 2011, Antoine Musso
+ *
+ * @author Antoine Musso
+ */
+class FormOptionsTest extends MediaWikiTestCase {
+ /**
+ * @var FormOptions
+ */
+ protected $object;
+
+ /**
+ * Instanciates a FormOptions object to play with.
+ * FormOptions::add() is tested by the class FormOptionsInitializationTest
+ * so we assume the function is well tested already an use it to create
+ * the fixture.
+ */
+ protected function setUp() {
+ parent::setUp();
+ $this->object = new FormOptions;
+ $this->object->add( 'string1', 'string one' );
+ $this->object->add( 'string2', 'string two' );
+ $this->object->add( 'integer', 0 );
+ $this->object->add( 'intnull', 0, FormOptions::INTNULL );
+ }
+
+ /** Helpers for testGuessType() */
+ /* @{ */
+ private function assertGuessBoolean( $data ) {
+ $this->guess( FormOptions::BOOL, $data );
+ }
+ private function assertGuessInt( $data ) {
+ $this->guess( FormOptions::INT, $data );
+ }
+ private function assertGuessString( $data ) {
+ $this->guess( FormOptions::STRING, $data );
+ }
+
+ /** Generic helper */
+ private function guess( $expected, $data ) {
+ $this->assertEquals(
+ $expected,
+ FormOptions::guessType( $data )
+ );
+ }
+ /* @} */
+
+ /**
+ * Reuse helpers above assertGuessBoolean assertGuessInt assertGuessString
+ */
+ public function testGuessTypeDetection() {
+ $this->assertGuessBoolean( true );
+ $this->assertGuessBoolean( false );
+
+ $this->assertGuessInt( 0 );
+ $this->assertGuessInt( -5 );
+ $this->assertGuessInt( 5 );
+ $this->assertGuessInt( 0x0F );
+
+ $this->assertGuessString( 'true' );
+ $this->assertGuessString( 'false' );
+ $this->assertGuessString( '5' );
+ $this->assertGuessString( '0' );
+ }
+
+ /**
+ * @expectedException MWException
+ */
+ public function testGuessTypeOnArrayThrowException() {
+ $this->object->guessType( array( 'foo' ) );
+ }
+ /**
+ * @expectedException MWException
+ */
+ public function testGuessTypeOnNullThrowException() {
+ $this->object->guessType( null );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/GlobalTest.php b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
new file mode 100644
index 00000000..6154df1d
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalTest.php
@@ -0,0 +1,652 @@
+<?php
+
+class GlobalTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $readOnlyFile = tempnam( wfTempDir(), "mwtest_readonly" );
+ unlink( $readOnlyFile );
+
+ $this->setMwGlobals( array(
+ 'wgReadOnlyFile' => $readOnlyFile,
+ 'wgUrlProtocols' => array(
+ 'http://',
+ 'https://',
+ 'mailto:',
+ '//',
+ 'file://', # Non-default
+ ),
+ ) );
+ }
+
+ protected function tearDown() {
+ global $wgReadOnlyFile;
+
+ if ( file_exists( $wgReadOnlyFile ) ) {
+ unlink( $wgReadOnlyFile );
+ }
+
+ parent::tearDown();
+ }
+
+ /**
+ * @dataProvider provideForWfArrayDiff2
+ * @covers ::wfArrayDiff2
+ */
+ public function testWfArrayDiff2( $a, $b, $expected ) {
+ $this->assertEquals(
+ wfArrayDiff2( $a, $b ), $expected
+ );
+ }
+
+ // @todo Provide more tests
+ public static function provideForWfArrayDiff2() {
+ // $a $b $expected
+ return array(
+ array(
+ array( 'a', 'b' ),
+ array( 'a', 'b' ),
+ array(),
+ ),
+ array(
+ array( array( 'a' ), array( 'a', 'b', 'c' ) ),
+ array( array( 'a' ), array( 'a', 'b' ) ),
+ array( 1 => array( 'a', 'b', 'c' ) ),
+ ),
+ );
+ }
+
+ /**
+ * @covers ::wfRandom
+ */
+ public function testRandom() {
+ # This could hypothetically fail, but it shouldn't ;)
+ $this->assertFalse(
+ wfRandom() == wfRandom() );
+ }
+
+ /**
+ * @covers ::wfUrlencode
+ */
+ public function testUrlencode() {
+ $this->assertEquals(
+ "%E7%89%B9%E5%88%A5:Contributions/Foobar",
+ wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) );
+ }
+
+ /**
+ * @covers ::wfExpandIRI
+ */
+ public function testExpandIRI() {
+ $this->assertEquals(
+ "https://te.wikibooks.org/wiki/ఉబుంటు_వాడుకరి_మార్గదర్శని",
+ wfExpandIRI( "https://te.wikibooks.org/wiki/%E0%B0%89%E0%B0%AC%E0%B1%81%E0%B0%82%E0%B0%9F%E0%B1%81_%E0%B0%B5%E0%B0%BE%E0%B0%A1%E0%B1%81%E0%B0%95%E0%B0%B0%E0%B0%BF_%E0%B0%AE%E0%B0%BE%E0%B0%B0%E0%B1%8D%E0%B0%97%E0%B0%A6%E0%B0%B0%E0%B1%8D%E0%B0%B6%E0%B0%A8%E0%B0%BF" ) );
+ }
+
+ /**
+ * @covers ::wfReadOnly
+ */
+ public function testReadOnlyEmpty() {
+ global $wgReadOnly;
+ $wgReadOnly = null;
+
+ $this->assertFalse( wfReadOnly() );
+ $this->assertFalse( wfReadOnly() );
+ }
+
+ /**
+ * @covers ::wfReadOnly
+ */
+ public function testReadOnlySet() {
+ global $wgReadOnly, $wgReadOnlyFile;
+
+ $f = fopen( $wgReadOnlyFile, "wt" );
+ fwrite( $f, 'Message' );
+ fclose( $f );
+ $wgReadOnly = null; # Check on $wgReadOnlyFile
+
+ $this->assertTrue( wfReadOnly() );
+ $this->assertTrue( wfReadOnly() ); # Check cached
+
+ unlink( $wgReadOnlyFile );
+ $wgReadOnly = null; # Clean cache
+
+ $this->assertFalse( wfReadOnly() );
+ $this->assertFalse( wfReadOnly() );
+ }
+
+ public static function provideArrayToCGI() {
+ 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 provideArrayToCGI
+ * @covers ::wfArrayToCgi
+ */
+ public function testArrayToCGI( $array, $result ) {
+ $this->assertEquals( $result, wfArrayToCgi( $array ) );
+ }
+
+
+ /**
+ * @covers ::testWfArrayDiff2
+ */
+ public function testArrayToCGI2() {
+ $this->assertEquals(
+ "baz=bar&foo=bar",
+ wfArrayToCgi(
+ array( 'baz' => 'bar' ),
+ array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) );
+ }
+
+ public static function provideCgiToArray() {
+ 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 provideCgiToArray
+ * @covers ::wfCgiToArray
+ */
+ public function testCgiToArray( $cgi, $result ) {
+ $this->assertEquals( $result, wfCgiToArray( $cgi ) );
+ }
+
+ public static function provideCgiRoundTrip() {
+ 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 provideCgiRoundTrip
+ * @covers ::wfArrayToCgi
+ */
+ public function testCgiRoundTrip( $cgi ) {
+ $this->assertEquals( $cgi, wfArrayToCgi( wfCgiToArray( $cgi ) ) );
+ }
+
+ /**
+ * @covers ::mimeTypeMatch
+ */
+ public function testMimeTypeMatch() {
+ $this->assertEquals(
+ 'text/html',
+ mimeTypeMatch( 'text/html',
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.3 ) ) );
+ $this->assertEquals(
+ 'text/*',
+ mimeTypeMatch( 'text/html',
+ array( 'image/*' => 1.0,
+ 'text/*' => 0.5 ) ) );
+ $this->assertEquals(
+ '*/*',
+ mimeTypeMatch( 'text/html',
+ array( '*/*' => 1.0 ) ) );
+ $this->assertNull(
+ mimeTypeMatch( 'text/html',
+ array( 'image/png' => 1.0,
+ 'image/svg+xml' => 0.5 ) ) );
+ }
+
+ /**
+ * @covers ::wfNegotiateType
+ */
+ public function testNegotiateType() {
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.2 ),
+ array( 'text/html' => 1.0 ) ) );
+ $this->assertEquals(
+ 'application/xhtml+xml',
+ wfNegotiateType(
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.7,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.2 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'text/html' => 1.0,
+ 'text/plain' => 0.5,
+ 'text/*' => 0.5,
+ 'application/xhtml+xml' => 0.2 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertEquals(
+ 'text/html',
+ wfNegotiateType(
+ array( 'text/*' => 1.0,
+ 'image/*' => 0.7,
+ '*/*' => 0.3 ),
+ array( 'application/xhtml+xml' => 1.0,
+ 'text/html' => 0.5 ) ) );
+ $this->assertNull(
+ wfNegotiateType(
+ array( 'text/*' => 1.0 ),
+ array( 'application/xhtml+xml' => 1.0 ) ) );
+ }
+
+ /**
+ * @covers ::wfDebug
+ * @covers ::wfDebugMem
+ */
+ public function testDebugFunctionTest() {
+
+ global $wgDebugLogFile, $wgDebugTimestamps;
+
+ $old_log_file = $wgDebugLogFile;
+ $wgDebugLogFile = tempnam( wfTempDir(), 'mw-' );
+ # @todo FIXME: $wgDebugTimestamps should be tested
+ $old_wgDebugTimestamps = $wgDebugTimestamps;
+ $wgDebugTimestamps = false;
+
+ wfDebug( "This is a normal string" );
+ $this->assertEquals( "This is a normal string", file_get_contents( $wgDebugLogFile ) );
+ unlink( $wgDebugLogFile );
+
+ wfDebug( "This is nöt an ASCII string" );
+ $this->assertEquals( "This is nöt an ASCII string", file_get_contents( $wgDebugLogFile ) );
+ unlink( $wgDebugLogFile );
+
+ wfDebug( "\00305This has böth UTF and control chars\003" );
+ $this->assertEquals( " 05This has böth UTF and control chars ", file_get_contents( $wgDebugLogFile ) );
+ unlink( $wgDebugLogFile );
+
+ wfDebugMem();
+ $this->assertGreaterThan( 5000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) );
+ unlink( $wgDebugLogFile );
+
+ wfDebugMem( true );
+ $this->assertGreaterThan( 5000000, preg_replace( '/\D/', '', file_get_contents( $wgDebugLogFile ) ) );
+ unlink( $wgDebugLogFile );
+
+ $wgDebugLogFile = $old_log_file;
+ $wgDebugTimestamps = $old_wgDebugTimestamps;
+ }
+
+ /**
+ * @covers ::wfClientAcceptsGzip
+ */
+ public function testClientAcceptsGzipTest() {
+
+ $settings = array(
+ 'gzip' => true,
+ 'bzip' => false,
+ '*' => false,
+ 'compress, gzip' => true,
+ 'gzip;q=1.0' => true,
+ 'foozip' => false,
+ 'foo*zip' => false,
+ 'gzip;q=abcde' => true, //is this REALLY valid?
+ 'gzip;q=12345678.9' => true,
+ ' gzip' => true,
+ );
+
+ if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) ) {
+ $old_server_setting = $_SERVER['HTTP_ACCEPT_ENCODING'];
+ }
+
+ foreach ( $settings as $encoding => $expect ) {
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = $encoding;
+
+ $this->assertEquals( $expect, wfClientAcceptsGzip( true ),
+ "'$encoding' => " . wfBoolToStr( $expect ) );
+ }
+
+ if ( isset( $old_server_setting ) ) {
+ $_SERVER['HTTP_ACCEPT_ENCODING'] = $old_server_setting;
+ }
+ }
+
+ /**
+ * @covers ::swap
+ */
+ public function testSwapVarsTest() {
+ $var1 = 1;
+ $var2 = 2;
+
+ $this->assertEquals( $var1, 1, 'var1 is set originally' );
+ $this->assertEquals( $var2, 2, 'var1 is set originally' );
+
+ swap( $var1, $var2 );
+
+ $this->assertEquals( $var1, 2, 'var1 is swapped' );
+ $this->assertEquals( $var2, 1, 'var2 is swapped' );
+ }
+
+ /**
+ * @covers ::wfPercent
+ */
+ public function testWfPercentTest() {
+
+ $pcts = array(
+ array( 6 / 7, '0.86%', 2, false ),
+ array( 3 / 3, '1%' ),
+ array( 22 / 7, '3.14286%', 5 ),
+ array( 3 / 6, '0.5%' ),
+ array( 1 / 3, '0%', 0 ),
+ array( 10 / 3, '0%', -1 ),
+ array( 3 / 4 / 5, '0.1%', 1 ),
+ array( 6 / 7 * 8, '6.8571428571%', 10 ),
+ );
+
+ foreach ( $pcts as $pct ) {
+ if ( !isset( $pct[2] ) ) {
+ $pct[2] = 2;
+ }
+ if ( !isset( $pct[3] ) ) {
+ $pct[3] = true;
+ }
+
+ $this->assertEquals( wfPercent( $pct[0], $pct[2], $pct[3] ), $pct[1], $pct[1] );
+ }
+ }
+
+ /**
+ * test @see wfShorthandToInteger()
+ * @dataProvider provideShorthand
+ * @covers ::wfShorthandToInteger
+ */
+ public function testWfShorthandToInteger( $shorthand, $expected ) {
+ $this->assertEquals( $expected,
+ wfShorthandToInteger( $shorthand )
+ );
+ }
+
+ /** array( shorthand, expected integer ) */
+ public static function provideShorthand() {
+ return array(
+ # Null, empty ...
+ array( '', -1 ),
+ array( ' ', -1 ),
+ array( null, -1 ),
+
+ # Failures returns 0 :(
+ array( 'ABCDEFG', 0 ),
+ array( 'Ak', 0 ),
+
+ # Int, strings with spaces
+ array( 1, 1 ),
+ array( ' 1 ', 1 ),
+ array( 1023, 1023 ),
+ array( ' 1023 ', 1023 ),
+
+ # kilo, Mega, Giga
+ array( '1k', 1024 ),
+ array( '1K', 1024 ),
+ array( '1m', 1024 * 1024 ),
+ array( '1M', 1024 * 1024 ),
+ array( '1g', 1024 * 1024 * 1024 ),
+ array( '1G', 1024 * 1024 * 1024 ),
+
+ # Negatives
+ array( -1, -1 ),
+ array( -500, -500 ),
+ array( '-500', -500 ),
+ array( '-1k', -1024 ),
+
+ # Zeroes
+ array( '0', 0 ),
+ array( '0k', 0 ),
+ array( '0M', 0 ),
+ array( '0G', 0 ),
+ array( '-0', 0 ),
+ array( '-0k', 0 ),
+ array( '-0M', 0 ),
+ array( '-0G', 0 ),
+ );
+ }
+
+ /**
+ * @param String $old: Text as it was in the database
+ * @param String $mine: Text submitted while user was editing
+ * @param String $yours: Text submitted by the user
+ * @param Boolean $expectedMergeResult Whether the merge should be a success
+ * @param String $expectedText: Text after merge has been completed
+ *
+ * @dataProvider provideMerge()
+ * @group medium
+ * @covers ::wfMerge
+ */
+ public function testMerge( $old, $mine, $yours, $expectedMergeResult, $expectedText ) {
+ $this->checkHasDiff3();
+
+ $mergedText = null;
+ $isMerged = wfMerge( $old, $mine, $yours, $mergedText );
+
+ $msg = 'Merge should be a ';
+ $msg .= $expectedMergeResult ? 'success' : 'failure';
+ $this->assertEquals( $expectedMergeResult, $isMerged, $msg );
+
+ if ( $isMerged ) {
+ // Verify the merged text
+ $this->assertEquals( $expectedText, $mergedText,
+ 'is merged text as expected?' );
+ }
+ }
+
+ public static function provideMerge() {
+ $EXPECT_MERGE_SUCCESS = true;
+ $EXPECT_MERGE_FAILURE = false;
+
+ return array(
+ // #0: clean merge
+ array(
+ // old:
+ "one one one\n" . // trimmed
+ "\n" .
+ "two two two",
+
+ // mine:
+ "one one one ONE ONE\n" .
+ "\n" .
+ "two two two\n", // with tailing whitespace
+
+ // yours:
+ "one one one\n" .
+ "\n" .
+ "two two TWO TWO", // trimmed
+
+ // ok:
+ $EXPECT_MERGE_SUCCESS,
+
+ // result:
+ "one one one ONE ONE\n" .
+ "\n" .
+ "two two TWO TWO\n", // note: will always end in a newline
+ ),
+
+ // #1: conflict, fail
+ array(
+ // old:
+ "one one one", // trimmed
+
+ // mine:
+ "one one one ONE ONE\n" .
+ "\n" .
+ "bla bla\n" .
+ "\n", // with tailing whitespace
+
+ // yours:
+ "one one one\n" .
+ "\n" .
+ "two two", // trimmed
+
+ $EXPECT_MERGE_FAILURE,
+
+ // result:
+ null,
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideMakeUrlIndexes()
+ * @covers ::wfMakeUrlIndexes
+ */
+ public function testMakeUrlIndexes( $url, $expected ) {
+ $index = wfMakeUrlIndexes( $url );
+ $this->assertEquals( $expected, $index, "wfMakeUrlIndexes(\"$url\")" );
+ }
+
+ public static function provideMakeUrlIndexes() {
+ return array(
+ array(
+ // just a regular :)
+ 'https://bugzilla.wikimedia.org/show_bug.cgi?id=28627',
+ array( 'https://org.wikimedia.bugzilla./show_bug.cgi?id=28627' )
+ ),
+ array(
+ // mailtos are handled special
+ // is this really right though? that final . probably belongs earlier?
+ 'mailto:wiki@wikimedia.org',
+ array( 'mailto:org.wikimedia@wiki.' )
+ ),
+
+ // file URL cases per bug 28627...
+ array(
+ // three slashes: local filesystem path Unix-style
+ 'file:///whatever/you/like.txt',
+ array( 'file://./whatever/you/like.txt' )
+ ),
+ array(
+ // three slashes: local filesystem path Windows-style
+ 'file:///c:/whatever/you/like.txt',
+ array( 'file://./c:/whatever/you/like.txt' )
+ ),
+ array(
+ // two slashes: UNC filesystem path Windows-style
+ 'file://intranet/whatever/you/like.txt',
+ array( 'file://intranet./whatever/you/like.txt' )
+ ),
+ // Multiple-slash cases that can sorta work on Mozilla
+ // if you hack it just right are kinda pathological,
+ // and unreliable cross-platform or on IE which means they're
+ // unlikely to appear on intranets.
+ //
+ // Those will survive the algorithm but with results that
+ // are less consistent.
+
+ // protocol-relative URL cases per bug 29854...
+ array(
+ '//bugzilla.wikimedia.org/show_bug.cgi?id=28627',
+ array(
+ 'http://org.wikimedia.bugzilla./show_bug.cgi?id=28627',
+ 'https://org.wikimedia.bugzilla./show_bug.cgi?id=28627'
+ )
+ ),
+ );
+ }
+
+ /**
+ * @dataProvider provideWfMatchesDomainList
+ * @covers ::wfMatchesDomainList
+ */
+ public function testWfMatchesDomainList( $url, $domains, $expected, $description ) {
+ $actual = wfMatchesDomainList( $url, $domains );
+ $this->assertEquals( $expected, $actual, $description );
+ }
+
+ public static 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" ),
+ array( "$p//nds-nl.wikipedia.org", array( 'nl.wikipedia.org' ), false, "Non-matching substring of domain, $pDesc URL" ),
+ ) );
+ }
+
+ return $a;
+ }
+
+ /**
+ * @covers ::wfMkdirParents
+ */
+ public function testWfMkdirParents() {
+ // Should not return true if file exists instead of directory
+ $fname = $this->getNewTempFile();
+ wfSuppressWarnings();
+ $ok = wfMkdirParents( $fname );
+ wfRestoreWarnings();
+ $this->assertFalse( $ok );
+ }
+
+ /**
+ * @dataProvider provideWfShellMaintenanceCmdList
+ * @covers ::wfShellMaintenanceCmd
+ */
+ public 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 );
+ }
+
+ public static function provideWfShellMaintenanceCmdList() {
+ global $wgPhpCli;
+
+ return array(
+ array( 'eval.php', array( '--help', '--test' ), array(),
+ "'$wgPhpCli' 'eval.php' '--help' '--test'",
+ "Called eval.php --help --test" ),
+ array( 'eval.php', array( '--help', '--test space' ), array( 'php' => 'php5' ),
+ "'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..cf891e7b
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/GlobalWithDBTest.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @group Database
+ */
+class GlobalWithDBTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideWfIsBadImageList
+ * @covers ::wfIsBadImage
+ */
+ public function testWfIsBadImage( $name, $title, $blacklist, $expected, $desc ) {
+ $this->assertEquals( $expected, wfIsBadImage( $name, $title, $blacklist ), $desc );
+ }
+
+ public static 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/README b/tests/phpunit/includes/GlobalFunctions/README
new file mode 100644
index 00000000..0042bdac
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/README
@@ -0,0 +1,2 @@
+This directory hold tests for includes/GlobalFunctions.php file
+which is a pile of functions.
diff --git a/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
new file mode 100644
index 00000000..9bb74873
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfAssembleUrlTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @covers ::wfAssembleUrl
+ */
+class WfAssembleUrlTest 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 static 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..a01c0d49
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBCP47Test.php
@@ -0,0 +1,120 @@
+<?php
+/**
+ * @covers ::wfBCP47
+ */
+class WfBCP47Test extends MediaWikiTestCase {
+ /**
+ * test @see wfBCP47().
+ * Please note the BCP explicitly state that language codes are case
+ * insensitive, there are some exceptions to the rule :)
+ * This test is used to verify our formatting against all lower and
+ * all upper cases language code.
+ *
+ * @see http://tools.ietf.org/html/bcp47
+ * @dataProvider provideLanguageCodes()
+ */
+ public 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)
+ */
+ public static function provideLanguageCodes() {
+ return array(
+ // Extracted from BCP47 (list not exhaustive)
+ # 2.1.1
+ array( 'en-ca-x-ca', 'en-CA-x-ca' ),
+ array( 'sgn-be-fr', 'sgn-BE-FR' ),
+ array( '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
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php
new file mode 100644
index 00000000..7da804e6
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBaseConvertTest.php
@@ -0,0 +1,182 @@
+<?php
+/**
+ * @covers ::wfBaseConvert
+ */
+class WfBaseConvertTest extends MediaWikiTestCase {
+ public static function provideSingleDigitConversions() {
+ return array(
+ // 2 3 5 8 10 16 36
+ array( '0', '0', '0', '0', '0', '0', '0' ),
+ array( '1', '1', '1', '1', '1', '1', '1' ),
+ array( '10', '2', '2', '2', '2', '2', '2' ),
+ array( '11', '10', '3', '3', '3', '3', '3' ),
+ array( '100', '11', '4', '4', '4', '4', '4' ),
+ array( '101', '12', '10', '5', '5', '5', '5' ),
+ array( '110', '20', '11', '6', '6', '6', '6' ),
+ array( '111', '21', '12', '7', '7', '7', '7' ),
+ array( '1000', '22', '13', '10', '8', '8', '8' ),
+ array( '1001', '100', '14', '11', '9', '9', '9' ),
+ array( '1010', '101', '20', '12', '10', 'a', 'a' ),
+ array( '1011', '102', '21', '13', '11', 'b', 'b' ),
+ array( '1100', '110', '22', '14', '12', 'c', 'c' ),
+ array( '1101', '111', '23', '15', '13', 'd', 'd' ),
+ array( '1110', '112', '24', '16', '14', 'e', 'e' ),
+ array( '1111', '120', '30', '17', '15', 'f', 'f' ),
+ array( '10000', '121', '31', '20', '16', '10', 'g' ),
+ array( '10001', '122', '32', '21', '17', '11', 'h' ),
+ array( '10010', '200', '33', '22', '18', '12', 'i' ),
+ array( '10011', '201', '34', '23', '19', '13', 'j' ),
+ array( '10100', '202', '40', '24', '20', '14', 'k' ),
+ array( '10101', '210', '41', '25', '21', '15', 'l' ),
+ array( '10110', '211', '42', '26', '22', '16', 'm' ),
+ array( '10111', '212', '43', '27', '23', '17', 'n' ),
+ array( '11000', '220', '44', '30', '24', '18', 'o' ),
+ array( '11001', '221', '100', '31', '25', '19', 'p' ),
+ array( '11010', '222', '101', '32', '26', '1a', 'q' ),
+ array( '11011', '1000', '102', '33', '27', '1b', 'r' ),
+ array( '11100', '1001', '103', '34', '28', '1c', 's' ),
+ array( '11101', '1002', '104', '35', '29', '1d', 't' ),
+ array( '11110', '1010', '110', '36', '30', '1e', 'u' ),
+ array( '11111', '1011', '111', '37', '31', '1f', 'v' ),
+ array( '100000', '1012', '112', '40', '32', '20', 'w' ),
+ array( '100001', '1020', '113', '41', '33', '21', 'x' ),
+ array( '100010', '1021', '114', '42', '34', '22', 'y' ),
+ array( '100011', '1022', '120', '43', '35', '23', 'z' )
+ );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase2( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base2, wfBaseConvert( $base3, '3', '2' ) );
+ $this->assertSame( $base2, wfBaseConvert( $base5, '5', '2' ) );
+ $this->assertSame( $base2, wfBaseConvert( $base8, '8', '2' ) );
+ $this->assertSame( $base2, wfBaseConvert( $base10, '10', '2' ) );
+ $this->assertSame( $base2, wfBaseConvert( $base16, '16', '2' ) );
+ $this->assertSame( $base2, wfBaseConvert( $base36, '36', '2' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase3( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base3, wfBaseConvert( $base2, '2', '3' ) );
+ $this->assertSame( $base3, wfBaseConvert( $base5, '5', '3' ) );
+ $this->assertSame( $base3, wfBaseConvert( $base8, '8', '3' ) );
+ $this->assertSame( $base3, wfBaseConvert( $base10, '10', '3' ) );
+ $this->assertSame( $base3, wfBaseConvert( $base16, '16', '3' ) );
+ $this->assertSame( $base3, wfBaseConvert( $base36, '36', '3' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase5( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base5, wfBaseConvert( $base2, '2', '5' ) );
+ $this->assertSame( $base5, wfBaseConvert( $base3, '3', '5' ) );
+ $this->assertSame( $base5, wfBaseConvert( $base8, '8', '5' ) );
+ $this->assertSame( $base5, wfBaseConvert( $base10, '10', '5' ) );
+ $this->assertSame( $base5, wfBaseConvert( $base16, '16', '5' ) );
+ $this->assertSame( $base5, wfBaseConvert( $base36, '36', '5' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase8( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base8, wfBaseConvert( $base2, '2', '8' ) );
+ $this->assertSame( $base8, wfBaseConvert( $base3, '3', '8' ) );
+ $this->assertSame( $base8, wfBaseConvert( $base5, '5', '8' ) );
+ $this->assertSame( $base8, wfBaseConvert( $base10, '10', '8' ) );
+ $this->assertSame( $base8, wfBaseConvert( $base16, '16', '8' ) );
+ $this->assertSame( $base8, wfBaseConvert( $base36, '36', '8' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase10( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base10, wfBaseConvert( $base2, '2', '10' ) );
+ $this->assertSame( $base10, wfBaseConvert( $base3, '3', '10' ) );
+ $this->assertSame( $base10, wfBaseConvert( $base5, '5', '10' ) );
+ $this->assertSame( $base10, wfBaseConvert( $base8, '8', '10' ) );
+ $this->assertSame( $base10, wfBaseConvert( $base16, '16', '10' ) );
+ $this->assertSame( $base10, wfBaseConvert( $base36, '36', '10' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase16( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base16, wfBaseConvert( $base2, '2', '16' ) );
+ $this->assertSame( $base16, wfBaseConvert( $base3, '3', '16' ) );
+ $this->assertSame( $base16, wfBaseConvert( $base5, '5', '16' ) );
+ $this->assertSame( $base16, wfBaseConvert( $base8, '8', '16' ) );
+ $this->assertSame( $base16, wfBaseConvert( $base10, '10', '16' ) );
+ $this->assertSame( $base16, wfBaseConvert( $base36, '36', '16' ) );
+ }
+
+ /**
+ * @dataProvider provideSingleDigitConversions
+ */
+ public function testDigitToBase36( $base2, $base3, $base5, $base8, $base10, $base16, $base36 ) {
+ $this->assertSame( $base36, wfBaseConvert( $base2, '2', '36' ) );
+ $this->assertSame( $base36, wfBaseConvert( $base3, '3', '36' ) );
+ $this->assertSame( $base36, wfBaseConvert( $base5, '5', '36' ) );
+ $this->assertSame( $base36, wfBaseConvert( $base8, '8', '36' ) );
+ $this->assertSame( $base36, wfBaseConvert( $base10, '10', '36' ) );
+ $this->assertSame( $base36, wfBaseConvert( $base16, '16', '36' ) );
+ }
+
+ public function testLargeNumber() {
+ $this->assertSame( '1100110001111010000000101110100', wfBaseConvert( 'sd89ys', 36, 2 ) );
+ $this->assertSame( '11102112120221201101', wfBaseConvert( 'sd89ys', 36, 3 ) );
+ $this->assertSame( '12003102232400', wfBaseConvert( 'sd89ys', 36, 5 ) );
+ $this->assertSame( '14617200564', wfBaseConvert( 'sd89ys', 36, 8 ) );
+ $this->assertSame( '1715274100', wfBaseConvert( 'sd89ys', 36, 10 ) );
+ $this->assertSame( '663d0174', wfBaseConvert( 'sd89ys', 36, 16 ) );
+ }
+
+ public static function provideNumbers() {
+ $x = array();
+ $chars = '0123456789abcdefghijklmnopqrstuvwxyz';
+ for ( $i = 0; $i < 50; $i++ ) {
+ $base = mt_rand( 2, 36 );
+ $len = mt_rand( 10, 100 );
+
+ $str = '';
+ for ( $j = 0; $j < $len; $j++ ) {
+ $str .= $chars[mt_rand( 0, $base - 1 )];
+ }
+
+ $x[] = array( $base, $str );
+ }
+
+ return $x;
+ }
+
+ /**
+ * @dataProvider provideNumbers
+ */
+ public function testIdentity( $base, $number ) {
+ $this->assertSame( $number, wfBaseConvert( $number, $base, $base, strlen( $number ) ) );
+ }
+
+ public function testInvalid() {
+ $this->assertFalse( wfBaseConvert( '101', 1, 15 ) );
+ $this->assertFalse( wfBaseConvert( '101', 15, 1 ) );
+ $this->assertFalse( wfBaseConvert( '101', 37, 15 ) );
+ $this->assertFalse( wfBaseConvert( '101', 15, 37 ) );
+ $this->assertFalse( wfBaseConvert( 'abcde', 10, 11 ) );
+ $this->assertFalse( wfBaseConvert( '12930', 2, 10 ) );
+ $this->assertFalse( wfBaseConvert( '101', 'abc', 15 ) );
+ $this->assertFalse( wfBaseConvert( '101', 15, 'abc' ) );
+ }
+
+ public function testPadding() {
+ $number = "10101010101";
+ $this->assertSame( strlen( $number ) + 5, strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) + 5 ) ) );
+ $this->assertSame( strlen( $number ), strlen( wfBaseConvert( $number, 2, 2, strlen( $number ) - 5 ) ) );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
new file mode 100644
index 00000000..8c548040
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfBaseNameTest.php
@@ -0,0 +1,36 @@
+<?php
+/**
+ * @covers ::wfBaseName
+ */
+class WfBaseNameTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider providePaths
+ */
+ public function testBaseName( $fullpath, $basename ) {
+ $this->assertEquals( $basename, wfBaseName( $fullpath ),
+ "wfBaseName('$fullpath') => '$basename'" );
+ }
+
+ public static 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/wfExpandUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
new file mode 100644
index 00000000..41230a1e
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfExpandUrlTest.php
@@ -0,0 +1,111 @@
+<?php
+/**
+ * @covers ::wfExpandUrl
+ */
+class WfExpandUrlTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideExpandableUrls
+ */
+ public function testWfExpandUrl( $fullUrl, $shortUrl, $defaultProto, $server, $canServer, $httpsMode, $message ) {
+ // Fake $wgServer and $wgCanonicalServer
+ $this->setMwGlobals( array(
+ '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 );
+ }
+
+ /**
+ * Provider of URL examples for testing wfExpandUrl()
+ *
+ * @return array
+ */
+ public static function provideExpandableUrls() {
+ $modes = array( 'http', 'https' );
+ $servers = array(
+ 'http' => 'http://example.com',
+ 'https' => 'https://example.com',
+ 'protocol-relative' => '//example.com'
+ );
+ $defaultProtos = array(
+ 'http' => PROTO_HTTP,
+ 'https' => PROTO_HTTPS,
+ 'protocol-relative' => PROTO_RELATIVE,
+ 'current' => PROTO_CURRENT,
+ 'canonical' => PROTO_CANONICAL
+ );
+
+ $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
+ 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/wfGetCallerTest.php b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php
new file mode 100644
index 00000000..62296245
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfGetCallerTest.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @covers ::wfGetCaller
+ */
+class WfGetCallerTest extends MediaWikiTestCase {
+
+ public function testZero() {
+ $this->assertEquals( __METHOD__, wfGetCaller( 1 ) );
+ }
+
+ function callerOne() {
+ return wfGetCaller();
+ }
+
+ public function testOne() {
+ $this->assertEquals( 'WfGetCallerTest::testOne', self::callerOne() );
+ }
+
+ function intermediateFunction( $level = 2, $n = 0 ) {
+ if ( $n > 0 ) {
+ return self::intermediateFunction( $level, $n - 1 );
+ }
+
+ return wfGetCaller( $level );
+ }
+
+ public function testTwo() {
+ $this->assertEquals( 'WfGetCallerTest::testTwo', self::intermediateFunction() );
+ }
+
+ public function testN() {
+ $this->assertEquals( 'WfGetCallerTest::testN', self::intermediateFunction( 2, 0 ) );
+ $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( 1, 0 ) );
+
+ for ( $i = 0; $i < 10; $i++ ) {
+ $this->assertEquals( 'WfGetCallerTest::intermediateFunction', self::intermediateFunction( $i + 1, $i ) );
+ }
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php
new file mode 100644
index 00000000..5032dc11
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfParseUrlTest.php
@@ -0,0 +1,146 @@
+<?php
+/**
+ * Copyright © 2013 Alexandre Emsenhuber
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * @covers ::wfParseUrl
+ */
+class WfParseUrlTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgUrlProtocols', array(
+ '//', 'http://', 'file://', 'mailto:',
+ ) );
+ }
+
+ /**
+ * @dataProvider provideURLs
+ */
+ public function testWfParseUrl( $url, $parts ) {
+ $partsDump = var_export( $parts, true );
+ $this->assertEquals(
+ $parts,
+ wfParseUrl( $url ),
+ "Testing $url parses to $partsDump"
+ );
+ }
+
+ /**
+ * Provider of URLs for testing wfParseUrl()
+ *
+ * @return array
+ */
+ public static function provideURLs() {
+ return array(
+ array(
+ '//example.org',
+ array(
+ 'scheme' => '',
+ 'delimiter' => '//',
+ 'host' => 'example.org',
+ )
+ ),
+ array(
+ 'http://example.org',
+ array(
+ 'scheme' => 'http',
+ 'delimiter' => '://',
+ 'host' => 'example.org',
+ )
+ ),
+ array(
+ 'http://id:key@example.org:123/path?foo=bar#baz',
+ array(
+ 'scheme' => 'http',
+ 'delimiter' => '://',
+ 'user' => 'id',
+ 'pass' => 'key',
+ 'host' => 'example.org',
+ 'port' => 123,
+ 'path' => '/path',
+ 'query' => 'foo=bar',
+ 'fragment' => 'baz',
+ )
+ ),
+ array(
+ 'file://example.org/etc/php.ini',
+ array(
+ 'scheme' => 'file',
+ 'delimiter' => '://',
+ 'host' => 'example.org',
+ 'path' => '/etc/php.ini',
+ )
+ ),
+ array(
+ 'file:///etc/php.ini',
+ array(
+ 'scheme' => 'file',
+ 'delimiter' => '://',
+ 'host' => '',
+ 'path' => '/etc/php.ini',
+ )
+ ),
+ array(
+ 'file:///c:/',
+ array(
+ 'scheme' => 'file',
+ 'delimiter' => '://',
+ 'host' => '',
+ 'path' => '/c:/',
+ )
+ ),
+ array(
+ 'mailto:id@example.org',
+ array(
+ 'scheme' => 'mailto',
+ 'delimiter' => ':',
+ 'host' => 'id@example.org',
+ 'path' => '',
+ )
+ ),
+ array(
+ 'mailto:id@example.org?subject=Foo',
+ array(
+ 'scheme' => 'mailto',
+ 'delimiter' => ':',
+ 'host' => 'id@example.org',
+ 'path' => '',
+ 'query' => 'subject=Foo',
+ )
+ ),
+ array(
+ 'mailto:?subject=Foo',
+ array(
+ 'scheme' => 'mailto',
+ 'delimiter' => ':',
+ 'host' => '',
+ 'path' => '',
+ 'query' => 'subject=Foo',
+ )
+ ),
+ array(
+ 'invalid://test/',
+ false
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
new file mode 100644
index 00000000..238a2c9c
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfRemoveDotSegmentsTest.php
@@ -0,0 +1,91 @@
+<?php
+/**
+ *@covers ::wfRemoveDotSegments
+ */
+class WfRemoveDotSegmentsTest 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 static 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..aadec87f
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfShorthandToIntegerTest.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @covers ::wfShorthandToInteger
+ */
+class WfShorthandToIntegerTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideABunchOfShorthands
+ */
+ public function testWfShorthandToInteger( $input, $output, $description ) {
+ $this->assertEquals(
+ wfShorthandToInteger( $input ),
+ $output,
+ $description
+ );
+ }
+
+ public static 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..5998f186
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfTimestampTest.php
@@ -0,0 +1,134 @@
+<?php
+/*
+ * @covers ::wfTimestamp
+ */
+class WfTimestampTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider provideNormalTimestamps
+ */
+ public function testNormalTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ public static 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
+ */
+ public function testOldTimestamps( $input, $format, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( $format, $input ), $desc );
+ }
+
+ public static function provideOldTimestamps() {
+ return array(
+ array( '19011213204554', TS_RFC2822, 'Fri, 13 Dec 1901 20:45:54 GMT', 'Earliest time according to php documentation' ),
+ array( '20380119031407', TS_RFC2822, 'Tue, 19 Jan 2038 03:14:07 GMT', 'Latest 32 bit time' ),
+ array( '19011213204552', TS_UNIX, '-2147483648', 'Earliest 32 bit unix time' ),
+ 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
+ */
+ public function testHttpDate( $input, $output, $desc ) {
+ $this->assertEquals( $output, wfTimestamp( TS_MW, $input ), $desc );
+ }
+
+ public static function provideHttpDates() {
+ return array(
+ array( 'Sun, 06 Nov 1994 08:49:37 GMT', '19941106084937', 'RFC 822 date' ),
+ array( 'Sunday, 06-Nov-94 08:49:37 GMT', '19941106084937', 'RFC 850 date' ),
+ 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
+ */
+ public 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/GlobalFunctions/wfUrlencodeTest.php b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
new file mode 100644
index 00000000..ce6c82c5
--- /dev/null
+++ b/tests/phpunit/includes/GlobalFunctions/wfUrlencodeTest.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * The function only need a string parameter and might react to IIS7.0
+ * @covers ::wfUrlencode
+ */
+class WfUrlencodeTest extends MediaWikiTestCase {
+ #### TESTS ##############################################################
+
+ /**
+ * @dataProvider provideURLS
+ */
+ public function testEncodingUrlWith( $input, $expected ) {
+ $this->verifyEncodingFor( 'Apache', $input, $expected );
+ }
+
+ /**
+ * @dataProvider provideURLS
+ */
+ public function testEncodingUrlWithMicrosoftIis7( $input, $expected ) {
+ $this->verifyEncodingFor( 'Microsoft-IIS/7', $input, $expected );
+ }
+
+ #### HELPERS #############################################################
+
+ /**
+ * Internal helper that actually run the test.
+ * Called by the public methods testEncodingUrlWith...()
+ *
+ */
+ private function verifyEncodingFor( $server, $input, $expectations ) {
+ $expected = $this->extractExpect( $server, $expectations );
+
+ // save up global
+ $old = isset( $_SERVER['SERVER_SOFTWARE'] )
+ ? $_SERVER['SERVER_SOFTWARE']
+ : null;
+ $_SERVER['SERVER_SOFTWARE'] = $server;
+ wfUrlencode( null );
+
+ // do the requested test
+ $this->assertEquals(
+ $expected,
+ wfUrlencode( $input ),
+ "Encoding '$input' for server '$server' should be '$expected'"
+ );
+
+ // restore global
+ if ( $old === null ) {
+ unset( $_SERVER['SERVER_SOFTWARE'] );
+ } else {
+ $_SERVER['SERVER_SOFTWARE'] = $old;
+ }
+ wfUrlencode( null );
+ }
+
+ /**
+ * Interprets the provider array. Return expected value depending
+ * the HTTP server name.
+ */
+ private function extractExpect( $server, $expectations ) {
+ if ( is_string( $expectations ) ) {
+ return $expectations;
+ } elseif ( is_array( $expectations ) ) {
+ if ( !array_key_exists( $server, $expectations ) ) {
+ throw new MWException( __METHOD__ . " expectation does not have any value for server name $server. Check the provider array.\n" );
+ } else {
+ return $expectations[$server];
+ }
+ } else {
+ throw new MWException( __METHOD__ . " given invalid expectation for '$server'. Should be a string or an array( <http server name> => <string> ).\n" );
+ }
+ }
+
+ #### PROVIDERS ###########################################################
+
+ /**
+ * Format is either:
+ * array( 'input', 'expected' );
+ * Or:
+ * array( 'input',
+ * array( 'Apache', 'expected' ),
+ * array( 'Microsoft-IIS/7', 'expected' ),
+ * ),
+ * If you want to add other HTTP server name, you will have to add a new
+ * testing method much like the testEncodingUrlWith() method above.
+ */
+ public static function provideURLS() {
+ return array(
+ ### RFC 1738 chars
+ // + is not safe
+ array( '+', '%2B' ),
+ // & and = not safe in queries
+ array( '&', '%26' ),
+ array( '=', '%3D' ),
+
+ array( ':', array(
+ 'Apache' => ':',
+ 'Microsoft-IIS/7' => '%3A',
+ ) ),
+
+ // remaining chars do not need encoding
+ array(
+ ';@$-_.!*',
+ ';@$-_.!*',
+ ),
+
+ ### Other tests
+ // slash remain unchanged. %2F seems to break things
+ array( '/', '/' ),
+
+ // Other 'funnies' chars
+ array( '[]', '%5B%5D' ),
+ array( '<>', '%3C%3E' ),
+
+ // Apostrophe is encoded
+ array( '\'', '%27' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/HTMLCheckMatrixTest.php b/tests/phpunit/includes/HTMLCheckMatrixTest.php
new file mode 100644
index 00000000..5bbafd37
--- /dev/null
+++ b/tests/phpunit/includes/HTMLCheckMatrixTest.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * Unit tests for the HTMLCheckMatrix form field
+ */
+class HtmlCheckMatrixTest extends MediaWikiTestCase {
+ static private $defaultOptions = array(
+ 'rows' => array( 'r1', 'r2' ),
+ 'columns' => array( 'c1', 'c2' ),
+ 'fieldname' => 'test',
+ );
+
+ public function testPlainInstantiation() {
+ try {
+ $form = new HTMLCheckMatrix( array() );
+ } catch ( MWException $e ) {
+ $this->assertInstanceOf( 'HTMLFormFieldRequiredOptionsException', $e );
+ return;
+ }
+
+ $this->fail( 'Expected MWException indicating missing parameters but none was thrown.' );
+ }
+
+ public function testInstantiationWithMinimumRequiredParameters() {
+ $form = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertTrue( true ); // form instantiation must throw exception on failure
+ }
+
+ public function testValidateCallsUserDefinedValidationCallback() {
+ $called = false;
+ $field = new HTMLCheckMatrix( self::$defaultOptions + array(
+ 'validation-callback' => function() use ( &$called ) {
+ $called = true;
+ return false;
+ },
+ ) );
+ $this->assertEquals( false, $this->validate( $field, array() ) );
+ $this->assertTrue( $called );
+ }
+
+ public function testValidateRequiresArrayInput() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertEquals( false, $this->validate( $field, null ) );
+ $this->assertEquals( false, $this->validate( $field, true ) );
+ $this->assertEquals( false, $this->validate( $field, 'abc' ) );
+ $this->assertEquals( false, $this->validate( $field, new stdClass ) );
+ $this->assertEquals( true, $this->validate( $field, array() ) );
+ }
+
+ public function testValidateAllowsOnlyKnownTags() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertInternalType( 'string', $this->validate( $field, array( 'foo' ) ) );
+ }
+
+ public function testValidateAcceptsPartialTagList() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions );
+ $this->assertTrue( $this->validate( $field, array() ) );
+ $this->assertTrue( $this->validate( $field, array( 'c1-r1' ) ) );
+ $this->assertTrue( $this->validate( $field, array( 'c1-r1', 'c1-r2', 'c2-r1', 'c2-r2' ) ) );
+ }
+
+ /**
+ * This form object actually has no visibility into what happens later on, but essentially
+ * if the data submitted by the user passes validate the following is run:
+ * foreach ( $field->filterDataForSubmit( $data ) as $k => $v ) {
+ * $user->setOption( $k, $v );
+ * }
+ */
+ public function testValuesForcedOnRemainOn() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions + array(
+ 'force-options-on' => array( 'c2-r1' ),
+ ) );
+ $expected = array(
+ 'c1-r1' => false,
+ 'c1-r2' => false,
+ 'c2-r1' => true,
+ 'c2-r2' => false,
+ );
+ $this->assertEquals( $expected, $field->filterDataForSubmit( array() ) );
+ }
+
+ public function testValuesForcedOffRemainOff() {
+ $field = new HTMLCheckMatrix( self::$defaultOptions + array(
+ 'force-options-off' => array( 'c1-r2', 'c2-r2' ),
+ ) );
+ $expected = array(
+ 'c1-r1' => true,
+ 'c1-r2' => false,
+ 'c2-r1' => true,
+ 'c2-r2' => false,
+ );
+ // array_keys on the result simulates submitting all fields checked
+ $this->assertEquals( $expected, $field->filterDataForSubmit( array_keys( $expected ) ) );
+ }
+
+ protected function validate( HTMLFormField $field, $submitted ) {
+ return $field->validate(
+ $submitted,
+ array( self::$defaultOptions['fieldname'] => $submitted )
+ );
+ }
+}
diff --git a/tests/phpunit/includes/HashRingTest.php b/tests/phpunit/includes/HashRingTest.php
new file mode 100644
index 00000000..65f13696
--- /dev/null
+++ b/tests/phpunit/includes/HashRingTest.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @group HashRing
+ */
+class HashRingTest extends MediaWikiTestCase {
+ public function testHashRing() {
+ $ring = new HashRing( array( 's1' => 1, 's2' => 1, 's3' => 2, 's4' => 2, 's5' => 2, 's6' => 3 ) );
+
+ $locations = array();
+ for ( $i = 0; $i < 20; $i++ ) {
+ $locations[ "hello$i"] = $ring->getLocation( "hello$i" );
+ }
+ $expectedLocations = array(
+ "hello0" => "s5",
+ "hello1" => "s6",
+ "hello2" => "s2",
+ "hello3" => "s5",
+ "hello4" => "s6",
+ "hello5" => "s4",
+ "hello6" => "s5",
+ "hello7" => "s4",
+ "hello8" => "s5",
+ "hello9" => "s5",
+ "hello10" => "s3",
+ "hello11" => "s6",
+ "hello12" => "s1",
+ "hello13" => "s3",
+ "hello14" => "s3",
+ "hello15" => "s5",
+ "hello16" => "s4",
+ "hello17" => "s6",
+ "hello18" => "s6",
+ "hello19" => "s3"
+ );
+
+ $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
+
+ $locations = array();
+ for ( $i = 0; $i < 5; $i++ ) {
+ $locations[ "hello$i"] = $ring->getLocations( "hello$i", 2 );
+ }
+
+ $expectedLocations = array(
+ "hello0" => array( "s5", "s6" ),
+ "hello1" => array( "s6", "s4" ),
+ "hello2" => array( "s2", "s1" ),
+ "hello3" => array( "s5", "s6" ),
+ "hello4" => array( "s6", "s4" ),
+ );
+ $this->assertEquals( $expectedLocations, $locations, 'Items placed at proper locations' );
+ }
+}
diff --git a/tests/phpunit/includes/HooksTest.php b/tests/phpunit/includes/HooksTest.php
new file mode 100644
index 00000000..81dd4870
--- /dev/null
+++ b/tests/phpunit/includes/HooksTest.php
@@ -0,0 +1,158 @@
+<?php
+
+class HooksTest extends MediaWikiTestCase {
+
+ function setUp() {
+ global $wgHooks;
+ parent::setUp();
+ Hooks::clear( 'MediaWikiHooksTest001' );
+ unset( $wgHooks['MediaWikiHooksTest001'] );
+ }
+
+ public static function provideHooks() {
+ $i = new NothingClass();
+
+ return array(
+ array( 'Object and method', array( $i, 'someNonStatic' ), 'changed-nonstatic', 'changed-nonstatic' ),
+ array( 'Object and no method', array( $i ), 'changed-onevent', 'original' ),
+ array( 'Object and method with data', array( $i, 'someNonStaticWithData', 'data' ), 'data', 'original' ),
+ array( 'Object and static method', array( $i, 'someStatic' ), 'changed-static', 'original' ),
+ array( 'Class::method static call', array( 'NothingClass::someStatic' ), 'changed-static', 'original' ),
+ array( 'Global function', array( 'NothingFunction' ), 'changed-func', 'original' ),
+ array( 'Global function with data', array( 'NothingFunctionData', 'data' ), 'data', 'original' ),
+ array( 'Closure', array( function ( &$foo, $bar ) {
+ $foo = 'changed-closure';
+
+ return true;
+ } ), 'changed-closure', 'original' ),
+ array( 'Closure with data', array( function ( $data, &$foo, $bar ) {
+ $foo = $data;
+
+ return true;
+ }, 'data' ), 'data', 'original' )
+ );
+ }
+
+ /**
+ * @dataProvider provideHooks
+ */
+ public function testOldStyleHooks( $msg, array $hook, $expectedFoo, $expectedBar ) {
+ global $wgHooks;
+ $foo = $bar = 'original';
+
+ $wgHooks['MediaWikiHooksTest001'][] = $hook;
+ wfRunHooks( 'MediaWikiHooksTest001', array( &$foo, &$bar ) );
+
+ $this->assertSame( $expectedFoo, $foo, $msg );
+ $this->assertSame( $expectedBar, $bar, $msg );
+ }
+
+ /**
+ * @dataProvider provideHooks
+ */
+ public function testNewStyleHooks( $msg, $hook, $expectedFoo, $expectedBar ) {
+ $foo = $bar = 'original';
+
+ Hooks::register( 'MediaWikiHooksTest001', $hook );
+ Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) );
+
+ $this->assertSame( $expectedFoo, $foo, $msg );
+ $this->assertSame( $expectedBar, $bar, $msg );
+ }
+
+ public function testNewStyleHookInteraction() {
+ global $wgHooks;
+
+ $a = new NothingClass();
+ $b = new NothingClass();
+
+ $wgHooks['MediaWikiHooksTest001'][] = $a;
+ $this->assertTrue( Hooks::isRegistered( 'MediaWikiHooksTest001' ), 'Hook registered via $wgHooks should be noticed by Hooks::isRegistered' );
+
+ Hooks::register( 'MediaWikiHooksTest001', $b );
+ $this->assertEquals( 2, count( Hooks::getHandlers( 'MediaWikiHooksTest001' ) ), 'Hooks::getHandlers() should return hooks registered via wgHooks as well as Hooks::register' );
+
+ $foo = 'quux';
+ $bar = 'qaax';
+
+ Hooks::run( 'MediaWikiHooksTest001', array( &$foo, &$bar ) );
+ $this->assertEquals( 1, $a->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+ $this->assertEquals( 1, $b->calls, 'Hooks::run() should run hooks registered via wgHooks as well as Hooks::register' );
+ }
+
+ /**
+ * @expectedException MWException
+ */
+ public function testUncallableFunction() {
+ Hooks::register( 'MediaWikiHooksTest001', 'ThisFunctionDoesntExist' );
+ Hooks::run( 'MediaWikiHooksTest001', array() );
+ }
+
+ public function testFalseReturn() {
+ Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
+ return false;
+ } );
+ Hooks::register( 'MediaWikiHooksTest001', function ( &$foo ) {
+ $foo = 'test';
+
+ return true;
+ } );
+ $foo = 'original';
+ Hooks::run( 'MediaWikiHooksTest001', array( &$foo ) );
+ $this->assertSame( 'original', $foo, 'Hooks continued processing after a false return.' );
+ }
+
+ /**
+ * @expectedException FatalError
+ */
+ public function testFatalError() {
+ Hooks::register( 'MediaWikiHooksTest001', function () {
+ return 'test';
+ } );
+ Hooks::run( 'MediaWikiHooksTest001', array() );
+ }
+}
+
+function NothingFunction( &$foo, &$bar ) {
+ $foo = 'changed-func';
+
+ return true;
+}
+
+function NothingFunctionData( $data, &$foo, &$bar ) {
+ $foo = $data;
+
+ return true;
+}
+
+class NothingClass {
+ public $calls = 0;
+
+ public static function someStatic( &$foo, &$bar ) {
+ $foo = 'changed-static';
+
+ return true;
+ }
+
+ public function someNonStatic( &$foo, &$bar ) {
+ $this->calls++;
+ $foo = 'changed-nonstatic';
+ $bar = 'changed-nonstatic';
+
+ return true;
+ }
+
+ public function onMediaWikiHooksTest001( &$foo, &$bar ) {
+ $this->calls++;
+ $foo = 'changed-onevent';
+
+ return true;
+ }
+
+ public function someNonStaticWithData( $data, &$foo, &$bar ) {
+ $this->calls++;
+ $foo = $data;
+
+ return true;
+ }
+}
diff --git a/tests/phpunit/includes/HtmlFormatterTest.php b/tests/phpunit/includes/HtmlFormatterTest.php
new file mode 100644
index 00000000..a37df74f
--- /dev/null
+++ b/tests/phpunit/includes/HtmlFormatterTest.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @group HtmlFormatter
+ */
+class HtmlFormatterTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider getHtmlData
+ */
+ public function testTransform( $input, $expected, $callback = false ) {
+ $input = self::normalize( $input );
+ $formatter = new HtmlFormatter( HtmlFormatter::wrapHTML( $input ) );
+ if ( $callback ) {
+ $callback( $formatter );
+ }
+ $formatter->filterContent();
+ $html = $formatter->getText();
+ $this->assertEquals( self::normalize( $expected ), self::normalize( $html ) );
+ }
+
+ private static function normalize( $s ) {
+ return str_replace( "\n", '',
+ str_replace( "\r", '', $s ) // "yay" to Windows!
+ );
+ }
+
+ public function getHtmlData() {
+ $removeImages = function( HtmlFormatter $f ) {
+ $f->setRemoveMedia();
+ };
+ $removeTags = function( HtmlFormatter $f ) {
+ $f->remove( array( 'table', '.foo', '#bar', 'div.baz' ) );
+ };
+ $flattenSomeStuff = function( HtmlFormatter $f ) {
+ $f->flatten( array( 's', 'div' ) );
+ };
+ $flattenEverything = function( HtmlFormatter $f ) {
+ $f->flattenAllTags();
+ };
+ return array(
+ // remove images if asked
+ array(
+ '<img src="/foo/bar.jpg" alt="Blah"/>',
+ '',
+ $removeImages,
+ ),
+ // basic tag removal
+ array(
+ '<table><tr><td>foo</td></tr></table><div class="foo">foo</div><div class="foo quux">foo</div><span id="bar">bar</span>
+<strong class="foo" id="bar">foobar</strong><div class="notfoo">test</div><div class="baz"/>
+<span class="baz">baz</span>',
+
+ '<div class="notfoo">test</div>
+<span class="baz">baz</span>',
+ $removeTags,
+ ),
+ // don't flatten tags that start like chosen ones
+ array(
+ '<div><s>foo</s> <span>bar</span></div>',
+ 'foo <span>bar</span>',
+ $flattenSomeStuff,
+ ),
+ // total flattening
+ array(
+ '<div style="foo">bar<sup>2</sup></div>',
+ 'bar2',
+ $flattenEverything,
+ ),
+ // UTF-8 preservation and security
+ array(
+ '<span title="&quot; \' &amp;">&lt;Тест!&gt;</span> &amp;&lt;&#38;&#0038;&#x26;&#x026;',
+ '<span title="&quot; \' &amp;">&lt;Тест!&gt;</span> &amp;&lt;&amp;&amp;&amp;&amp;',
+ ),
+ // https://bugzilla.wikimedia.org/show_bug.cgi?id=53086
+ array(
+ 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>',
+ 'Foo<sup id="cite_ref-1" class="reference"><a href="#cite_note-1">[1]</a></sup> <a href="/wiki/Bar" title="Bar" class="mw-redirect">Bar</a>',
+ ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/HtmlTest.php b/tests/phpunit/includes/HtmlTest.php
new file mode 100644
index 00000000..1c62d032
--- /dev/null
+++ b/tests/phpunit/includes/HtmlTest.php
@@ -0,0 +1,631 @@
+<?php
+/** tests for includes/Html.php */
+
+class HtmlTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $langCode = 'en';
+ $langObj = Language::factory( $langCode );
+
+ // Hardcode namespaces during test runs,
+ // so that html output based on existing namespaces
+ // can be properly evaluated.
+ $langObj->setNamespaces( array(
+ -2 => 'Media',
+ -1 => 'Special',
+ 0 => '',
+ 1 => 'Talk',
+ 2 => 'User',
+ 3 => 'User_talk',
+ 4 => 'MyWiki',
+ 5 => 'MyWiki_Talk',
+ 6 => 'File',
+ 7 => 'File_talk',
+ 8 => 'MediaWiki',
+ 9 => 'MediaWiki_talk',
+ 10 => 'Template',
+ 11 => 'Template_talk',
+ 14 => 'Category',
+ 15 => 'Category_talk',
+ 100 => 'Custom',
+ 101 => 'Custom_talk',
+ ) );
+
+ $this->setMwGlobals( array(
+ 'wgLanguageCode' => $langCode,
+ 'wgContLang' => $langObj,
+ 'wgLang' => $langObj,
+ 'wgWellFormedXml' => false,
+ ) );
+ }
+
+ public function testElementBasics() {
+ $this->assertEquals(
+ '<img>',
+ Html::element( 'img', null, '' ),
+ 'No close tag for short-tag elements'
+ );
+
+ $this->assertEquals(
+ '<element></element>',
+ Html::element( 'element', null, null ),
+ 'Close tag for empty element (null, null)'
+ );
+
+ $this->assertEquals(
+ '<element></element>',
+ Html::element( 'element', array(), '' ),
+ 'Close tag for empty element (array, string)'
+ );
+
+ $this->setMwGlobals( 'wgWellFormedXml', true );
+
+ $this->assertEquals(
+ '<img />',
+ Html::element( 'img', null, '' ),
+ 'Self-closing tag for short-tag elements (wgWellFormedXml = true)'
+ );
+ }
+
+ public function dataXmlMimeType() {
+ return array(
+ // ( $mimetype, $isXmlMimeType )
+ # HTML is not an XML MimeType
+ array( 'text/html', false ),
+ # XML is an XML MimeType
+ array( 'text/xml', true ),
+ array( 'application/xml', true ),
+ # XHTML is an XML MimeType
+ array( 'application/xhtml+xml', true ),
+ # Make sure other +xml MimeTypes are supported
+ # SVG is another random MimeType even though we don't use it
+ array( 'image/svg+xml', true ),
+ # Complete random other MimeTypes are not XML
+ array( 'text/plain', false ),
+ );
+ }
+
+ /**
+ * @dataProvider dataXmlMimeType
+ */
+ public function testXmlMimeType( $mimetype, $isXmlMimeType ) {
+ $this->assertEquals( $isXmlMimeType, Html::isXmlMimeType( $mimetype ) );
+ }
+
+ public function testExpandAttributesSkipsNullAndFalse() {
+
+ ### EMPTY ########
+ $this->assertEmpty(
+ Html::expandAttributes( array( 'foo' => null ) ),
+ 'skip keys with null value'
+ );
+ $this->assertEmpty(
+ Html::expandAttributes( array( 'foo' => false ) ),
+ 'skip keys with false value'
+ );
+ $this->assertNotEmpty(
+ Html::expandAttributes( array( 'foo' => '' ) ),
+ 'keep keys with an empty string'
+ );
+ }
+
+ public function testExpandAttributesForBooleans() {
+ $this->assertEquals(
+ '',
+ Html::expandAttributes( array( 'selected' => false ) ),
+ 'Boolean attributes do not generates output when value is false'
+ );
+ $this->assertEquals(
+ '',
+ Html::expandAttributes( array( 'selected' => null ) ),
+ 'Boolean attributes do not generates output when value is null'
+ );
+
+ $this->assertEquals(
+ ' selected',
+ Html::expandAttributes( array( 'selected' => true ) ),
+ 'Boolean attributes have no value when value is true'
+ );
+ $this->assertEquals(
+ ' selected',
+ Html::expandAttributes( array( 'selected' ) ),
+ 'Boolean attributes have no value when value is true (passed as numerical array)'
+ );
+
+ $this->setMwGlobals( 'wgWellFormedXml', true );
+
+ $this->assertEquals(
+ ' selected=""',
+ Html::expandAttributes( array( 'selected' => true ) ),
+ 'Boolean attributes have empty string value when value is true (wgWellFormedXml)'
+ );
+ }
+
+ /**
+ * Test for Html::expandAttributes()
+ * Please note it output a string prefixed with a space!
+ */
+ public function testExpandAttributesVariousExpansions() {
+ ### NOT EMPTY ####
+ $this->assertEquals(
+ ' empty_string=""',
+ Html::expandAttributes( array( 'empty_string' => '' ) ),
+ 'Empty string is always quoted'
+ );
+ $this->assertEquals(
+ ' key=value',
+ Html::expandAttributes( array( 'key' => 'value' ) ),
+ 'Simple string value needs no quotes'
+ );
+ $this->assertEquals(
+ ' one=1',
+ Html::expandAttributes( array( 'one' => 1 ) ),
+ 'Number 1 value needs no quotes'
+ );
+ $this->assertEquals(
+ ' zero=0',
+ Html::expandAttributes( array( 'zero' => 0 ) ),
+ 'Number 0 value needs no quotes'
+ );
+
+ $this->setMwGlobals( 'wgWellFormedXml', true );
+
+ $this->assertEquals(
+ ' empty_string=""',
+ Html::expandAttributes( array( 'empty_string' => '' ) ),
+ 'Attribute values are always quoted (wgWellFormedXml): Empty string'
+ );
+ $this->assertEquals(
+ ' key="value"',
+ Html::expandAttributes( array( 'key' => 'value' ) ),
+ 'Attribute values are always quoted (wgWellFormedXml): Simple string'
+ );
+ $this->assertEquals(
+ ' one="1"',
+ Html::expandAttributes( array( 'one' => 1 ) ),
+ 'Attribute values are always quoted (wgWellFormedXml): Number 1'
+ );
+ $this->assertEquals(
+ ' zero="0"',
+ Html::expandAttributes( array( 'zero' => 0 ) ),
+ 'Attribute values are always quoted (wgWellFormedXml): Number 0'
+ );
+ }
+
+ /**
+ * 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.
+ */
+ public 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
+ */
+ public function testValueIsAuthoritativeInSpaceSeparatedAttributesArrays() {
+ $this->assertEquals(
+ ' class=""',
+ Html::expandAttributes( array( 'class' => array(
+ 'GREEN',
+ 'GREEN' => false,
+ 'GREEN',
+ ) ) )
+ );
+ }
+
+ public function testNamespaceSelector() {
+ $this->assertEquals(
+ '<select id=namespace name=namespace>' . "\n" .
+ '<option value=0>(Main)</option>' . "\n" .
+ '<option value=1>Talk</option>' . "\n" .
+ '<option value=2>User</option>' . "\n" .
+ '<option value=3>User talk</option>' . "\n" .
+ '<option value=4>MyWiki</option>' . "\n" .
+ '<option value=5>MyWiki Talk</option>' . "\n" .
+ '<option value=6>File</option>' . "\n" .
+ '<option value=7>File talk</option>' . "\n" .
+ '<option value=8>MediaWiki</option>' . "\n" .
+ '<option value=9>MediaWiki talk</option>' . "\n" .
+ '<option value=10>Template</option>' . "\n" .
+ '<option value=11>Template talk</option>' . "\n" .
+ '<option value=14>Category</option>' . "\n" .
+ '<option value=15>Category talk</option>' . "\n" .
+ '<option value=100>Custom</option>' . "\n" .
+ '<option value=101>Custom talk</option>' . "\n" .
+ '</select>',
+ Html::namespaceSelector(),
+ 'Basic namespace selector without custom options'
+ );
+
+ $this->assertEquals(
+ '<label for=mw-test-namespace>Select a namespace:</label>&#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=14>Category</option>' . "\n" .
+ '<option value=15>Category talk</option>' . "\n" .
+ '<option value=100>Custom</option>' . "\n" .
+ '<option value=101>Custom talk</option>' . "\n" .
+ '</select>',
+ Html::namespaceSelector(
+ array( 'selected' => '2', 'all' => 'all', 'label' => 'Select a namespace:' ),
+ array( 'name' => 'wpNamespace', 'id' => 'mw-test-namespace' )
+ ),
+ 'Basic namespace selector with custom values'
+ );
+
+ $this->assertEquals(
+ '<label for=namespace>Select a namespace:</label>&#160;' .
+ '<select id=namespace name=namespace>' . "\n" .
+ '<option value=0>(Main)</option>' . "\n" .
+ '<option value=1>Talk</option>' . "\n" .
+ '<option value=2>User</option>' . "\n" .
+ '<option value=3>User talk</option>' . "\n" .
+ '<option value=4>MyWiki</option>' . "\n" .
+ '<option value=5>MyWiki Talk</option>' . "\n" .
+ '<option value=6>File</option>' . "\n" .
+ '<option value=7>File talk</option>' . "\n" .
+ '<option value=8>MediaWiki</option>' . "\n" .
+ '<option value=9>MediaWiki talk</option>' . "\n" .
+ '<option value=10>Template</option>' . "\n" .
+ '<option value=11>Template talk</option>' . "\n" .
+ '<option value=14>Category</option>' . "\n" .
+ '<option value=15>Category talk</option>' . "\n" .
+ '<option value=100>Custom</option>' . "\n" .
+ '<option value=101>Custom talk</option>' . "\n" .
+ '</select>',
+ Html::namespaceSelector(
+ array( 'label' => 'Select a namespace:' )
+ ),
+ 'Basic namespace selector with a custom label but no id attribtue for the <select>'
+ );
+ }
+
+ public function testCanFilterOutNamespaces() {
+ $this->assertEquals(
+ '<select id=namespace name=namespace>' . "\n" .
+ '<option value=2>User</option>' . "\n" .
+ '<option value=4>MyWiki</option>' . "\n" .
+ '<option value=5>MyWiki Talk</option>' . "\n" .
+ '<option value=6>File</option>' . "\n" .
+ '<option value=7>File talk</option>' . "\n" .
+ '<option value=8>MediaWiki</option>' . "\n" .
+ '<option value=9>MediaWiki talk</option>' . "\n" .
+ '<option value=10>Template</option>' . "\n" .
+ '<option value=11>Template talk</option>' . "\n" .
+ '<option value=14>Category</option>' . "\n" .
+ '<option value=15>Category talk</option>' . "\n" .
+ '</select>',
+ Html::namespaceSelector(
+ array( 'exclude' => array( 0, 1, 3, 100, 101 ) )
+ ),
+ 'Namespace selector namespace filtering.'
+ );
+ }
+
+ public function testCanDisableANamespaces() {
+ $this->assertEquals(
+ '<select id=namespace name=namespace>' . "\n" .
+ '<option disabled value=0>(Main)</option>' . "\n" .
+ '<option disabled value=1>Talk</option>' . "\n" .
+ '<option disabled value=2>User</option>' . "\n" .
+ '<option disabled value=3>User talk</option>' . "\n" .
+ '<option disabled value=4>MyWiki</option>' . "\n" .
+ '<option value=5>MyWiki Talk</option>' . "\n" .
+ '<option value=6>File</option>' . "\n" .
+ '<option value=7>File talk</option>' . "\n" .
+ '<option value=8>MediaWiki</option>' . "\n" .
+ '<option value=9>MediaWiki talk</option>' . "\n" .
+ '<option value=10>Template</option>' . "\n" .
+ '<option value=11>Template talk</option>' . "\n" .
+ '<option value=14>Category</option>' . "\n" .
+ '<option value=15>Category talk</option>' . "\n" .
+ '<option value=100>Custom</option>' . "\n" .
+ '<option value=101>Custom talk</option>' . "\n" .
+ '</select>',
+ Html::namespaceSelector( array(
+ 'disable' => array( 0, 1, 2, 3, 4 )
+ ) ),
+ 'Namespace selector namespace disabling'
+ );
+ }
+
+ /**
+ * @dataProvider provideHtml5InputTypes
+ */
+ public function testHtmlElementAcceptsNewHtml5TypesInHtml5Mode( $HTML5InputType ) {
+ $this->assertEquals(
+ '<input type=' . $HTML5InputType . '>',
+ Html::element( 'input', array( 'type' => $HTML5InputType ) ),
+ 'In HTML5, HTML::element() should accept type="' . $HTML5InputType . '"'
+ );
+ }
+
+ /**
+ * List of input element types values introduced by HTML5
+ * Full list at http://www.w3.org/TR/html-markup/input.html
+ */
+ public static function provideHtml5InputTypes() {
+ $types = array(
+ 'datetime',
+ 'datetime-local',
+ 'date',
+ 'month',
+ 'time',
+ 'week',
+ 'number',
+ 'range',
+ 'email',
+ 'url',
+ 'search',
+ 'tel',
+ 'color',
+ );
+ $cases = array();
+ foreach ( $types as $type ) {
+ $cases[] = array( $type );
+ }
+
+ return $cases;
+ }
+
+ /**
+ * Test out Html::element drops or enforces default value
+ * @covers Html::dropDefaults
+ * @dataProvider provideElementsWithAttributesHavingDefaultValues
+ */
+ public function testDropDefaults( $expected, $element, $attribs, $message = '' ) {
+ $this->assertEquals( $expected, Html::element( $element, $attribs ), $message );
+ }
+
+ public static function provideElementsWithAttributesHavingDefaultValues() {
+ # Use cases in a concise format:
+ # <expected>, <element name>, <array of attributes> [, <message>]
+ # Will be mapped to Html::element()
+ $cases = array();
+
+ ### Generic cases, match $attribDefault static array
+ $cases[] = array( '<area>',
+ 'area', array( 'shape' => 'rect' )
+ );
+
+ $cases[] = array( '<button type=submit></button>',
+ 'button', array( 'formaction' => 'GET' )
+ );
+ $cases[] = array( '<button type=submit></button>',
+ 'button', array( 'formenctype' => 'application/x-www-form-urlencoded' )
+ );
+
+ $cases[] = array( '<canvas></canvas>',
+ 'canvas', array( 'height' => '150' )
+ );
+ $cases[] = array( '<canvas></canvas>',
+ 'canvas', array( 'width' => '300' )
+ );
+ # Also check with numeric values
+ $cases[] = array( '<canvas></canvas>',
+ 'canvas', array( 'height' => 150 )
+ );
+ $cases[] = array( '<canvas></canvas>',
+ 'canvas', array( 'width' => 300 )
+ );
+
+ $cases[] = array( '<command>',
+ 'command', array( 'type' => 'command' )
+ );
+
+ $cases[] = array( '<form></form>',
+ 'form', array( 'action' => 'GET' )
+ );
+ $cases[] = array( '<form></form>',
+ 'form', array( 'autocomplete' => 'on' )
+ );
+ $cases[] = array( '<form></form>',
+ 'form', array( 'enctype' => 'application/x-www-form-urlencoded' )
+ );
+
+ $cases[] = array( '<input>',
+ 'input', array( 'formaction' => 'GET' )
+ );
+ $cases[] = array( '<input>',
+ 'input', array( 'type' => 'text' )
+ );
+
+ $cases[] = array( '<keygen>',
+ 'keygen', array( 'keytype' => 'rsa' )
+ );
+
+ $cases[] = array( '<link>',
+ 'link', array( 'media' => 'all' )
+ );
+
+ $cases[] = array( '<menu></menu>',
+ 'menu', array( 'type' => 'list' )
+ );
+
+ $cases[] = array( '<script></script>',
+ 'script', array( 'type' => 'text/javascript' )
+ );
+
+ $cases[] = array( '<style></style>',
+ 'style', array( 'media' => 'all' )
+ );
+ $cases[] = array( '<style></style>',
+ 'style', array( 'type' => 'text/css' )
+ );
+
+ $cases[] = array( '<textarea></textarea>',
+ 'textarea', array( 'wrap' => 'soft' )
+ );
+
+ ### SPECIFIC CASES
+
+ # <link type="text/css">
+ $cases[] = array( '<link>',
+ 'link', array( 'type' => 'text/css' )
+ );
+
+ # <input> specific handling
+ $cases[] = array( '<input type=checkbox>',
+ 'input', array( 'type' => 'checkbox', 'value' => 'on' ),
+ 'Default value "on" is stripped of checkboxes',
+ );
+ $cases[] = array( '<input type=radio>',
+ 'input', array( 'type' => 'radio', 'value' => 'on' ),
+ 'Default value "on" is stripped of radio buttons',
+ );
+ $cases[] = array( '<input type=submit value=Submit>',
+ 'input', array( 'type' => 'submit', 'value' => 'Submit' ),
+ 'Default value "Submit" is kept on submit buttons (for possible l10n issues)',
+ );
+ $cases[] = array( '<input type=color>',
+ 'input', array( 'type' => 'color', 'value' => '' ),
+ );
+ $cases[] = array( '<input type=range>',
+ 'input', array( 'type' => 'range', 'value' => '' ),
+ );
+
+ # <button> specific handling
+ # see remarks on http://msdn.microsoft.com/en-us/library/ie/ms535211%28v=vs.85%29.aspx
+ $cases[] = array( '<button type=submit></button>',
+ 'button', array( 'type' => 'submit' ),
+ 'According to standard the default type is "submit". Depending on compatibility mode IE might use "button", instead.',
+ );
+
+ # <select> specifc handling
+ $cases[] = array( '<select multiple></select>',
+ 'select', array( 'size' => '4', 'multiple' => true ),
+ );
+ # .. with numeric value
+ $cases[] = array( '<select multiple></select>',
+ 'select', array( 'size' => 4, 'multiple' => true ),
+ );
+ $cases[] = array( '<select></select>',
+ 'select', array( 'size' => '1', 'multiple' => false ),
+ );
+ # .. with numeric value
+ $cases[] = array( '<select></select>',
+ 'select', array( 'size' => 1, 'multiple' => false ),
+ );
+
+ # Passing an array as value
+ $cases[] = array( '<a class="css-class-one css-class-two"></a>',
+ 'a', array( 'class' => array( 'css-class-one', 'css-class-two' ) ),
+ "dropDefaults accepts values given as an array"
+ );
+
+ # FIXME: doDropDefault should remove defaults given in an array
+ # Expected should be '<a></a>'
+ $cases[] = array( '<a class=""></a>',
+ 'a', array( 'class' => array( '', '' ) ),
+ "dropDefaults accepts values given as an array"
+ );
+
+ # Craft the Html elements
+ $ret = array();
+ foreach ( $cases as $case ) {
+ $ret[] = array(
+ $case[0],
+ $case[1], $case[2],
+ isset( $case[3] ) ? $case[3] : ''
+ );
+ }
+
+ return $ret;
+ }
+
+ public function testFormValidationBlacklist() {
+ $this->assertEmpty(
+ Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 2 ) ),
+ 'Blacklist form validation attributes.'
+ );
+ $this->assertEquals(
+ ' step=any',
+ Html::expandAttributes( array( 'min' => 1, 'max' => 100, 'pattern' => 'abc', 'required' => true, 'step' => 'any' ) ),
+ 'Allow special case "step=any".'
+ );
+ }
+}
diff --git a/tests/phpunit/includes/HttpTest.php b/tests/phpunit/includes/HttpTest.php
new file mode 100644
index 00000000..11d8ed60
--- /dev/null
+++ b/tests/phpunit/includes/HttpTest.php
@@ -0,0 +1,216 @@
+<?php
+/**
+ * @group Broken
+ */
+class HttpTest extends MediaWikiTestCase {
+ /**
+ * @dataProvider cookieDomains
+ * @covers Cookie::validateCookieDomain
+ */
+ public function testValidateCookieDomain( $expected, $domain, $origin = null ) {
+ if ( $origin ) {
+ $ok = Cookie::validateCookieDomain( $domain, $origin );
+ $msg = "$domain against origin $origin";
+ } else {
+ $ok = Cookie::validateCookieDomain( $domain );
+ $msg = "$domain";
+ }
+ $this->assertEquals( $expected, $ok, $msg );
+ }
+
+ public static function cookieDomains() {
+ return array(
+ array( false, "org" ),
+ array( false, ".org" ),
+ array( true, "wikipedia.org" ),
+ array( true, ".wikipedia.org" ),
+ array( false, "co.uk" ),
+ array( false, ".co.uk" ),
+ array( false, "gov.uk" ),
+ array( false, ".gov.uk" ),
+ array( true, "supermarket.uk" ),
+ array( false, "uk" ),
+ array( false, ".uk" ),
+ array( false, "127.0.0." ),
+ array( false, "127." ),
+ array( false, "127.0.0.1." ),
+ array( true, "127.0.0.1" ),
+ array( false, "333.0.0.1" ),
+ array( true, "example.com" ),
+ array( false, "example.com." ),
+ array( true, ".example.com" ),
+
+ array( true, ".example.com", "www.example.com" ),
+ array( false, "example.com", "www.example.com" ),
+ array( true, "127.0.0.1", "127.0.0.1" ),
+ array( false, "127.0.0.1", "localhost" ),
+ );
+ }
+
+ /**
+ * Test Http::isValidURI()
+ * @bug 27854 : Http::isValidURI is too lax
+ * @dataProvider provideURI
+ * @covers Http::isValidURI
+ */
+ public function testIsValidUri( $expect, $URI, $message = '' ) {
+ $this->assertEquals(
+ $expect,
+ (bool)Http::isValidURI( $URI ),
+ $message
+ );
+ }
+
+ /**
+ * Feeds URI to test a long regular expression in Http::isValidURI
+ */
+ public static function provideURI() {
+ /** Format: 'boolean expectation', 'URI to test', 'Optional message' */
+ return array(
+ array( false, '¿non sens before!! http://a', 'Allow anything before URI' ),
+
+ # (http|https) - only two schemes allowed
+ array( true, 'http://www.example.org/' ),
+ array( true, 'https://www.example.org/' ),
+ array( true, 'http://www.example.org', 'URI without directory' ),
+ array( true, 'http://a', 'Short name' ),
+ array( true, 'http://étoile', 'Allow UTF-8 in hostname' ), # 'étoile' is french for 'star'
+ array( false, '\\host\directory', 'CIFS share' ),
+ array( false, 'gopher://host/dir', 'Reject gopher scheme' ),
+ array( false, 'telnet://host', 'Reject telnet scheme' ),
+
+ # :\/\/ - double slashes
+ array( false, 'http//example.org', 'Reject missing colon in protocol' ),
+ array( false, 'http:/example.org', 'Reject missing slash in protocol' ),
+ array( false, 'http:example.org', 'Must have two slashes' ),
+ # Following fail since hostname can be made of anything
+ array( false, 'http:///example.org', 'Must have exactly two slashes, not three' ),
+
+ # (\w+:{0,1}\w*@)? - optional user:pass
+ array( true, 'http://user@host', 'Username provided' ),
+ array( true, 'http://user:@host', 'Username provided, no password' ),
+ array( true, 'http://user:pass@host', 'Username and password provided' ),
+
+ # (\S+) - host part is made of anything not whitespaces
+ array( false, 'http://!"èèè¿¿¿~~\'', 'hostname is made of any non whitespace' ),
+ array( false, 'http://exam:ple.org/', 'hostname can not use colons!' ),
+
+ # (:[0-9]+)? - port number
+ array( true, 'http://example.org:80/' ),
+ array( true, 'https://example.org:80/' ),
+ array( true, 'http://example.org:443/' ),
+ array( true, 'https://example.org:443/' ),
+
+ # Part after the hostname is / or / with something else
+ array( true, 'http://example/#' ),
+ array( true, 'http://example/!' ),
+ array( true, 'http://example/:' ),
+ array( true, 'http://example/.' ),
+ array( true, 'http://example/?' ),
+ array( true, 'http://example/+' ),
+ array( true, 'http://example/=' ),
+ array( true, 'http://example/&' ),
+ array( true, 'http://example/%' ),
+ array( true, 'http://example/@' ),
+ array( true, 'http://example/-' ),
+ array( true, 'http://example//' ),
+ array( true, 'http://example/&' ),
+
+ # Fragment
+ array( true, 'http://exam#ple.org', ), # This one is valid, really!
+ array( true, 'http://example.org:80#anchor' ),
+ array( true, 'http://example.org/?id#anchor' ),
+ array( true, 'http://example.org/?#anchor' ),
+
+ array( false, 'http://a ¿non !!sens after', 'Allow anything after URI' ),
+ );
+ }
+
+ /**
+ * 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).
+ */
+ public function testRelativeRedirections() {
+ $h = MWHttpRequestTester::factory( '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 derived from the MWHttpRequest factory function but
+ // returns appropriate tester class here
+ public static function factory( $url, $options = null ) {
+ if ( !Http::$httpEngine ) {
+ Http::$httpEngine = function_exists( 'curl_init' ) ? 'curl' : 'php';
+ } elseif ( Http::$httpEngine == 'curl' && !function_exists( 'curl_init' ) ) {
+ throw new MWException( __METHOD__ . ': curl (http://php.net/curl) is not installed, but' .
+ 'Http::$httpEngine is set to "curl"' );
+ }
+
+ switch ( Http::$httpEngine ) {
+ case 'curl':
+ return new CurlHttpRequestTester( $url, $options );
+ case 'php':
+ if ( !wfIniGetBool( 'allow_url_fopen' ) ) {
+ throw new MWException( __METHOD__ . ': allow_url_fopen needs to be enabled for pure PHP' .
+ ' http requests to work. If possible, curl should be used instead. See http://php.net/curl.' );
+ }
+
+ return new PhpHttpRequestTester( $url, $options );
+ default:
+ }
+ }
+}
+
+class CurlHttpRequestTester extends CurlHttpRequest {
+ function setRespHeaders( $name, $value ) {
+ $this->respHeaders[$name] = $value;
+ }
+}
+
+class PhpHttpRequestTester extends PhpHttpRequest {
+ function setRespHeaders( $name, $value ) {
+ $this->respHeaders[$name] = $value;
+ }
+}
diff --git a/tests/phpunit/includes/IPTest.php b/tests/phpunit/includes/IPTest.php
new file mode 100644
index 00000000..c074eea6
--- /dev/null
+++ b/tests/phpunit/includes/IPTest.php
@@ -0,0 +1,595 @@
+<?php
+/**
+ * Tests for IP validity functions.
+ *
+ * Ported from /t/inc/IP.t by avar.
+ *
+ * @group IP
+ * @todo Test methods in this call should be split into a method and a
+ * dataprovider.
+ */
+
+class IPTest extends MediaWikiTestCase {
+ /**
+ * not sure it should be tested with boolean false. hashar 20100924
+ * @covers IP::isIPAddress
+ */
+ public function testisIPAddress() {
+ $this->assertFalse( IP::isIPAddress( false ), 'Boolean false is not an IP' );
+ $this->assertFalse( IP::isIPAddress( true ), 'Boolean true is not an IP' );
+ $this->assertFalse( IP::isIPAddress( "" ), 'Empty string is not an IP' );
+ $this->assertFalse( IP::isIPAddress( 'abc' ), 'Garbage IP string' );
+ $this->assertFalse( IP::isIPAddress( ':' ), 'Single ":" is not an IP' );
+ $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::1' ), 'IPv6 with a double :: occurrence' );
+ $this->assertFalse( IP::isIPAddress( '2001:0DB8::A:1::' ), 'IPv6 with a double :: occurrence, last at end' );
+ $this->assertFalse( IP::isIPAddress( '::2001:0DB8::5:1' ), 'IPv6 with a double :: occurrence, firt at beginning' );
+ $this->assertFalse( IP::isIPAddress( '124.24.52' ), 'IPv4 not enough quads' );
+ $this->assertFalse( IP::isIPAddress( '24.324.52.13' ), 'IPv4 out of range' );
+ $this->assertFalse( IP::isIPAddress( '.24.52.13' ), 'IPv4 starts with period' );
+ $this->assertFalse( IP::isIPAddress( 'fc:100:300' ), 'IPv6 with only 3 words' );
+
+ $this->assertTrue( IP::isIPAddress( '::' ), 'RFC 4291 IPv6 Unspecified Address' );
+ $this->assertTrue( IP::isIPAddress( '::1' ), 'RFC 4291 IPv6 Loopback Address' );
+ $this->assertTrue( IP::isIPAddress( '74.24.52.13/20', 'IPv4 range' ) );
+ $this->assertTrue( IP::isIPAddress( 'fc:100:a:d:1:e:ac:0/24' ), 'IPv6 range' );
+ $this->assertTrue( IP::isIPAddress( 'fc::100:a:d:1:e:ac/96' ), 'IPv6 range with "::"' );
+
+ $validIPs = array( 'fc:100::', 'fc:100:a:d:1:e:ac::', 'fc::100', '::fc:100:a:d:1:e:ac',
+ '::fc', 'fc::100:a:d:1:e:ac', 'fc:100:a:d:1:e:ac:0', '124.24.52.13', '1.24.52.13' );
+ foreach ( $validIPs as $ip ) {
+ $this->assertTrue( IP::isIPAddress( $ip ), "$ip is a valid IP address" );
+ }
+ }
+
+ /**
+ * @covers IP::isIPv6
+ */
+ public function testisIPv6() {
+ $this->assertFalse( IP::isIPv6( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+ $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' ) );
+ $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 "::" 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' ) );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e' ), 'IPv6 with "::" and 6 words' );
+ $this->assertTrue( IP::isIPv6( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+ $this->assertTrue( IP::isIPv6( '2001::df' ), 'IPv6 with "::" and 2 words' );
+ $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+ $this->assertTrue( IP::isIPv6( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+
+ $this->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->assertTrue( IP::isIPv6( 'fc:100:a:d:1:e:ac:0' ) );
+ }
+
+ /**
+ * @covers IP::isIPv4
+ */
+ public function testisIPv4() {
+ $this->assertFalse( IP::isIPv4( false ), 'Boolean false is not an IP' );
+ $this->assertFalse( IP::isIPv4( true ), 'Boolean true is not an IP' );
+ $this->assertFalse( IP::isIPv4( "" ), 'Empty string is not an IP' );
+ $this->assertFalse( IP::isIPv4( 'abc' ) );
+ $this->assertFalse( IP::isIPv4( ':' ) );
+ $this->assertFalse( IP::isIPv4( '124.24.52' ), 'IPv4 not enough quads' );
+ $this->assertFalse( IP::isIPv4( '24.324.52.13' ), 'IPv4 out of range' );
+ $this->assertFalse( IP::isIPv4( '.24.52.13' ), 'IPv4 starts with period' );
+
+ $this->assertTrue( IP::isIPv4( '124.24.52.13' ) );
+ $this->assertTrue( IP::isIPv4( '1.24.52.13' ) );
+ $this->assertTrue( IP::isIPv4( '74.24.52.13/20', 'IPv4 range' ) );
+ }
+
+ /**
+ * @covers IP::isValid
+ */
+ public function testValidIPs() {
+ foreach ( range( 0, 255 ) as $i ) {
+ $a = sprintf( "%03d", $i );
+ $b = sprintf( "%02d", $i );
+ $c = sprintf( "%01d", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f.$f.$f.$f";
+ $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv4 address" );
+ }
+ }
+ foreach ( range( 0x0, 0xFFFF, 0xF ) as $i ) {
+ $a = sprintf( "%04x", $i );
+ $b = sprintf( "%03x", $i );
+ $c = sprintf( "%02x", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+ $this->assertTrue( IP::isValid( $ip ), "$ip is a valid IPv6 address" );
+ }
+ }
+ // test with some abbreviations
+ $this->assertFalse( IP::isValid( ':fc:100::' ), 'IPv6 starting with lone ":"' );
+ $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' );
+ $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df' ), 'IPv6 with "::" and 5 words' );
+ $this->assertTrue( IP::isValid( '2001:5c0:1400:a::df:2' ), 'IPv6 with "::" and 6 words' );
+ $this->assertTrue( IP::isValid( 'fc::100:a:d:1' ), 'IPv6 with "::" and 5 words' );
+ $this->assertTrue( IP::isValid( 'fc::100:a:d:1:e:ac' ), 'IPv6 with "::" and 7 words' );
+
+ $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 "::"' );
+ }
+
+ /**
+ * @covers IP::isValid
+ */
+ public function testInvalidIPs() {
+ // Out of range...
+ foreach ( range( 256, 999 ) as $i ) {
+ $a = sprintf( "%03d", $i );
+ $b = sprintf( "%02d", $i );
+ $c = sprintf( "%01d", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f.$f.$f.$f";
+ $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv4 address" );
+ }
+ }
+ foreach ( range( 'g', 'z' ) as $i ) {
+ $a = sprintf( "%04s", $i );
+ $b = sprintf( "%03s", $i );
+ $c = sprintf( "%02s", $i );
+ foreach ( array_unique( array( $a, $b, $c ) ) as $f ) {
+ $ip = "$f:$f:$f:$f:$f:$f:$f:$f";
+ $this->assertFalse( IP::isValid( $ip ), "$ip is not a valid IPv6 address" );
+ }
+ }
+ // Have CIDR
+ $ipCIDRs = array(
+ '212.35.31.121/32',
+ '212.35.31.121/18',
+ '212.35.31.121/24',
+ '::ff:d:321:5/96',
+ 'ff::d3:321:5/116',
+ 'c:ff:12:1:ea:d:321:5/120',
+ );
+ foreach ( $ipCIDRs as $i ) {
+ $this->assertFalse( IP::isValid( $i ),
+ "$i is an invalid IP address because it is a block" );
+ }
+ // Incomplete/garbage
+ $invalid = array(
+ 'www.xn--var-xla.net',
+ '216.17.184.G',
+ '216.17.184.1.',
+ '216.17.184',
+ '216.17.184.',
+ '256.17.184.1'
+ );
+ foreach ( $invalid as $i ) {
+ $this->assertFalse( IP::isValid( $i ), "$i is an invalid IP address" );
+ }
+ }
+
+ /**
+ * @covers IP::isValidBlock
+ */
+ public function testValidBlocks() {
+ $valid = array(
+ '116.17.184.5/32',
+ '0.17.184.5/30',
+ '16.17.184.1/24',
+ '30.242.52.14/1',
+ '10.232.52.13/8',
+ '30.242.52.14/0',
+ '::e:f:2001/96',
+ '::c:f:2001/128',
+ '::10:f:2001/70',
+ '::fe:f:2001/1',
+ '::6d:f:2001/8',
+ '::fe:f:2001/0',
+ );
+ foreach ( $valid as $i ) {
+ $this->assertTrue( IP::isValidBlock( $i ), "$i is a valid IP block" );
+ }
+ }
+
+ /**
+ * @covers IP::isValidBlock
+ */
+ public function testInvalidBlocks() {
+ $invalid = array(
+ '116.17.184.5/33',
+ '0.17.184.5/130',
+ '16.17.184.1/-1',
+ '10.232.52.13/*',
+ '7.232.52.13/ab',
+ '11.232.52.13/',
+ '::e:f:2001/129',
+ '::c:f:2001/228',
+ '::10:f:2001/-1',
+ '::6d:f:2001/*',
+ '::86:f:2001/ab',
+ '::23:f:2001/',
+ );
+ foreach ( $invalid as $i ) {
+ $this->assertFalse( IP::isValidBlock( $i ), "$i is not a valid IP block" );
+ }
+ }
+
+ /**
+ * Improve IP::sanitizeIP() code coverage
+ * @todo Most probably incomplete
+ */
+ public function testSanitizeIP() {
+ $this->assertNull( IP::sanitizeIP( '' ) );
+ $this->assertNull( IP::sanitizeIP( ' ' ) );
+ }
+
+ /**
+ * @covers IP::toUnsigned
+ * @dataProvider provideToUnsigned
+ */
+ public function testToUnsigned( $expected, $input ) {
+ $result = IP::toUnsigned( $input );
+ $this->assertTrue( $result === false || is_string( $result ) || is_int( $result ) );
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * Provider for IP::testToUnsigned()
+ */
+ public static function provideToUnsigned() {
+ return array(
+ array( 1, '0.0.0.1' ),
+ array( 16909060, '1.2.3.4' ),
+ array( 2130706433, '127.0.0.1' ),
+ array( '2147483648', '128.0.0.0' ),
+ array( '3735931646', '222.173.202.254' ),
+ array( pow( 2, 32 ) - 1, '255.255.255.255' ),
+ array( false, 'IN.VA.LI.D' ),
+ array( 1, '::1' ),
+ array( '42540766452641154071740215577757643572', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ),
+ array( '42540766452641154071740215577757643572', '2001:db8:85a3::8a2e:0370:7334' ),
+ array( false, 'IN:VA::LI:D' ),
+ array( false, ':::1' )
+ );
+ }
+
+ /**
+ * @covers IP::toHex
+ * @dataProvider provideToHex
+ */
+ public function testToHex( $expected, $input ) {
+ $result = IP::toHex( $input );
+ $this->assertTrue( $result === false || is_string( $result ) );
+ $this->assertEquals( $expected, $result );
+ }
+
+ /**
+ * Provider for IP::testToHex()
+ */
+ public static function provideToHex() {
+ return array(
+ array( '00000001', '0.0.0.1' ),
+ array( '01020304', '1.2.3.4' ),
+ array( '7F000001', '127.0.0.1' ),
+ array( '80000000', '128.0.0.0' ),
+ array( 'DEADCAFE', '222.173.202.254' ),
+ array( 'FFFFFFFF', '255.255.255.255' ),
+ array( false, 'IN.VA.LI.D' ),
+ array( 'v6-00000000000000000000000000000001', '::1' ),
+ array( 'v6-20010DB885A3000000008A2E03707334', '2001:0db8:85a3:0000:0000:8a2e:0370:7334' ),
+ array( 'v6-20010DB885A3000000008A2E03707334', '2001:db8:85a3::8a2e:0370:7334' ),
+ array( false, 'IN:VA::LI:D' ),
+ array( false, ':::1' )
+ );
+ }
+
+ /**
+ * @covers IP::isPublic
+ */
+ public function testPrivateIPs() {
+ $private = array( 'fc00::3', 'fc00::ff', '::1', '10.0.0.1', '172.16.0.1', '192.168.0.1' );
+ 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', '00FC::' );
+ foreach ( $public as $p ) {
+ $this->assertTrue( IP::isPublic( $p ), "$p is a public IP address" );
+ }
+ }
+
+ // Private wrapper used to test CIDR Parsing.
+ private function assertFalseCIDR( $CIDR, $msg = '' ) {
+ $ff = array( false, false );
+ $this->assertEquals( $ff, IP::parseCIDR( $CIDR ), $msg );
+ }
+
+ // Private wrapper to test network shifting using only dot notation
+ private function assertNet( $expected, $CIDR ) {
+ $parse = IP::parseCIDR( $CIDR );
+ $this->assertEquals( $expected, long2ip( $parse[0] ), "network shifting $CIDR" );
+ }
+
+ /**
+ * @covers IP::hexToQuad
+ */
+ public function testHexToQuad() {
+ $this->assertEquals( '0.0.0.1', IP::hexToQuad( '00000001' ) );
+ $this->assertEquals( '255.0.0.0', IP::hexToQuad( 'FF000000' ) );
+ $this->assertEquals( '255.255.255.255', IP::hexToQuad( 'FFFFFFFF' ) );
+ $this->assertEquals( '10.188.222.255', IP::hexToQuad( '0ABCDEFF' ) );
+ // hex not left-padded...
+ $this->assertEquals( '0.0.0.0', IP::hexToQuad( '0' ) );
+ $this->assertEquals( '0.0.0.1', IP::hexToQuad( '1' ) );
+ $this->assertEquals( '0.0.0.255', IP::hexToQuad( 'FF' ) );
+ $this->assertEquals( '0.0.255.0', IP::hexToQuad( 'FF00' ) );
+ }
+
+ /**
+ * @covers IP::hexToOctet
+ */
+ public function testHexToOctet() {
+ $this->assertEquals( '0:0:0:0:0:0:0:1',
+ IP::hexToOctet( '00000000000000000000000000000001' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FF:3',
+ IP::hexToOctet( '00000000000000000000000000FF0003' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FF00:6',
+ IP::hexToOctet( '000000000000000000000000FF000006' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FCCF:FAFF',
+ IP::hexToOctet( '000000000000000000000000FCCFFAFF' ) );
+ $this->assertEquals( 'FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+ IP::hexToOctet( 'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' ) );
+ // hex not left-padded...
+ $this->assertEquals( '0:0:0:0:0:0:0:0', IP::hexToOctet( '0' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:1', IP::hexToOctet( '1' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:FF', IP::hexToOctet( 'FF' ) );
+ $this->assertEquals( '0:0:0:0:0:0:0:FFD0', IP::hexToOctet( 'FFD0' ) );
+ $this->assertEquals( '0:0:0:0:0:0:FA00:0', IP::hexToOctet( 'FA000000' ) );
+ $this->assertEquals( '0:0:0:0:0:0: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
+ */
+ public function testCIDRParsing() {
+ $this->assertFalseCIDR( '192.0.2.0', "missing mask" );
+ $this->assertFalseCIDR( '192.0.2.0/', "missing bitmask" );
+
+ // Verify if statement
+ $this->assertFalseCIDR( '256.0.0.0/32', "invalid net" );
+ $this->assertFalseCIDR( '192.0.2.0/AA', "mask not numeric" );
+ $this->assertFalseCIDR( '192.0.2.0/-1', "mask < 0" );
+ $this->assertFalseCIDR( '192.0.2.0/33', "mask > 32" );
+
+ // Check internal logic
+ # 0 mask always result in array(0,0)
+ $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '192.0.0.2/0' ) );
+ $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '0.0.0.0/0' ) );
+ $this->assertEquals( array( 0, 0 ), IP::parseCIDR( '255.255.255.255/0' ) );
+
+ // @todo FIXME: Add more tests.
+
+ # This part test network shifting
+ $this->assertNet( '192.0.0.0', '192.0.0.2/24' );
+ $this->assertNet( '192.168.5.0', '192.168.5.13/24' );
+ $this->assertNet( '10.0.0.160', '10.0.0.161/28' );
+ $this->assertNet( '10.0.0.0', '10.0.0.3/28' );
+ $this->assertNet( '10.0.0.0', '10.0.0.3/30' );
+ $this->assertNet( '10.0.0.4', '10.0.0.4/30' );
+ $this->assertNet( '172.17.32.0', '172.17.35.48/21' );
+ $this->assertNet( '10.128.0.0', '10.135.0.0/9' );
+ $this->assertNet( '134.0.0.0', '134.0.5.1/8' );
+ }
+
+ /**
+ * @covers IP::canonicalize
+ */
+ public function testIPCanonicalizeOnValidIp() {
+ $this->assertEquals( '192.0.2.152', IP::canonicalize( '192.0.2.152' ),
+ 'Canonicalization of a valid IP returns it unchanged' );
+ }
+
+ /**
+ * @covers IP::canonicalize
+ */
+ public function testIPCanonicalizeMappedAddress() {
+ $this->assertEquals(
+ '192.0.2.152',
+ IP::canonicalize( '::ffff:192.0.2.152' )
+ );
+ $this->assertEquals(
+ '192.0.2.152',
+ IP::canonicalize( '::192.0.2.152' )
+ );
+ }
+
+ /**
+ * Issues there are most probably from IP::toHex() or IP::parseRange()
+ * @covers IP::isInRange
+ * @dataProvider provideIPsAndRanges
+ */
+ public function testIPIsInRange( $expected, $addr, $range, $message = '' ) {
+ $this->assertEquals(
+ $expected,
+ IP::isInRange( $addr, $range ),
+ $message
+ );
+ }
+
+ /** Provider for testIPIsInRange() */
+ public static function provideIPsAndRanges() {
+ # Format: (expected boolean, address, range, optional message)
+ return array(
+ # IPv4
+ array( true, '192.0.2.0', '192.0.2.0/24', 'Network address' ),
+ array( true, '192.0.2.77', '192.0.2.0/24', 'Simple address' ),
+ array( true, '192.0.2.255', '192.0.2.0/24', 'Broadcast address' ),
+
+ array( false, '0.0.0.0', '192.0.2.0/24' ),
+ array( false, '255.255.255', '192.0.2.0/24' ),
+
+ # IPv6
+ array( false, '::1', '2001:DB8::/32' ),
+ array( false, '::', '2001:DB8::/32' ),
+ array( false, 'FE80::1', '2001:DB8::/32' ),
+
+ array( true, '2001:DB8::', '2001:DB8::/32' ),
+ array( true, '2001:0DB8::', '2001:DB8::/32' ),
+ array( true, '2001:DB8::1', '2001:DB8::/32' ),
+ array( true, '2001:0DB8::1', '2001:DB8::/32' ),
+ array( true, '2001:0DB8:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF',
+ '2001:DB8::/32' ),
+
+ array( false, '2001:0DB8:F::', '2001:DB8::/96' ),
+ );
+ }
+
+ /**
+ * Test for IP::splitHostAndPort().
+ * @dataProvider provideSplitHostAndPort
+ */
+ public function testSplitHostAndPort( $expected, $input, $description ) {
+ $this->assertEquals( $expected, IP::splitHostAndPort( $input ), $description );
+ }
+
+ /**
+ * Provider for IP::splitHostAndPort()
+ */
+ public static function provideSplitHostAndPort() {
+ return array(
+ array( false, '[', 'Unclosed square bracket' ),
+ array( false, '[::', 'Unclosed square bracket 2' ),
+ array( array( '::', false ), '::', 'Bare IPv6 0' ),
+ array( array( '::1', false ), '::1', 'Bare IPv6 1' ),
+ array( array( '::', false ), '[::]', 'Bracketed IPv6 0' ),
+ array( array( '::1', false ), '[::1]', 'Bracketed IPv6 1' ),
+ array( array( '::1', 80 ), '[::1]:80', 'Bracketed IPv6 with port' ),
+ array( false, '::x', 'Double colon but no IPv6' ),
+ array( array( 'x', 80 ), 'x:80', 'Hostname and port' ),
+ array( false, 'x:x', 'Hostname and invalid port' ),
+ array( array( 'x', false ), 'x', 'Plain hostname' )
+ );
+ }
+
+ /**
+ * Test for IP::combineHostAndPort()
+ * @dataProvider provideCombineHostAndPort
+ */
+ public function testCombineHostAndPort( $expected, $input, $description ) {
+ list( $host, $port, $defaultPort ) = $input;
+ $this->assertEquals(
+ $expected,
+ IP::combineHostAndPort( $host, $port, $defaultPort ),
+ $description );
+ }
+
+ /**
+ * Provider for IP::combineHostAndPort()
+ */
+ public static function provideCombineHostAndPort() {
+ return array(
+ array( '[::1]', array( '::1', 2, 2 ), 'IPv6 default port' ),
+ array( '[::1]:2', array( '::1', 2, 3 ), 'IPv6 non-default port' ),
+ array( 'x', array( 'x', 2, 2 ), 'Normal default port' ),
+ array( 'x:2', array( 'x', 2, 3 ), 'Normal non-default port' ),
+ );
+ }
+
+ /**
+ * Test for IP::sanitizeRange()
+ * @dataProvider provideIPCIDRs
+ */
+ public function testSanitizeRange( $input, $expected, $description ) {
+ $this->assertEquals( $expected, IP::sanitizeRange( $input ), $description );
+ }
+
+ /**
+ * Provider for IP::testSanitizeRange()
+ */
+ public static function provideIPCIDRs() {
+ return array(
+ array( '35.56.31.252/16', '35.56.0.0/16', 'IPv4 range' ),
+ array( '135.16.21.252/24', '135.16.21.0/24', 'IPv4 range' ),
+ array( '5.36.71.252/32', '5.36.71.252/32', 'IPv4 silly range' ),
+ array( '5.36.71.252', '5.36.71.252', 'IPv4 non-range' ),
+ array( '0:1:2:3:4:c5:f6:7/96', '0:1:2:3:4:C5:0:0/96', 'IPv6 range' ),
+ array( '0:1:2:3:4:5:6:7/120', '0:1:2:3:4:5:6:0/120', 'IPv6 range' ),
+ array( '0:e1:2:3:4:5:e6:7/128', '0:E1:2:3:4:5:E6:7/128', 'IPv6 silly range' ),
+ array( '0:c1:A2:3:4:5:c6:7', '0:C1:A2:3:4:5:C6:7', 'IPv6 non range' ),
+ );
+ }
+
+ /**
+ * Test for IP::prettifyIP()
+ * @dataProvider provideIPsToPrettify
+ */
+ public function testPrettifyIP( $ip, $prettified ) {
+ $this->assertEquals( $prettified, IP::prettifyIP( $ip ), "Prettify of $ip" );
+ }
+
+ /**
+ * Provider for IP::testPrettifyIP()
+ */
+ public static function provideIPsToPrettify() {
+ return array(
+ array( '0:0:0:0:0:0:0:0', '::' ),
+ array( '0:0:0::0:0:0', '::' ),
+ array( '0:0:0:1:0:0:0:0', '0:0:0:1::' ),
+ array( '0:0::f', '::f' ),
+ array( '0::0:0:0:33:fef:b', '::33:fef:b' ),
+ array( '3f:535:0:0:0:0:e:fbb', '3f:535::e:fbb' ),
+ array( '0:0:fef:0:0:0:e:fbb', '0:0:fef::e:fbb' ),
+ array( 'abbc:2004::0:0:0:0', 'abbc:2004::' ),
+ array( 'cebc:2004:f:0:0:0:0:0', 'cebc:2004:f::' ),
+ array( '0:0:0:0:0:0:0:0/16', '::/16' ),
+ array( '0:0:0::0:0:0/64', '::/64' ),
+ array( '0:0::f/52', '::f/52' ),
+ array( '::0:0:33:fef:b/52', '::33:fef:b/52' ),
+ array( '3f:535:0:0:0:0:e:fbb/48', '3f:535::e:fbb/48' ),
+ array( '0:0:fef:0:0:0:e:fbb/96', '0:0:fef::e:fbb/96' ),
+ array( 'abbc:2004:0:0::0:0/40', 'abbc:2004::/40' ),
+ array( 'aebc:2004:f:0:0:0:0:0/80', 'aebc:2004:f::/80' ),
+ );
+ }
+}
diff --git a/tests/phpunit/includes/LanguageConverterTest.php b/tests/phpunit/includes/LanguageConverterTest.php
new file mode 100644
index 00000000..7c2134b9
--- /dev/null
+++ b/tests/phpunit/includes/LanguageConverterTest.php
@@ -0,0 +1,148 @@
+<?php
+
+class LanguageConverterTest extends MediaWikiLangTestCase {
+ protected $lang = null;
+ protected $lc = null;
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgContLang' => Language::factory( 'tg' ),
+ 'wgLanguageCode' => 'tg',
+ 'wgDefaultLanguageVariant' => false,
+ 'wgMemc' => new EmptyBagOStuff,
+ 'wgRequest' => new FauxRequest( array() ),
+ 'wgUser' => new User,
+ ) );
+
+ $this->lang = new LanguageToTest();
+ $this->lc = new TestConverter(
+ $this->lang, 'tg',
+ array( 'tg', 'tg-latn' )
+ );
+ }
+
+ protected function tearDown() {
+ unset( $this->lc );
+ unset( $this->lang );
+
+ parent::tearDown();
+ }
+
+ public function testGetPreferredVariantDefaults() {
+ $this->assertEquals( 'tg', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantHeaders() {
+ global $wgRequest;
+ $wgRequest->setHeader( 'Accept-Language', 'tg-latn' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantHeaderWeight() {
+ global $wgRequest;
+ $wgRequest->setHeader( 'Accept-Language', 'tg;q=1' );
+
+ $this->assertEquals( 'tg', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantHeaderWeight2() {
+ global $wgRequest;
+ $wgRequest->setHeader( 'Accept-Language', 'tg-latn;q=1' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantHeaderMulti() {
+ global $wgRequest;
+ $wgRequest->setHeader( 'Accept-Language', 'en, tg-latn;q=1' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantUserOption() {
+ global $wgUser;
+
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant', 'tg-latn' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantUserOptionForForeignLanguage() {
+ global $wgContLang, $wgUser;
+
+ $wgContLang = Language::factory( 'en' );
+ $wgUser = new User;
+ $wgUser->load(); // from 'defaults'
+ $wgUser->mId = 1;
+ $wgUser->mDataLoaded = true;
+ $wgUser->mOptionsLoaded = true;
+ $wgUser->setOption( 'variant-tg', 'tg-latn' );
+
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantHeaderUserVsUrl() {
+ global $wgContLang, $wgRequest, $wgUser;
+
+ $wgContLang = Language::factory( 'tg-latn' );
+ $wgRequest->setVal( 'variant', 'tg' );
+ $wgUser = User::newFromId( "admin" );
+ $wgUser->setId( 1 );
+ $wgUser->mFrom = 'defaults';
+ $wgUser->mOptionsLoaded = true;
+ // The user's data is ignored because the variant is set in the URL.
+ $wgUser->setOption( 'variant', 'tg-latn' );
+ $this->assertEquals( 'tg', $this->lc->getPreferredVariant() );
+ }
+
+
+ public function testGetPreferredVariantDefaultLanguageVariant() {
+ global $wgDefaultLanguageVariant;
+
+ $wgDefaultLanguageVariant = 'tg-latn';
+ $this->assertEquals( 'tg-latn', $this->lc->getPreferredVariant() );
+ }
+
+ public function testGetPreferredVariantDefaultLanguageVsUrlVariant() {
+ global $wgDefaultLanguageVariant, $wgRequest, $wgContLang;
+
+ $wgContLang = Language::factory( 'tg-latn' );
+ $wgDefaultLanguageVariant = 'tg';
+ $wgRequest->setVal( 'variant', null );
+ $this->assertEquals( 'tg', $this->lc->getPreferredVariant() );
+ }
+}
+
+/**
+ * Test converter (from Tajiki to latin orthography)
+ */
+class TestConverter extends LanguageConverter {
+ private $table = array(
+ 'б' => 'b',
+ 'в' => 'v',
+ 'г' => 'g',
+ );
+
+ function loadDefaultTables() {
+ $this->mTables = array(
+ 'tg-latn' => new ReplacementArray( $this->table ),
+ 'tg' => new ReplacementArray()
+ );
+ }
+}
+
+class LanguageToTest extends Language {
+ function __construct() {
+ parent::__construct();
+ $variants = array( 'tg', 'tg-latn' );
+ $this->mConverter = new TestConverter( $this, 'tg', $variants );
+ }
+}
diff --git a/tests/phpunit/includes/LicensesTest.php b/tests/phpunit/includes/LicensesTest.php
new file mode 100644
index 00000000..478a2ffc
--- /dev/null
+++ b/tests/phpunit/includes/LicensesTest.php
@@ -0,0 +1,22 @@
+<?php
+
+class LicensesTest extends MediaWikiTestCase {
+
+ public function testLicenses() {
+ $str = "
+* Free licenses:
+** GFDL|Debian disagrees
+";
+
+ $lc = new Licenses( array(
+ 'fieldname' => 'FooField',
+ 'type' => 'select',
+ 'section' => 'description',
+ 'id' => 'wpLicense',
+ 'label' => 'A label text', # Note can't test label-message because $wgOut is not defined
+ 'name' => 'AnotherName',
+ 'licenses' => $str,
+ ) );
+ $this->assertThat( $lc, $this->isInstanceOf( 'Licenses' ) );
+ }
+}
diff --git a/tests/phpunit/includes/LinkerTest.php b/tests/phpunit/includes/LinkerTest.php
new file mode 100644
index 00000000..b605f08f
--- /dev/null
+++ b/tests/phpunit/includes/LinkerTest.php
@@ -0,0 +1,71 @@
+<?php
+
+class LinkerTest extends MediaWikiLangTestCase {
+
+ /**
+ * @dataProvider provideCasesForUserLink
+ * @covers Linker::userLink
+ */
+ public function testUserLink( $expected, $userId, $userName, $altUserName = false, $msg = '' ) {
+ $this->setMwGlobals( array(
+ 'wgArticlePath' => '/wiki/$1',
+ 'wgWellFormedXml' => true,
+ ) );
+
+ $this->assertEquals( $expected,
+ Linker::userLink( $userId, $userName, $altUserName, $msg )
+ );
+ }
+
+ public static function provideCasesForUserLink() {
+ # Format:
+ # - expected
+ # - userid
+ # - username
+ # - optional altUserName
+ # - optional message
+ return array(
+
+ ### ANONYMOUS USER ########################################
+ array(
+ '<a href="/wiki/Special:Contributions/JohnDoe" title="Special:Contributions/JohnDoe" class="mw-userlink">JohnDoe</a>',
+ 0, 'JohnDoe', false,
+ ),
+ array(
+ '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">::1</a>',
+ 0, '::1', false,
+ 'Anonymous with pretty IPv6'
+ ),
+ array(
+ '<a href="/wiki/Special:Contributions/0:0:0:0:0:0:0:1" title="Special:Contributions/0:0:0:0:0:0:0:1" class="mw-userlink">::1</a>',
+ 0, '0:0:0:0:0:0:0:1', false,
+ 'Anonymous with almost pretty IPv6'
+ ),
+ array(
+ '<a href="/wiki/Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" title="Special:Contributions/0000:0000:0000:0000:0000:0000:0000:0001" class="mw-userlink">::1</a>',
+ 0, '0000:0000:0000:0000:0000:0000:0000:0001', false,
+ 'Anonymous with full IPv6'
+ ),
+ array(
+ '<a href="/wiki/Special:Contributions/::1" title="Special:Contributions/::1" class="mw-userlink">AlternativeUsername</a>',
+ 0, '::1', 'AlternativeUsername',
+ 'Anonymous with pretty IPv6 and an alternative username'
+ ),
+
+ # IPV4
+ array(
+ '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">127.0.0.1</a>',
+ 0, '127.0.0.1', false,
+ 'Anonymous with IPv4'
+ ),
+ array(
+ '<a href="/wiki/Special:Contributions/127.0.0.1" title="Special:Contributions/127.0.0.1" class="mw-userlink">AlternativeUsername</a>',
+ 0, '127.0.0.1', 'AlternativeUsername',
+ 'Anonymous with IPv4 and an alternative username'
+ ),
+
+ ### Regular user ##########################################
+ # TODO!
+ );
+ }
+}
diff --git a/tests/phpunit/includes/LinksUpdateTest.php b/tests/phpunit/includes/LinksUpdateTest.php
new file mode 100644
index 00000000..5ade250e
--- /dev/null
+++ b/tests/phpunit/includes/LinksUpdateTest.php
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ *
+ * @group Database
+ * ^--- make sure temporary tables are used.
+ */
+class LinksUpdateTest extends MediaWikiTestCase {
+
+ function __construct( $name = null, array $data = array(), $dataName = '' ) {
+ parent::__construct( $name, $data, $dataName );
+
+ $this->tablesUsed = array_merge( $this->tablesUsed,
+ array(
+ 'interwiki',
+ 'page_props',
+ 'pagelinks',
+ 'categorylinks',
+ 'langlinks',
+ 'externallinks',
+ 'imagelinks',
+ 'templatelinks',
+ 'iwlinks'
+ )
+ );
+ }
+
+ protected function setUp() {
+ parent::setUp();
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->replace(
+ 'interwiki',
+ array( 'iw_prefix' ),
+ array(
+ 'iw_prefix' => 'linksupdatetest',
+ 'iw_url' => 'http://testing.com/wiki/$1',
+ 'iw_api' => 'http://testing.com/w/api.php',
+ 'iw_local' => 0,
+ 'iw_trans' => 0,
+ 'iw_wikiid' => 'linksupdatetest',
+ )
+ );
+ }
+
+ protected function makeTitleAndParserOutput( $name, $id ) {
+ $t = Title::newFromText( $name );
+ $t->mArticleID = $id; # XXX: this is fugly
+
+ $po = new ParserOutput();
+ $po->setTitleText( $t->getPrefixedText() );
+
+ return array( $t, $po );
+ }
+
+ public function testUpdate_pagelinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addLink( Title::newFromText( "Foo" ) );
+ $po->addLink( Title::newFromText( "Special:Foo" ) ); // special namespace should be ignored
+ $po->addLink( Title::newFromText( "linksupdatetest:Foo" ) ); // interwiki link should be ignored
+ $po->addLink( Title::newFromText( "#Foo" ) ); // hash link should be ignored
+
+ $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array(
+ array( NS_MAIN, 'Foo' ),
+ ) );
+ $this->assertArrayEquals( array(
+ Title::makeTitle( NS_MAIN, 'Foo' ), // newFromText doesn't yield the same internal state....
+ ), $update->getAddedLinks() );
+
+ $po = new ParserOutput();
+ $po->setTitleText( $t->getPrefixedText() );
+
+ $po->addLink( Title::newFromText( "Bar" ) );
+ $po->addLink( Title::newFromText( "Talk:Bar" ) );
+
+ $update = $this->assertLinksUpdate( $t, $po, 'pagelinks', 'pl_namespace, pl_title', 'pl_from = 111', array(
+ array( NS_MAIN, 'Bar' ),
+ array( NS_TALK, 'Bar' ),
+ ) );
+ $this->assertArrayEquals( array(
+ Title::makeTitle( NS_MAIN, 'Bar' ),
+ Title::makeTitle( NS_TALK, 'Bar' ),
+ ), $update->getAddedLinks() );
+ $this->assertArrayEquals( array(
+ Title::makeTitle( NS_MAIN, 'Foo' ),
+ ), $update->getRemovedLinks() );
+ }
+
+ public function testUpdate_externallinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addExternalLink( "http://testing.com/wiki/Foo" );
+
+ $this->assertLinksUpdate( $t, $po, 'externallinks', 'el_to, el_index', 'el_from = 111', array(
+ array( 'http://testing.com/wiki/Foo', 'http://com.testing./wiki/Foo' ),
+ ) );
+ }
+
+ public function testUpdate_categorylinks() {
+ $this->setMwGlobals( 'wgCategoryCollation', 'uppercase' );
+
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addCategory( "Foo", "FOO" );
+
+ $this->assertLinksUpdate( $t, $po, 'categorylinks', 'cl_to, cl_sortkey', 'cl_from = 111', array(
+ array( 'Foo', "FOO\nTESTING" ),
+ ) );
+ }
+
+ public function testUpdate_iwlinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $target = Title::makeTitleSafe( NS_MAIN, "Foo", '', 'linksupdatetest' );
+ $po->addInterwikiLink( $target );
+
+ $this->assertLinksUpdate( $t, $po, 'iwlinks', 'iwl_prefix, iwl_title', 'iwl_from = 111', array(
+ array( 'linksupdatetest', 'Foo' ),
+ ) );
+ }
+
+ public function testUpdate_templatelinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addTemplate( Title::newFromText( "Template:Foo" ), 23, 42 );
+
+ $this->assertLinksUpdate( $t, $po, 'templatelinks', 'tl_namespace, tl_title', 'tl_from = 111', array(
+ array( NS_TEMPLATE, 'Foo' ),
+ ) );
+ }
+
+ public function testUpdate_imagelinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addImage( "Foo.png" );
+
+ $this->assertLinksUpdate( $t, $po, 'imagelinks', 'il_to', 'il_from = 111', array(
+ array( 'Foo.png' ),
+ ) );
+ }
+
+ public function testUpdate_langlinks() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->addLanguageLink( Title::newFromText( "en:Foo" )->getFullText() );
+
+ $this->assertLinksUpdate( $t, $po, 'langlinks', 'll_lang, ll_title', 'll_from = 111', array(
+ array( 'En', 'Foo' ),
+ ) );
+ }
+
+ public function testUpdate_page_props() {
+ list( $t, $po ) = $this->makeTitleAndParserOutput( "Testing", 111 );
+
+ $po->setProperty( "foo", "bar" );
+
+ $this->assertLinksUpdate( $t, $po, 'page_props', 'pp_propname, pp_value', 'pp_page = 111', array(
+ array( 'foo', 'bar' ),
+ ) );
+ }
+
+ // @todo test recursive, too!
+
+ protected function assertLinksUpdate( Title $title, ParserOutput $parserOutput, $table, $fields, $condition, array $expectedRows ) {
+ $update = new LinksUpdate( $title, $parserOutput );
+
+ //NOTE: make sure LinksUpdate does not generate warnings when called inside a transaction.
+ $update->beginTransaction();
+ $update->doUpdate();
+ $update->commitTransaction();
+
+ $this->assertSelect( $table, $fields, $condition, $expectedRows );
+ return $update;
+ }
+}
diff --git a/tests/phpunit/includes/LocalFileTest.php b/tests/phpunit/includes/LocalFileTest.php
new file mode 100644
index 00000000..2501c783
--- /dev/null
+++ b/tests/phpunit/includes/LocalFileTest.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * These tests should work regardless of $wgCapitalLinks
+ * @group Database
+ */
+
+class LocalFileTest extends MediaWikiTestCase {
+
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( 'wgCapitalLinks', true );
+
+ $info = array(
+ '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 );
+ $this->repo_lc = new LocalRepo( array( 'initialCapital' => false ) + $info );
+ $this->file_hl0 = $this->repo_hl0->newFile( 'test!' );
+ $this->file_hl2 = $this->repo_hl2->newFile( 'test!' );
+ $this->file_lc = $this->repo_lc->newFile( 'test!' );
+ }
+
+ public function testGetHashPath() {
+ $this->assertEquals( '', $this->file_hl0->getHashPath() );
+ $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() );
+ $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() );
+ }
+
+ public function testGetRel() {
+ $this->assertEquals( 'Test!', $this->file_hl0->getRel() );
+ $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() );
+ $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() );
+ }
+
+ public function testGetUrlRel() {
+ $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() );
+ $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() );
+ $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() );
+ }
+
+ public function testGetArchivePath() {
+ $this->assertEquals( 'mwstore://local-backend/test-public/archive', $this->file_hl0->getArchivePath() );
+ $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2', $this->file_hl2->getArchivePath() );
+ $this->assertEquals( 'mwstore://local-backend/test-public/archive/!', $this->file_hl0->getArchivePath( '!' ) );
+ $this->assertEquals( 'mwstore://local-backend/test-public/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) );
+ }
+
+ public function testGetThumbPath() {
+ $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!', $this->file_hl0->getThumbPath() );
+ $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!', $this->file_hl2->getThumbPath() );
+ $this->assertEquals( 'mwstore://local-backend/test-thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) );
+ $this->assertEquals( 'mwstore://local-backend/test-thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) );
+ }
+
+ public function testGetArchiveUrl() {
+ $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() );
+ $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() );
+ $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) );
+ $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) );
+ }
+
+ public function testGetThumbUrl() {
+ $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() );
+ $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() );
+ $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) );
+ $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) );
+ }
+
+ public function testGetArchiveVirtualUrl() {
+ $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/public/archive/a/a2', $this->file_hl2->getArchiveVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/public/archive/%21', $this->file_hl0->getArchiveVirtualUrl( '!' ) );
+ $this->assertEquals( 'mwrepo://test/public/archive/a/a2/%21', $this->file_hl2->getArchiveVirtualUrl( '!' ) );
+ }
+
+ public function testGetThumbVirtualUrl() {
+ $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() );
+ $this->assertEquals( 'mwrepo://test/thumb/Test%21/%21', $this->file_hl0->getThumbVirtualUrl( '!' ) );
+ $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21/%21', $this->file_hl2->getThumbVirtualUrl( '!' ) );
+ }
+
+ public function testGetUrl() {
+ $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() );
+ $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() );
+ }
+
+ public function testWfLocalFile() {
+ $file = wfLocalFile( "File:Some_file_that_probably_doesn't exist.png" );
+ $this->assertThat( $file, $this->isInstanceOf( 'LocalFile' ), 'wfLocalFile() returns LocalFile for valid Titles' );
+ }
+}
diff --git a/tests/phpunit/includes/LocalisationCacheTest.php b/tests/phpunit/includes/LocalisationCacheTest.php
new file mode 100644
index 00000000..b34847aa
--- /dev/null
+++ b/tests/phpunit/includes/LocalisationCacheTest.php
@@ -0,0 +1,31 @@
+<?php
+
+class LocalisationCacheTest extends MediaWikiTestCase {
+ public function testPuralRulesFallback() {
+ $cache = Language::getLocalisationCache();
+
+ $this->assertEquals(
+ $cache->getItem( 'ar', 'pluralRules' ),
+ $cache->getItem( 'arz', 'pluralRules' ),
+ 'arz plural rules (undefined) fallback to ar (defined)'
+ );
+
+ $this->assertEquals(
+ $cache->getItem( 'ar', 'compiledPluralRules' ),
+ $cache->getItem( 'arz', 'compiledPluralRules' ),
+ 'arz compiled plural rules (undefined) fallback to ar (defined)'
+ );
+
+ $this->assertNotEquals(
+ $cache->getItem( 'ksh', 'pluralRules' ),
+ $cache->getItem( 'de', 'pluralRules' ),
+ 'ksh plural rules (defined) dont fallback to de (defined)'
+ );
+
+ $this->assertNotEquals(
+ $cache->getItem( 'ksh', 'compiledPluralRules' ),
+ $cache->getItem( 'de', 'compiledPluralRules' ),
+ 'ksh compiled plural rules (defined) dont fallback to de (defined)'
+ );
+ }
+}
diff --git a/tests/phpunit/includes/MWExceptionHandlerTest.php b/tests/phpunit/includes/MWExceptionHandlerTest.php
new file mode 100644
index 00000000..987dfa83
--- /dev/null
+++ b/tests/phpunit/includes/MWExceptionHandlerTest.php
@@ -0,0 +1,73 @@
+<?php
+/**
+ * Tests for includes/Exception.php.
+ *
+ * @author Antoine Musso
+ * @copyright Copyright © 2013, Antoine Musso
+ * @copyright Copyright © 2013, Wikimedia Foundation Inc.
+ * @file
+ */
+
+class MWExceptionHandlerTest extends MediaWikiTestCase {
+
+ /**
+ * @covers MWExceptionHandler::getRedactedTrace
+ */
+ function testGetRedactedTrace() {
+ try {
+ $array = array( 'a', 'b' );
+ $object = new StdClass();
+ self::helperThrowAnException( $array, $object );
+ } catch (Exception $e) {
+ }
+
+ # Make sure our strack trace contains an array and an object passed to
+ # some function in the stacktrace. Else, we can not assert the trace
+ # redaction achieved its job.
+ $trace = $e->getTrace();
+ $hasObject = false;
+ $hasArray = false;
+ foreach ( $trace as $frame ) {
+ if ( ! isset( $frame['args'] ) ) {
+ continue;
+ }
+ foreach ( $frame['args'] as $arg ) {
+ $hasObject = $hasObject || is_object( $arg );
+ $hasArray = $hasArray || is_array( $arg );
+ }
+
+ if( $hasObject && $hasArray ) {
+ break;
+ }
+ }
+ $this->assertTrue( $hasObject,
+ "The stacktrace must have a function having an object has parameter" );
+ $this->assertTrue( $hasArray,
+ "The stacktrace must have a function having an array has parameter" );
+
+ # Now we redact the trace.. and make sure no function arguments are
+ # arrays or objects.
+ $redacted = MWExceptionHandler::getRedactedTrace( $e );
+
+ foreach ( $redacted as $frame ) {
+ if ( ! isset( $frame['args'] ) ) {
+ continue;
+ }
+ foreach ( $frame['args'] as $arg ) {
+ $this->assertNotInternalType( 'array', $arg);
+ $this->assertNotInternalType( 'object', $arg);
+ }
+ }
+ }
+
+ /**
+ * Helper function for testExpandArgumentsInCall
+ *
+ * Pass it an object and an array :-)
+ *
+ * @throws Exception
+ */
+ protected static function helperThrowAnException( $a, $b ) {
+ throw new Exception();
+ }
+}
diff --git a/tests/phpunit/includes/MWFunctionTest.php b/tests/phpunit/includes/MWFunctionTest.php
new file mode 100644
index 00000000..d86f2c9b
--- /dev/null
+++ b/tests/phpunit/includes/MWFunctionTest.php
@@ -0,0 +1,30 @@
+<?php
+
+class MWFunctionTest extends MediaWikiTestCase {
+ public function testNewObjFunction() {
+ $arg1 = 'Foo';
+ $arg2 = 'Bar';
+ $arg3 = array( 'Baz' );
+ $arg4 = new ExampleObject;
+
+ $args = array( $arg1, $arg2, $arg3, $arg4 );
+
+ $newObject = new MWBlankClass( $arg1, $arg2, $arg3, $arg4 );
+ $this->assertEquals(
+ MWFunction::newObj( 'MWBlankClass', $args )->args,
+ $newObject->args
+ );
+ }
+}
+
+class MWBlankClass {
+
+ public $args = array();
+
+ function __construct( $arg1, $arg2, $arg3, $arg4 ) {
+ $this->args = array( $arg1, $arg2, $arg3, $arg4 );
+ }
+}
+
+class ExampleObject {
+}
diff --git a/tests/phpunit/includes/MWNamespaceTest.php b/tests/phpunit/includes/MWNamespaceTest.php
new file mode 100644
index 00000000..10e9db61
--- /dev/null
+++ b/tests/phpunit/includes/MWNamespaceTest.php
@@ -0,0 +1,571 @@
+<?php
+/**
+ * @author Antoine Musso
+ * @copyright Copyright © 2011, Antoine Musso
+ * @file
+ */
+
+/**
+ * Test class for MWNamespace.
+ * Generated by PHPUnit on 2011-02-20 at 21:01:55.
+ *
+ */
+class MWNamespaceTest extends MediaWikiTestCase {
+ protected function setUp() {
+ parent::setUp();
+
+ $this->setMwGlobals( array(
+ 'wgContentNamespaces' => array( NS_MAIN ),
+ 'wgNamespacesWithSubpages' => array(
+ NS_TALK => true,
+ NS_USER => true,
+ NS_USER_TALK => true,
+ ),
+ 'wgCapitalLinks' => true,
+ 'wgCapitalLinkOverrides' => array(),
+ 'wgNonincludableNamespaces' => array(),
+ ) );
+ }
+
+#### START OF TESTS #########################################################
+
+ /**
+ * @todo Write more texts, handle $wgAllowImageMoving setting
+ */
+ public function testIsMovable() {
+ $this->assertFalse( MWNamespace::isMovable( NS_CATEGORY ) );
+ # @todo FIXME: Write more tests!!
+ }
+
+ /**
+ * Please make sure to change testIsTalk() if you change the assertions below
+ */
+ public function testIsSubject() {
+ // Special namespaces
+ $this->assertIsSubject( NS_MEDIA );
+ $this->assertIsSubject( NS_SPECIAL );
+
+ // Subject pages
+ $this->assertIsSubject( NS_MAIN );
+ $this->assertIsSubject( NS_USER );
+ $this->assertIsSubject( 100 ); # user defined
+
+ // Talk pages
+ $this->assertIsNotSubject( NS_TALK );
+ $this->assertIsNotSubject( NS_USER_TALK );
+ $this->assertIsNotSubject( 101 ); # user defined
+ }
+
+ /**
+ * Reverse of testIsSubject().
+ * Please update testIsSubject() if you change assertions below
+ */
+ public function testIsTalk() {
+ // Special namespaces
+ $this->assertIsNotTalk( NS_MEDIA );
+ $this->assertIsNotTalk( NS_SPECIAL );
+
+ // Subject pages
+ $this->assertIsNotTalk( NS_MAIN );
+ $this->assertIsNotTalk( NS_USER );
+ $this->assertIsNotTalk( 100 ); # user defined
+
+ // Talk pages
+ $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 ) );
+ }
+
+ /**
+ * Regular getTalk() calls
+ * Namespaces without a talk page (NS_MEDIA, NS_SPECIAL) are tested in
+ * the function testGetTalkExceptions()
+ */
+ 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 ) );
+ }
+
+ /**
+ * Exceptions with getTalk()
+ * NS_MEDIA does not have talk pages. MediaWiki raise an exception for them.
+ * @expectedException MWException
+ */
+ public function testGetTalkExceptionsForNsMedia() {
+ $this->assertNull( MWNamespace::getTalk( NS_MEDIA ) );
+ }
+
+ /**
+ * Exceptions with getTalk()
+ * NS_SPECIAL does not have talk pages. MediaWiki raise an exception for them.
+ * @expectedException MWException
+ */
+ public function testGetTalkExceptionsForNsSpecial() {
+ $this->assertNull( MWNamespace::getTalk( NS_SPECIAL ) );
+ }
+
+ /**
+ * Regular getAssociated() calls
+ * Namespaces without an associated page (NS_MEDIA, NS_SPECIAL) are tested in
+ * the function testGetAssociatedExceptions()
+ */
+ public function testGetAssociated() {
+ $this->assertEquals( NS_TALK, MWNamespace::getAssociated( NS_MAIN ) );
+ $this->assertEquals( NS_MAIN, MWNamespace::getAssociated( NS_TALK ) );
+ }
+
+ ### Exceptions with getAssociated()
+ ### NS_MEDIA and NS_SPECIAL do not have talk pages. MediaWiki raises
+ ### an exception for them.
+ /**
+ * @expectedException MWException
+ */
+ public function testGetAssociatedExceptionsForNsMedia() {
+ $this->assertNull( MWNamespace::getAssociated( NS_MEDIA ) );
+ }
+
+ /**
+ * @expectedException MWException
+ */
+ public function testGetAssociatedExceptionsForNsSpecial() {
+ $this->assertNull( MWNamespace::getAssociated( NS_SPECIAL ) );
+ }
+
+ /**
+ * @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 ) );
+ }
+
+ /**