diff options
Diffstat (limited to 'maintenance/tests')
72 files changed, 13013 insertions, 3106 deletions
diff --git a/maintenance/tests/ApiSetup.php b/maintenance/tests/ApiSetup.php deleted file mode 100644 index 549d8aef..00000000 --- a/maintenance/tests/ApiSetup.php +++ /dev/null @@ -1,39 +0,0 @@ -<?php - -abstract class ApiSetup extends PHPUnit_Framework_TestCase { - protected static $userName; - protected static $passWord; - protected static $user; - protected static $apiUrl; - - function setup() { - global $wgServerName, $wgServer, $wgContLang, $wgAuth, $wgScriptPath, - $wgScriptExtension, $wgMemc, $wgRequest; - - self::$apiUrl = $wgServer.$wgScriptPath."/api".$wgScriptExtension; - - $wgMemc = new FakeMemCachedClient; - $wgContLang = Language::factory( 'en' ); - $wgAuth = new StubObject( 'wgAuth', 'AuthPlugin' ); - $wgRequest = new FauxRequest(array()); - self::setupUser(); - } - - static function setupUser() { - if ( self::$user == NULL ) { - self::$userName = "Useruser"; - self::$passWord = User::randomPassword(); - - self::$user = User::newFromName(self::$userName); - if ( !self::$user->getID() ) { - self::$user = User::createNew(self::$userName, array( - "password" => self::$passWord, - "email" => "test@example.com", - "real_name" => "Test User")); - } else { - self::$user->setPassword(self::$passWord); - } - self::$user->saveSettings(); - } - } -} diff --git a/maintenance/tests/ApiTest.php b/maintenance/tests/ApiTest.php deleted file mode 100644 index d098b1a2..00000000 --- a/maintenance/tests/ApiTest.php +++ /dev/null @@ -1,164 +0,0 @@ -<?php - -require_once( "ApiSetup.php" ); - -class MockApi extends ApiBase { - public function execute() {} - public function getVersion() {} - - public function __construct() {} - - public function getAllowedParams() { - $params = array( - 'filename' => null, - 'enablechunks' => false, - 'sessionkey' => null, - ); - } - - -} - - -class ApiTest extends ApiSetup { - - function setup() { - parent::setup(); - } - - function testRequireOnlyOneParameterDefault() { - $mock = new MockApi(); - - $this->assertEquals( - null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt", - "enablechunks" => false), "filename", "enablechunks")); - } - - /** - * @expectedException UsageException - */ - function testRequireOnlyOneParameterZero() { - $mock = new MockApi(); - - $this->assertEquals( - null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt", - "enablechunks" => 0), "filename", "enablechunks")); - } - - /** - * @expectedException UsageException - */ - function testRequireOnlyOneParameterTrue() { - $mock = new MockApi(); - - $this->assertEquals( - null, $mock->requireOnlyOneParameter(array("filename" => "foo.txt", - "enablechunks" => true), "filename", "enablechunks")); - } - - function testApi() { - if(!isset($wgServername) || !isset($wgServer)) { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - /* Haven't thought about test ordering yet -- but this depends on HttpTest.php */ - $resp = Http::get( self::$apiUrl . "?format=xml" ); - - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - } - - function testApiLoginNoName() { - if(!isset($wgServername) || !isset($wgServer)) { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - $resp = Http::post( self::$apiUrl . "?action=login&format=xml", - array( "postData" => array( - "lgname" => "", - "lgpassword" => self::$passWord ) ) ); - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - $a = $sxe->login[0]->attributes()->result; - $this->assertEquals( ' result="NoName"', $a->asXML() ); - } - - function testApiLoginBadPass() { - if(!isset($wgServername) || !isset($wgServer)) { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - $resp = Http::post( self::$apiUrl . "?action=login&format=xml", - array( "postData" => array( - "lgname" => self::$userName, - "lgpassword" => "bad" ) ) ); - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - $a = $sxe->login[0]->attributes()->result; - $this->assertEquals( ' result="WrongPass"', $a->asXML() ); - } - - function testApiLoginGoodPass() { - if(!isset($wgServername) || !isset($wgServer)) { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - $resp = Http::post( self::$apiUrl . "?action=login&format=xml", - array( "postData" => array( - "lgname" => self::$userName, - "lgpassword" => self::$passWord ) ) ); - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $resp ); - $this->assertNotType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - $a = $sxe->login[0]->attributes()->result; - $this->assertEquals( ' result="Success"', $a->asXML() ); - } - - function testApiGotCookie() { - global $wgScriptPath, $wgServerName; - - if(!isset($wgServername) || !isset($wgServer)) { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - $req = HttpRequest::factory( self::$apiUrl . "?action=login&format=xml", - array( "method" => "POST", - "postData" => array( "lgname" => self::$userName, - "lgpassword" => self::$passWord ) ) ); - $req->execute(); - $cj = $req->getCookieJar(); - $this->assertRegexp( '/_session=[^;]*; .*UserID=[0-9]*; .*UserName=' . self::$userName . '; .*Token=/', - $cj->serializeToHttpRequest( $wgScriptPath, $wgServerName ) ); - - - return $cj; - } - - /** - * @depends testApiGotCookie - */ - function testApiListPages(CookieJar $cj) { - $this->markTestIncomplete("Not done with this yet"); - - if($wgServerName == "localhost" || $wgServer == "http://localhost") { - $this->markTestIncomplete('This test needs $wgServerName and $wgServer to '. - 'be set in LocalSettings.php'); - } - $req = HttpRequest::factory( self::$apiUrl . "?action=query&format=xml&prop=revisions&". - "titles=Main%20Page&rvprop=timestamp|user|comment|content" ); - $req->setCookieJar($cj); - $req->execute(); - libxml_use_internal_errors( true ); - $sxe = simplexml_load_string( $req->getContent() ); - $this->assertNotType( "bool", $sxe ); - $this->assertThat( $sxe, $this->isInstanceOf( "SimpleXMLElement" ) ); - $a = $sxe->query[0]->pages[0]->page[0]->attributes(); - } -} diff --git a/maintenance/tests/CdbTest.php b/maintenance/tests/CdbTest.php deleted file mode 100644 index 444229e7..00000000 --- a/maintenance/tests/CdbTest.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php - -/** - * Test the CDB reader/writer - */ - -class CdbTest extends PHPUnit_Framework_TestCase { - - public function setup() { - if ( !CdbReader::haveExtension() ) { - $this->markTestIncomplete( 'This test requires native CDB support to be present.' ); - } - } - - public function testCdb() { - $w1 = new CdbWriter_PHP( 'php.cdb' ); - $w2 = new CdbWriter_DBA( 'dba.cdb' ); - - $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( 'dba.cdb' ), - md5_file( 'php.cdb' ), - 'same hash' - ); - - $r1 = new CdbReader_PHP( 'php.cdb' ); - $r2 = new CdbReader_DBA( 'dba.cdb' ); - - 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 ); - } - - unlink( 'dba.cdb' ); - unlink( 'php.cdb' ); - } - - 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/maintenance/tests/DatabaseSqliteTest.php b/maintenance/tests/DatabaseSqliteTest.php deleted file mode 100644 index 011ef798..00000000 --- a/maintenance/tests/DatabaseSqliteTest.php +++ /dev/null @@ -1,57 +0,0 @@ -<?php - -class MockDatabaseSqlite extends DatabaseSqliteStandalone { - var $lastQuery; - - function __construct( ) { - parent::__construct( '' ); - } - - function query( $sql, $fname = '', $tempIgnore = false ) { - $this->lastQuery = $sql; - return true; - } - - function replaceVars( $s ) { - return parent::replaceVars( $s ); - } -} - -class DatabaseSqliteTest extends PHPUnit_Framework_TestCase { - var $db; - - function setup() { - if ( !extension_loaded( 'pdo_sqlite' ) ) { - $this->markTestIncomplete( 'No SQLite support detected' ); - } - $this->db = new MockDatabaseSqlite(); - } - - function replaceVars( $sql ) { - // normalize spacing to hide implementation details - return preg_replace( '/\s+/', ' ', $this->db->replaceVars( $sql ) ); - } - - function testReplaceVars() { - $this->assertEquals( 'foo', $this->replaceVars( 'foo' ), "Don't break anything accidentally" ); - - $this->assertEquals( "CREATE TABLE /**/foo (foo_key INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, " - . "foo_name TEXT NOT NULL DEFAULT '', foo_int INTEGER, foo_int2 INTEGER );", - $this->replaceVars( "CREATE TABLE /**/foo (foo_key int unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT, - foo_name varchar(255) binary NOT NULL DEFAULT '', foo_int tinyint( 8 ), foo_int2 int(16) ) ENGINE=MyISAM;" ) - ); - - $this->assertEquals( "CREATE TABLE foo ( foo_binary1 BLOB, foo_binary2 BLOB );", - $this->replaceVars( "CREATE TABLE foo ( foo_binary1 binary(16), foo_binary2 varbinary(32) );" ) - ); - - $this->assertEquals( "CREATE TABLE text ( text_foo TEXT );", - $this->replaceVars( "CREATE TABLE text ( text_foo tinytext );" ), - 'Table name changed' - ); - - $this->assertEquals( "ALTER TABLE foo ADD COLUMN foo_bar INTEGER DEFAULT 42", - $this->replaceVars( "ALTER TABLE foo\nADD COLUMN foo_bar int(10) unsigned DEFAULT 42") - ); - } -}
\ No newline at end of file diff --git a/maintenance/tests/DatabaseTest.php b/maintenance/tests/DatabaseTest.php deleted file mode 100644 index aa50de2e..00000000 --- a/maintenance/tests/DatabaseTest.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php - -class DatabaseTest extends PHPUnit_Framework_TestCase { - var $db; - - function setUp() { - $this->db = wfGetDB( DB_SLAVE ); - } - - function testAddQuotesNull() { - $check = "NULL"; - if ( $this->db->getType() === 'sqlite' ) { - $check = "''"; - } - $this->assertEquals( $check, $this->db->addQuotes( null ) ); - } - - function testAddQuotesInt() { - # returning just "1234" should be ok too, though... - # maybe - $this->assertEquals( - "'1234'", - $this->db->addQuotes( 1234 ) ); - } - - function testAddQuotesFloat() { - # returning just "1234.5678" would be ok too, though - $this->assertEquals( - "'1234.5678'", - $this->db->addQuotes( 1234.5678 ) ); - } - - function testAddQuotesString() { - $this->assertEquals( - "'string'", - $this->db->addQuotes( 'string' ) ); - } - - function testAddQuotesStringQuote() { - $check = "'string''s cause trouble'"; - if ( $this->db->getType() === 'mysql' ) { - $check = "'string\'s cause trouble'"; - } - $this->assertEquals( - $check, - $this->db->addQuotes( "string's cause trouble" ) ); - } - - function testFillPreparedEmpty() { - $sql = $this->db->fillPrepared( - 'SELECT * FROM interwiki', array() ); - $this->assertEquals( - "SELECT * FROM interwiki", - $sql); - } - - function testFillPreparedQuestion() { - $sql = $this->db->fillPrepared( - 'SELECT * FROM cur WHERE cur_namespace=? AND cur_title=?', - array( 4, "Snicker's_paradox" ) ); - - $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker''s_paradox'"; - if ( $this->db->getType() === 'mysql' ) { - $check = "SELECT * FROM cur WHERE cur_namespace='4' AND cur_title='Snicker\'s_paradox'"; - } - $this->assertEquals( $check, $sql ); - } - - function testFillPreparedBang() { - $sql = $this->db->fillPrepared( - 'SELECT user_id FROM ! WHERE user_name=?', - array( '"user"', "Slash's Dot" ) ); - - $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash''s Dot'"; - if ( $this->db->getType() === 'mysql' ) { - $check = "SELECT user_id FROM \"user\" WHERE user_name='Slash\'s Dot'"; - } - $this->assertEquals( $check, $sql ); - } - - function testFillPreparedRaw() { - $sql = $this->db->fillPrepared( - "SELECT * FROM cur WHERE cur_title='This_\\&_that,_WTF\\?\\!'", - array( '"user"', "Slash's Dot" ) ); - $this->assertEquals( - "SELECT * FROM cur WHERE cur_title='This_&_that,_WTF?!'", - $sql); - } - -} - - diff --git a/maintenance/tests/GlobalTest.php b/maintenance/tests/GlobalTest.php deleted file mode 100644 index ec694241..00000000 --- a/maintenance/tests/GlobalTest.php +++ /dev/null @@ -1,212 +0,0 @@ -<?php - -class GlobalTest extends PHPUnit_Framework_TestCase { - function setUp() { - global $wgReadOnlyFile; - $this->originals['wgReadOnlyFile'] = $wgReadOnlyFile; - $wgReadOnlyFile = tempnam(wfTempDir(), "mwtest_readonly"); - unlink( $wgReadOnlyFile ); - } - - function tearDown() { - global $wgReadOnlyFile; - if( file_exists( $wgReadOnlyFile ) ) { - unlink( $wgReadOnlyFile ); - } - $wgReadOnlyFile = $this->originals['wgReadOnlyFile']; - } - - function testRandom() { - # This could hypothetically fail, but it shouldn't ;) - $this->assertFalse( - wfRandom() == wfRandom() ); - } - - function testUrlencode() { - $this->assertEquals( - "%E7%89%B9%E5%88%A5:Contributions/Foobar", - wfUrlencode( "\xE7\x89\xB9\xE5\x88\xA5:Contributions/Foobar" ) ); - } - - function testReadOnlyEmpty() { - global $wgReadOnly; - $wgReadOnly = null; - - $this->assertFalse( wfReadOnly() ); - $this->assertFalse( wfReadOnly() ); - } - - function testReadOnlySet() { - global $wgReadOnly, $wgReadOnlyFile; - - $f = fopen( $wgReadOnlyFile, "wt" ); - fwrite( $f, 'Message' ); - fclose( $f ); - $wgReadOnly = null; - - $this->assertTrue( wfReadOnly() ); - $this->assertTrue( wfReadOnly() ); - - unlink( $wgReadOnlyFile ); - $wgReadOnly = null; - - $this->assertFalse( wfReadOnly() ); - $this->assertFalse( wfReadOnly() ); - } - - function testQuotedPrintable() { - $this->assertEquals( - "=?UTF-8?Q?=C4=88u=20legebla=3F?=", - wfQuotedPrintable( "\xc4\x88u legebla?", "UTF-8" ) ); - } - - function testTime() { - $start = wfTime(); - $this->assertType( 'float', $start ); - $end = wfTime(); - $this->assertTrue( $end > $start, "Time is running backwards!" ); - } - - function testArrayToCGI() { - $this->assertEquals( - "baz=AT%26T&foo=bar", - wfArrayToCGI( - array( 'baz' => 'AT&T', 'ignore' => '' ), - array( 'foo' => 'bar', 'baz' => 'overridden value' ) ) ); - } - - 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 ) ) ); - } - - 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 ) ) ); - } - - function testTimestamp() { - $t = gmmktime( 12, 34, 56, 1, 15, 2001 ); - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, $t ), - 'TS_UNIX to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, $t ), - 'TS_UNIX to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, $t ), - 'TS_UNIX to TS_DB' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, '20010115123456' ), - 'TS_MW to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, '20010115123456' ), - 'TS_MW to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, '20010115123456' ), - 'TS_MW to TS_DB' ); - - $this->assertEquals( - '20010115123456', - wfTimestamp( TS_MW, '2001-01-15 12:34:56' ), - 'TS_DB to TS_MW' ); - $this->assertEquals( - 979562096, - wfTimestamp( TS_UNIX, '2001-01-15 12:34:56' ), - 'TS_DB to TS_UNIX' ); - $this->assertEquals( - '2001-01-15 12:34:56', - wfTimestamp( TS_DB, '2001-01-15 12:34:56' ), - 'TS_DB to TS_DB' ); - } - - function testBasename() { - $sets = array( - '' => '', - '/' => '', - '\\' => '', - '//' => '', - '\\\\' => '', - 'a' => 'a', - 'aaaa' => 'aaaa', - '/a' => 'a', - '\\a' => 'a', - '/aaaa' => 'aaaa', - '\\aaaa' => 'aaaa', - '/aaaa/' => 'aaaa', - '\\aaaa\\' => 'aaaa', - '\\aaaa\\' => 'aaaa', - '/mnt/upload3/wikipedia/en/thumb/8/8b/Zork_Grand_Inquisitor_box_cover.jpg/93px-Zork_Grand_Inquisitor_box_cover.jpg' => '93px-Zork_Grand_Inquisitor_box_cover.jpg', - 'C:\\Progra~1\\Wikime~1\\Wikipe~1\\VIEWER.EXE' => 'VIEWER.EXE', - 'Östergötland_coat_of_arms.png' => 'Östergötland_coat_of_arms.png', - ); - foreach( $sets as $from => $to ) { - $this->assertEquals( $to, wfBaseName( $from ), - "wfBaseName('$from') => '$to'"); - } - } - - /* TODO: many more! */ -} - - diff --git a/maintenance/tests/HttpTest.php b/maintenance/tests/HttpTest.php deleted file mode 100644 index 83734910..00000000 --- a/maintenance/tests/HttpTest.php +++ /dev/null @@ -1,567 +0,0 @@ -<?php - -class MockCookie extends Cookie { - public function canServeDomain($arg) { return parent::canServeDomain($arg); } - public function canServePath($arg) { return parent::canServePath($arg); } - public function isUnExpired() { return parent::isUnExpired(); } -} - -class HttpTest extends PhpUnit_Framework_TestCase { - static $content; - static $headers; - static $has_curl; - static $has_fopen; - static $has_proxy = false; - static $proxy = "http://hulk:8080/"; - var $test_geturl = array( - "http://www.example.com/", - "http://pecl.php.net/feeds/pkg_apc.rss", - "http://toolserver.org/~jan/poll/dev/main.php?page=wiki_output&id=3", - "http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw", - "http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&format=php", - ); - var $test_requesturl = array( "http://en.wikipedia.org/wiki/Special:Export/User:MarkAHershberger" ); - - var $test_posturl = array( "http://www.comp.leeds.ac.uk/cgi-bin/Perl/environment-example" => "review=test" ); - - function setup() { - putenv("http_proxy"); /* Remove any proxy env var, so curl doesn't get confused */ - if ( is_array( self::$content ) ) { - return; - } - self::$has_curl = function_exists( 'curl_init' ); - self::$has_fopen = wfIniGetBool( 'allow_url_fopen' ); - - if ( !file_exists("/usr/bin/curl") ) { - $this->markTestIncomplete("This test requires the curl binary at /usr/bin/curl. If you have curl, please file a bug on this test, or, better yet, provide a patch."); - } - - $content = tempnam( wfTempDir(), "" ); - $headers = tempnam( wfTempDir(), "" ); - if ( !$content && !$headers ) { - die( "Couldn't create temp file!" ); - } - - // This probably isn't the best test for a proxy, but it works on my system! - system("curl -0 -o $content -s ".self::$proxy); - $out = file_get_contents( $content ); - if( $out ) { - self::$has_proxy = true; - } - - /* Maybe use wget instead of curl here ... just to use a different codebase? */ - foreach ( $this->test_geturl as $u ) { - system( "curl -0 -s -D $headers '$u' -o $content" ); - self::$content["GET $u"] = file_get_contents( $content ); - self::$headers["GET $u"] = file_get_contents( $headers ); - } - foreach ( $this->test_requesturl as $u ) { - system( "curl -0 -s -X POST -H 'Content-Length: 0' -D $headers '$u' -o $content" ); - self::$content["POST $u"] = file_get_contents( $content ); - self::$headers["POST $u"] = file_get_contents( $headers ); - } - foreach ( $this->test_posturl as $u => $postData ) { - system( "curl -0 -s -X POST -d '$postData' -D $headers '$u' -o $content" ); - self::$content["POST $u => $postData"] = file_get_contents( $content ); - self::$headers["POST $u => $postData"] = file_get_contents( $headers ); - } - unlink( $content ); - unlink( $headers ); - } - - - function testInstantiation() { - Http::$httpEngine = false; - - $r = HttpRequest::factory("http://www.example.com/"); - if ( self::$has_curl ) { - $this->assertThat($r, $this->isInstanceOf( 'CurlHttpRequest' )); - } else { - $this->assertThat($r, $this->isInstanceOf( 'PhpHttpRequest' )); - } - unset($r); - - if( !self::$has_fopen ) { - $this->setExpectedException( 'MWException' ); - } - Http::$httpEngine = 'php'; - $r = HttpRequest::factory("http://www.example.com/"); - $this->assertThat($r, $this->isInstanceOf( 'PhpHttpRequest' )); - unset($r); - - if( !self::$has_curl ) { - $this->setExpectedException( 'MWException' ); - } - Http::$httpEngine = 'curl'; - $r = HttpRequest::factory("http://www.example.com/"); - if( self::$has_curl ) { - $this->assertThat($r, $this->isInstanceOf( 'CurlHttpRequest' )); - } - } - - function runHTTPFailureChecks() { - // Each of the following requests should result in a failure. - - $timeout = 1; - $start_time = time(); - $r = HTTP::get( "http://www.example.com:1/", $timeout); - $end_time = time(); - $this->assertLessThan($timeout+2, $end_time - $start_time, - "Request took less than {$timeout}s via ".Http::$httpEngine); - $this->assertEquals($r, false, "false -- what we get on error from Http::get()"); - - $r = HTTP::get( "http://www.example.com/this-file-does-not-exist", $timeout); - $this->assertFalse($r, "False on 404s"); - - - $r = HttpRequest::factory( "http://www.example.com/this-file-does-not-exist" ); - $er = $r->execute(); - if ( is_a($r, 'PhpHttpRequest') && version_compare( '5.2.10', phpversion(), '>' ) ) { - $this->assertRegexp("/HTTP request failed/", $er->getWikiText()); - } else { - $this->assertRegexp("/404 Not Found/", $er->getWikiText()); - } - } - - function testFailureDefault() { - Http::$httpEngine = false; - self::runHTTPFailureChecks(); - } - - function testFailurePhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - self::runHTTPFailureChecks(); - } - - function testFailureCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - self::runHTTPFailureChecks(); - } - - /* ./phase3/includes/Import.php:1108: $data = Http::request( $method, $url ); */ - /* ./includes/Import.php:1124: $link = Title::newFromText( "$interwiki:Special:Export/$page" ); */ - /* ./includes/Import.php:1134: return ImportStreamSource::newFromURL( $url, "POST" ); */ - function runHTTPRequests($proxy=null) { - $opt = array(); - - if($proxy) { - $opt['proxy'] = $proxy; - } elseif( $proxy === false ) { - $opt['noProxy'] = true; - } - - /* no postData here because the only request I could find in code so far didn't have any */ - foreach ( $this->test_requesturl as $u ) { - $r = Http::request( "POST", $u, $opt ); - $this->assertEquals( self::$content["POST $u"], "$r", "POST $u with ".Http::$httpEngine ); - } - } - - function testRequestDefault() { - Http::$httpEngine = false; - self::runHTTPRequests(); - } - - function testRequestPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - self::runHTTPRequests(); - } - - function testRequestCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - self::runHTTPRequests(); - } - - /* ./extensions/SpamBlacklist/SpamBlacklist_body.php:164: $httpText = Http::get( $fileName ); */ - /* ./extensions/ApiSVGProxy/ApiSVGProxy.body.php:44: $contents = Http::get( $file->getFullUrl() ); */ - /* ./extensions/BookInformation/drivers/IsbnDb.php:24: if( ( $xml = Http::get( $uri ) ) !== false ) { */ - /* ./extensions/BookInformation/drivers/Amazon.php:23: if( ( $xml = Http::get( $uri ) ) !== false ) { */ - /* ./extensions/TitleBlacklist/TitleBlacklist.list.php:217: $result = Http::get( $url ); */ - /* ./extensions/TSPoll/TSPoll.php:68: $get_server = Http::get( 'http://toolserver.org/~jan/poll/dev/main.php?page=wiki_output&id='.$id ); */ - /* ./extensions/TSPoll/TSPoll.php:70: $get_server = Http::get( 'http://toolserver.org/~jan/poll/main.php?page=wiki_output&id='.$id ); */ - /* ./extensions/DoubleWiki/DoubleWiki.php:56: $translation = Http::get( $url.$sep.'action=render' ); */ - /* ./extensions/ExternalPages/ExternalPages_body.php:177: $serializedText = Http::get( $this->mPageURL ); */ - /* ./extensions/Translate/utils/TranslationHelpers.php:143: $suggestions = Http::get( $url, $timeout ); */ - /* ./extensions/Translate/SpecialImportTranslations.php:169: $filedata = Http::get( $url ); ; */ - /* ./extensions/Translate/TranslateEditAddons.php:338: $suggestions = Http::get( $url, $timeout ); */ - /* ./extensions/SecurePoll/includes/user/Auth.php:283: $value = Http::get( $url, 20, $curlParams ); */ - /* ./extensions/DumpHTML/dumpHTML.inc:778: $contents = Http::get( $url ); */ - /* ./extensions/DumpHTML/dumpHTML.inc:1298: $contents = Http::get( $sourceUrl ); */ - /* ./extensions/DumpHTML/dumpHTML.inc:1373: $contents = Http::get( $sourceUrl ); */ - /* ./phase3/maintenance/rebuildInterwiki.inc:101: $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); */ - /* ./phase3/maintenance/findhooks.php:98: $allhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:MediaWiki_hooks&cmlimit=500&format=php' ); */ - /* ./phase3/maintenance/findhooks.php:109: $oldhookdata = Http::get( 'http://www.mediawiki.org/w/api.php?action=query&list=categorymembers&cmtitle=Category:Removed_hooks&cmlimit=500&format=php' ); */ - /* ./phase3/maintenance/dumpInterwiki.inc:95: $intermap = Http::get( 'http://meta.wikimedia.org/w/index.php?title=Interwiki_map&action=raw', 30 ); */ - /* ./phase3/includes/parser/Parser.php:3204: $text = Http::get($url); */ - /* ./phase3/includes/filerepo/ForeignAPIRepo.php:131: $data = Http::get( $url ); */ - /* ./phase3/includes/filerepo/ForeignAPIRepo.php:205: $thumb = Http::get( $foreignUrl ); */ - /* ./phase3/includes/filerepo/File.php:1105: $res = Http::get( $renderUrl ); */ - /* ./phase3/includes/GlobalFunctions.php:2760: * @deprecated Use Http::get() instead */ - /* ./phase3/includes/GlobalFunctions.php:2764: return Http::get( $url ); */ - /* ./phase3/includes/ExternalStoreHttp.php:18: $ret = Http::get( $url ); */ - /* ./phase3/includes/Import.php:357: $data = Http::get( $src ); */ - /* ./extensions/ExternalData/ED_Utils.php:291: return Http::get( $url, 'default', array(CURLOPT_SSL_VERIFYPEER => false) ); */ - /* ./extensions/ExternalData/ED_Utils.php:293: return Http::get( $url ); */ - /* ./extensions/ExternalData/ED_Utils.php:306: $page = Http::get( $url, 'default', array(CURLOPT_SSL_VERIFYPEER => false) ); */ - /* ./extensions/ExternalData/ED_Utils.php:308: $page = Http::get( $url ); */ - /* ./extensions/CodeReview/backend/Subversion.php:320: $blob = Http::get( $target, $this->mTimeout ); */ - /* ./extensions/AmazonPlus/AmazonPlus.php:214: $this->response = Http::get( $urlstr ); */ - /* ./extensions/StaticWiki/StaticWiki.php:24: $text = Http::get( $url ) ; */ - /* ./extensions/StaticWiki/StaticWiki.php:64: $history = Http::get ( $wgStaticWikiExternalSite . "index.php?title=" . urlencode ( $url_title ) . "&action=history" ) ; */ - /* ./extensions/Configure/scripts/findSettings.php:126: $cont = Http::get( "http://www.mediawiki.org/w/index.php?title={$page}&action=raw" ); */ - /* ./extensions/TorBlock/TorBlock.class.php:148: $data = Http::get( $url ); */ - /* ./extensions/HoneypotIntegration/HoneypotIntegration.class.php:60: $data = Http::get( $wgHoneypotURLSource, 'default', */ - /* ./extensions/SemanticForms/includes/SF_Utils.inc:378: $page_contents = Http::get($url); */ - /* ./extensions/LocalisationUpdate/LocalisationUpdate.class.php:172: $basefilecontents = Http::get( $basefile ); */ - /* ./extensions/APC/SpecialAPC.php:245: $rss = Http::get( 'http://pecl.php.net/feeds/pkg_apc.rss' ); */ - /* ./extensions/Interlanguage/Interlanguage.php:56: $a = Http::get( $url ); */ - /* ./extensions/MWSearch/MWSearch_body.php:492: $data = Http::get( $searchUrl, $wgLuceneSearchTimeout, $httpOpts); */ - function runHTTPGets($proxy=null) { - $opt = array(); - - if($proxy) { - $opt['proxy'] = $proxy; - } elseif( $proxy === false ) { - $opt['noProxy'] = true; - } - - foreach ( $this->test_geturl as $u ) { - $r = Http::get( $u, 30, $opt ); /* timeout of 30s */ - $this->assertEquals( self::$content["GET $u"], "$r", "Get $u with ".Http::$httpEngine ); - } - } - - function testGetDefault() { - Http::$httpEngine = false; - self::runHTTPGets(); - } - - function testGetPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - self::runHTTPGets(); - } - - function testGetCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - self::runHTTPGets(); - } - - /* ./phase3/maintenance/parserTests.inc:1618: return Http::post( $url, array( 'postData' => wfArrayToCGI( $data ) ) ); */ - function runHTTPPosts($proxy=null) { - $opt = array(); - - if($proxy) { - $opt['proxy'] = $proxy; - } elseif( $proxy === false ) { - $opt['noProxy'] = true; - } - - foreach ( $this->test_posturl as $u => $postData ) { - $opt['postData'] = $postData; - $r = Http::post( $u, $opt ); - $this->assertEquals( self::$content["POST $u => $postData"], "$r", - "POST $u (postData=$postData) with ".Http::$httpEngine ); - } - } - - function testPostDefault() { - Http::$httpEngine = false; - self::runHTTPPosts(); - } - - function testPostPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = "php"; - self::runHTTPPosts(); - } - - function testPostCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = "curl"; - self::runHTTPPosts(); - } - - function runProxyRequests() { - if(!self::$has_proxy) { - $this->markTestIncomplete( "This test requires a proxy." ); - } - self::runHTTPGets(self::$proxy); - self::runHTTPPosts(self::$proxy); - self::runHTTPRequests(self::$proxy); - - // Set false here to do noProxy - self::runHTTPGets(false); - self::runHTTPPosts(false); - self::runHTTPRequests(false); - } - - function testProxyDefault() { - Http::$httpEngine = false; - self::runProxyRequests(); - } - - function testProxyPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = 'php'; - self::runProxyRequests(); - } - - function testProxyCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = 'curl'; - self::runProxyRequests(); - } - - function testIsLocalUrl() { - } - - /* ./extensions/DonationInterface/payflowpro_gateway/payflowpro_gateway.body.php:559: $user_agent = Http::userAgent(); */ - function testUserAgent() { - } - - function testIsValidUrl() { - } - - function testValidateCookieDomain() { - $this->assertFalse( Cookie::validateCookieDomain( "co.uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( ".co.uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "gov.uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( ".gov.uk" ) ); - $this->assertTrue( Cookie::validateCookieDomain( "supermarket.uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( ".uk" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "127.0.0." ) ); - $this->assertFalse( Cookie::validateCookieDomain( "127." ) ); - $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1." ) ); - $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "333.0.0.1" ) ); - $this->assertTrue( Cookie::validateCookieDomain( "example.com" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "example.com." ) ); - $this->assertTrue( Cookie::validateCookieDomain( ".example.com" ) ); - - $this->assertTrue( Cookie::validateCookieDomain( ".example.com", "www.example.com" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "example.com", "www.example.com" ) ); - $this->assertTrue( Cookie::validateCookieDomain( "127.0.0.1", "127.0.0.1" ) ); - $this->assertFalse( Cookie::validateCookieDomain( "127.0.0.1", "localhost" ) ); - - - } - - function testSetCooke() { - $c = new MockCookie( "name", "value", - array( - "domain" => "ac.th", - "path" => "/path/", - ) ); - $this->assertFalse($c->canServeDomain("ac.th")); - - $c = new MockCookie( "name", "value", - array( - "domain" => "example.com", - "path" => "/path/", - ) ); - - $this->assertTrue($c->canServeDomain("example.com")); - $this->assertFalse($c->canServeDomain("www.example.com")); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - ) ); - - $this->assertFalse($c->canServeDomain("www.example.net")); - $this->assertFalse($c->canServeDomain("example.com")); - $this->assertTrue($c->canServeDomain("www.example.com")); - - $this->assertFalse($c->canServePath("/")); - $this->assertFalse($c->canServePath("/bogus/path/")); - $this->assertFalse($c->canServePath("/path")); - $this->assertTrue($c->canServePath("/path/")); - - $this->assertTrue($c->isUnExpired()); - - $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.net")); - $this->assertEquals("", $c->serializeToHttpRequest("/", "www.example.com")); - $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com")); - - $c = new MockCookie( "name", "value", - array( - "domain" => "www.example.com", - "path" => "/path/", - ) ); - $this->assertFalse($c->canServeDomain("example.com")); - $this->assertFalse($c->canServeDomain("www.example.net")); - $this->assertTrue($c->canServeDomain("www.example.com")); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - "expires" => "-1 day", - ) ); - $this->assertFalse($c->isUnExpired()); - $this->assertEquals("", $c->serializeToHttpRequest("/path/", "www.example.com")); - - $c = new MockCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - "expires" => "+1 day", - ) ); - $this->assertTrue($c->isUnExpired()); - $this->assertEquals("name=value", $c->serializeToHttpRequest("/path/", "www.example.com")); - } - - function testCookieJarSetCookie() { - $cj = new CookieJar; - $cj->setCookie( "name", "value", - array( - "domain" => ".example.com", - "path" => "/path/", - ) ); - $cj->setCookie( "name2", "value", - array( - "domain" => ".example.com", - "path" => "/path/sub", - ) ); - $cj->setCookie( "name3", "value", - array( - "domain" => ".example.com", - "path" => "/", - ) ); - $cj->setCookie( "name4", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - ) ); - $cj->setCookie( "name5", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "-1 day", - ) ); - - $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/path/", "www.example.net")); - $this->assertEquals("name3=value", $cj->serializeToHttpRequest("/", "www.example.com")); - $this->assertEquals("name=value; name3=value", $cj->serializeToHttpRequest("/path/", "www.example.com")); - - $cj->setCookie( "name5", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "+1 day", - ) ); - $this->assertEquals("name4=value; name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net")); - - $cj->setCookie( "name4", "value", - array( - "domain" => ".example.net", - "path" => "/path/", - "expires" => "-1 day", - ) ); - $this->assertEquals("name5=value", $cj->serializeToHttpRequest("/path/", "www.example.net")); - } - - function testParseResponseHeader() { - $cj = new CookieJar; - - $h[] = "Set-Cookie: name4=value; domain=.example.com; path=/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[0], "www.example.com" ); - $this->assertEquals("name4=value", $cj->serializeToHttpRequest("/", "www.example.com")); - - $h[] = "name4=value2; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[1], "www.example.com" ); - $this->assertEquals("", $cj->serializeToHttpRequest("/", "www.example.com")); - $this->assertEquals("name4=value2", $cj->serializeToHttpRequest("/path/", "www.example.com")); - - $h[] = "name5=value3; domain=.example.com; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[2], "www.example.com" ); - $this->assertEquals("name4=value2; name5=value3", $cj->serializeToHttpRequest("/path/", "www.example.com")); - - $h[] = "name6=value3; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[3], "www.example.com" ); - $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net")); - - $h[] = "name6=value0; domain=.example.net; path=/path/; expires=Mon, 09-Dec-1999 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[4], "www.example.net" ); - $this->assertEquals("", $cj->serializeToHttpRequest("/path/", "www.example.net")); - - $h[] = "name6=value4; domain=.example.net; path=/path/; expires=Mon, 09-Dec-2029 13:46:00 GMT"; - $cj->parseCookieResponseHeader( $h[5], "www.example.net" ); - $this->assertEquals("name6=value4", $cj->serializeToHttpRequest("/path/", "www.example.net")); - } - - function runCookieRequests() { - $r = HttpRequest::factory( "http://www.php.net/manual" ); - $r->execute(); - - $jar = $r->getCookieJar(); - $this->assertThat( $jar, $this->isInstanceOf( 'CookieJar' ) ); - - if ( is_a($r, 'PhpHttpRequest') && version_compare( '5.1.7', phpversion(), '>' ) ) { - $this->markTestSkipped( 'Redirection fails or crashes PHP on 5.1.6 and prior' ); - } - $serialized = $jar->serializeToHttpRequest( "/search?q=test", "www.php.net" ); - $this->assertRegExp( '/\bCOUNTRY=[^=;]+/', $serialized ); - $this->assertRegExp( '/\bLAST_LANG=[^=;]+/', $serialized ); - $this->assertEquals( '', $jar->serializeToHttpRequest( "/search?q=test", "www.php.com" ) ); - } - - function testCookieRequestDefault() { - Http::$httpEngine = false; - self::runCookieRequests(); - } - function testCookieRequestPhp() { - if ( !self::$has_fopen ) { - $this->markTestIncomplete( "This test requires allow_url_fopen=true." ); - } - - Http::$httpEngine = 'php'; - self::runCookieRequests(); - } - function testCookieRequestCurl() { - if ( !self::$has_curl ) { - $this->markTestIncomplete( "This test requires curl." ); - } - - Http::$httpEngine = 'curl'; - self::runCookieRequests(); - } -}
\ No newline at end of file diff --git a/maintenance/tests/IPTest.php b/maintenance/tests/IPTest.php deleted file mode 100644 index 9db77f72..00000000 --- a/maintenance/tests/IPTest.php +++ /dev/null @@ -1,52 +0,0 @@ -<?php -/* - * Tests for IP validity functions. Ported from /t/inc/IP.t by avar. - */ - -class IPTest extends PHPUnit_Framework_TestCase { - - 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" ); - } - } - } - - public function testInvalidIPs() { - 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" ); - } - } - } - - public function testBogusIPs() { - $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 IPv4 address" ); - } - } - - public function testPrivateIPs() { - $private = array( '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" ); - } - } -} diff --git a/maintenance/tests/ImageFunctionsTest.php b/maintenance/tests/ImageFunctionsTest.php deleted file mode 100644 index 9794a2a2..00000000 --- a/maintenance/tests/ImageFunctionsTest.php +++ /dev/null @@ -1,48 +0,0 @@ -<?php - -class ImageFunctionsTest extends PHPUnit_Framework_TestCase { - function testFitBoxWidth() { - $vals = array( - array( - 'width' => 50, - 'height' => 50, - 'tests' => array( - 50 => 50, - 17 => 17, - 18 => 18 ) ), - array( - 'width' => 366, - 'height' => 300, - 'tests' => array( - 50 => 61, - 17 => 21, - 18 => 22 ) ), - array( - 'width' => 300, - 'height' => 366, - 'tests' => array( - 50 => 41, - 17 => 14, - 18 => 15 ) ), - array( - 'width' => 100, - 'height' => 400, - 'tests' => array( - 50 => 12, - 17 => 4, - 18 => 4 ) ) ); - foreach( $vals as $row ) { - extract( $row ); - foreach( $tests as $max => $expected ) { - $y = round( $expected * $height / $width ); - $result = wfFitBoxWidth( $width, $height, $max ); - $y2 = round( $result * $height / $width ); - $this->assertEquals( $expected, - $result, - "($width, $height, $max) wanted: {$expected}x$y, got: {$result}x$y2" ); - } - } - } -} - - diff --git a/maintenance/tests/LanguageConverterTest.php b/maintenance/tests/LanguageConverterTest.php deleted file mode 100644 index 22b396e7..00000000 --- a/maintenance/tests/LanguageConverterTest.php +++ /dev/null @@ -1,148 +0,0 @@ -<?php - -class LanguageConverterTest extends PHPUnit_Framework_TestCase { - protected $lang = null; - protected $lc = null; - - function setUp() { - global $wgMemc, $wgRequest, $wgUser, $wgContLang; - - $wgUser = new User; - $wgRequest = new FauxRequest(array()); - $wgMemc = new FakeMemCachedClient; - $wgContLang = Language::factory( 'tg' ); - $this->lang = new LanguageTest(); - $this->lc = new TestConverter( $this->lang, 'tg', - array( 'tg', 'tg-latn' ) ); - } - - function tearDown() { - global $wgMemc; - unset($wgMemc); - unset($this->lc); - unset($this->lang); - } - - function testGetPreferredVariantDefaults() { - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantHeaders() { - global $wgRequest; - $wgRequest->setHeader('Accept-Language', 'tg-latn'); - - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantHeaderWeight() { - global $wgRequest; - $wgRequest->setHeader('Accept-Language', 'tg;q=1'); - - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantHeaderWeight2() { - global $wgRequest; - $wgRequest->setHeader('Accept-Language', 'tg-latn;q=1'); - - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantHeaderMulti() { - global $wgRequest; - $wgRequest->setHeader('Accept-Language', 'en, tg-latn;q=1'); - - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantUserOption() { - global $wgUser; - - $wgUser = new User; - $wgUser->setId(1); - $wgUser->setOption('variant', 'tg-latn'); - - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantHeaderUserVsUrl() { - global $wgRequest, $wgUser, $wgContLang; - - $wgContLang = Language::factory( 'tg-latn' ); - $wgRequest->setVal('variant', 'tg'); - $wgUser = User::newFromId("admin"); - $wgUser->setId(1); - $wgUser->setOption('variant', 'tg-latn'); // The user's data is ignored - // because the variant is set in the URL. - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(true, true)); - } - - - function testGetPreferredVariantDefaultLanguageVariant() { - global $wgDefaultLanguageVariant; - - $wgDefaultLanguageVariant = 'tg-latn'; - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true)); - } - - function testGetPreferredVariantDefaultLanguageVsUrlVariant() { - global $wgDefaultLanguageVariant, $wgRequest, $wgContLang; - - $wgContLang = Language::factory( 'tg-latn' ); - $wgDefaultLanguageVariant = 'tg'; - $wgRequest->setVal('variant', null); - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, false)); - $this->assertEquals('tg', $this->lc->getPreferredVariant(false, true)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, false)); - $this->assertEquals('tg-latn', $this->lc->getPreferredVariant(true, true)); - } -} - -/** - * 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 LanguageTest extends Language { - function __construct() { - parent::__construct(); - $variants = array( 'tg', 'tg-latn' ); - $this->mConverter = new TestConverter( $this, 'tg', $variants ); - } -} diff --git a/maintenance/tests/LicensesTest.php b/maintenance/tests/LicensesTest.php deleted file mode 100644 index c5357f89..00000000 --- a/maintenance/tests/LicensesTest.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -/** - * @group Broken - */ -class LicensesTest extends PHPUnit_Framework_TestCase { - - function testLicenses() { - $str = " -* Free licenses: -** GFLD|Debian disagrees -"; - - $lc = new Licenses( $str ); - $this->assertTrue( is_a( $lc, 'Licenses' ), 'Correct class' ); - } -}
\ No newline at end of file diff --git a/maintenance/tests/LocalFileTest.php b/maintenance/tests/LocalFileTest.php deleted file mode 100644 index e57798be..00000000 --- a/maintenance/tests/LocalFileTest.php +++ /dev/null @@ -1,97 +0,0 @@ -<?php - -/** - * These tests should work regardless of $wgCapitalLinks - */ - -class LocalFileTest extends PHPUnit_Framework_TestCase { - function setUp() { - global $wgContLang; - $wgContLang = new Language; - $info = array( - 'name' => 'test', - 'directory' => '/testdir', - 'url' => '/testurl', - 'hashLevels' => 2, - 'transformVia404' => false, - ); - $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!' ); - } - - function tearDown() { - global $wgContLang; - unset($wgContLang); - } - - function testGetHashPath() { - $this->assertEquals( '', $this->file_hl0->getHashPath() ); - $this->assertEquals( 'a/a2/', $this->file_hl2->getHashPath() ); - $this->assertEquals( 'c/c4/', $this->file_lc->getHashPath() ); - } - - function testGetRel() { - $this->assertEquals( 'Test!', $this->file_hl0->getRel() ); - $this->assertEquals( 'a/a2/Test!', $this->file_hl2->getRel() ); - $this->assertEquals( 'c/c4/test!', $this->file_lc->getRel() ); - } - - function testGetUrlRel() { - $this->assertEquals( 'Test%21', $this->file_hl0->getUrlRel() ); - $this->assertEquals( 'a/a2/Test%21', $this->file_hl2->getUrlRel() ); - $this->assertEquals( 'c/c4/test%21', $this->file_lc->getUrlRel() ); - } - - function testGetArchivePath() { - $this->assertEquals( '/testdir/archive', $this->file_hl0->getArchivePath() ); - $this->assertEquals( '/testdir/archive/a/a2', $this->file_hl2->getArchivePath() ); - $this->assertEquals( '/testdir/archive/!', $this->file_hl0->getArchivePath( '!' ) ); - $this->assertEquals( '/testdir/archive/a/a2/!', $this->file_hl2->getArchivePath( '!' ) ); - } - - function testGetThumbPath() { - $this->assertEquals( '/testdir/thumb/Test!', $this->file_hl0->getThumbPath() ); - $this->assertEquals( '/testdir/thumb/a/a2/Test!', $this->file_hl2->getThumbPath() ); - $this->assertEquals( '/testdir/thumb/Test!/x', $this->file_hl0->getThumbPath( 'x' ) ); - $this->assertEquals( '/testdir/thumb/a/a2/Test!/x', $this->file_hl2->getThumbPath( 'x' ) ); - } - - function testGetArchiveUrl() { - $this->assertEquals( '/testurl/archive', $this->file_hl0->getArchiveUrl() ); - $this->assertEquals( '/testurl/archive/a/a2', $this->file_hl2->getArchiveUrl() ); - $this->assertEquals( '/testurl/archive/%21', $this->file_hl0->getArchiveUrl( '!' ) ); - $this->assertEquals( '/testurl/archive/a/a2/%21', $this->file_hl2->getArchiveUrl( '!' ) ); - } - - function testGetThumbUrl() { - $this->assertEquals( '/testurl/thumb/Test%21', $this->file_hl0->getThumbUrl() ); - $this->assertEquals( '/testurl/thumb/a/a2/Test%21', $this->file_hl2->getThumbUrl() ); - $this->assertEquals( '/testurl/thumb/Test%21/x', $this->file_hl0->getThumbUrl( 'x' ) ); - $this->assertEquals( '/testurl/thumb/a/a2/Test%21/x', $this->file_hl2->getThumbUrl( 'x' ) ); - } - - function testGetArchiveVirtualUrl() { - $this->assertEquals( 'mwrepo://test/public/archive', $this->file_hl0->getArchiveVirtualUrl() ); - $this->assertEquals( 'mwrepo://test/public/archive/a/a2', $this->file_hl2->getArchiveVirtualUrl() ); - $this->assertEquals( 'mwrepo://test/public/archive/%21', $this->file_hl0->getArchiveVirtualUrl( '!' ) ); - $this->assertEquals( 'mwrepo://test/public/archive/a/a2/%21', $this->file_hl2->getArchiveVirtualUrl( '!' ) ); - } - - function testGetThumbVirtualUrl() { - $this->assertEquals( 'mwrepo://test/thumb/Test%21', $this->file_hl0->getThumbVirtualUrl() ); - $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21', $this->file_hl2->getThumbVirtualUrl() ); - $this->assertEquals( 'mwrepo://test/thumb/Test%21/%21', $this->file_hl0->getThumbVirtualUrl( '!' ) ); - $this->assertEquals( 'mwrepo://test/thumb/a/a2/Test%21/%21', $this->file_hl2->getThumbVirtualUrl( '!' ) ); - } - - function testGetUrl() { - $this->assertEquals( '/testurl/Test%21', $this->file_hl0->getUrl() ); - $this->assertEquals( '/testurl/a/a2/Test%21', $this->file_hl2->getUrl() ); - } -} - - diff --git a/maintenance/tests/Makefile b/maintenance/tests/Makefile deleted file mode 100644 index b2c0fb71..00000000 --- a/maintenance/tests/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# See -# http://lists.wikimedia.org/pipermail/wikitech-l/2010-February/046657.html -# for why prove(1) is the default target and not phpunit(1) - -.PHONY: help test -all test: tap - -tap: - prove -e 'phpunit --tap' *Test*.php - -phpunit: - phpunit - -install: - pear channel-discover pear.phpunit.de - pear install phpunit/PHPUnit - -help: - # Options: - # test (default) Run the tests individually through Test::Harness's prove(1) - # phpunit Run all the tests with phpunit - # install Install PHPUnit from phpunit.de - # help You're looking at it! diff --git a/maintenance/tests/MediaWikiParserTest.php b/maintenance/tests/MediaWikiParserTest.php deleted file mode 100644 index a545b3bb..00000000 --- a/maintenance/tests/MediaWikiParserTest.php +++ /dev/null @@ -1,283 +0,0 @@ -<?php - -if ( !defined( 'MEDIAWIKI' ) ) { - exit; -} - -global $IP; -define( "NO_COMMAND_LINE", 1 ); -define( "PARSER_TESTS", "$IP/maintenance/parserTests.txt" ); - -require_once( "$IP/maintenance/parserTests.inc" ); - -class PHPUnitTestRecorder extends TestRecorder { - - function record( $test, $result ) { - $this->total++; - $this->success += $result; - - } - - function reportPercentage( $success, $total ) {} -} - -class MediaWikiParserTestSuite extends PHPUnit_Framework_TestSuite { -#implements PHPUnit_Framework_SelfDescribing { - static private $count; - static public $parser; - static public $iter; - - public static function suite() { - $suite = new PHPUnit_Framework_TestSuite(); - - self::$iter = new TestFileIterator( PARSER_TESTS ); - - foreach(self::$iter as $i => $test) { - $suite->addTest(new ParserUnitTest($i, $test['test'])); - self::$count++; - } - unset($tests); - - self::$parser = new PTShell; - self::$iter->setParser(self::$parser); - self::$parser->recorder->start(); - self::$parser->setupDatabase(); - self::$iter->rewind(); - /* } */ - /* function setUp() { */ - global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList, - $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, - $wgMessageCache, $wgUseDatabaseMessages, $wgMsgCacheExpiry, $parserMemc, - $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, - $wgNamespacesWithSubpages, $wgThumbnailScriptPath, $wgScriptPath, - $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath; - - $wgScript = '/index.php'; - $wgScriptPath = '/'; - $wgArticlePath = '/wiki/$1'; - $wgStyleSheetPath = '/skins'; - $wgStylePath = '/skins'; - $wgThumbnailScriptPath = false; - $wgLocalFileRepo = array( - 'class' => 'LocalRepo', - 'name' => 'local', - 'directory' => '', - 'url' => 'http://example.com/images', - 'hashLevels' => 2, - 'transformVia404' => false, - ); - $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; - $wgNamespaceAliases['Image'] = NS_FILE; - $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; - - - $wgEnableParserCache = false; - $wgDeferredUpdateList = array(); - $wgMemc =& wfGetMainCache(); - $messageMemc =& wfGetMessageCacheStorage(); - $parserMemc =& wfGetParserCacheStorage(); - - $wgContLang = new StubContLang; - $wgUser = new StubUser; - $wgLang = new StubUserLang; - $wgOut = new StubObject( 'wgOut', 'OutputPage' ); - $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); - $wgRequest = new WebRequest; - - $wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache', - array( $messageMemc, $wgUseDatabaseMessages, - $wgMsgCacheExpiry, wfWikiID() ) ); - if( $wgStyleDirectory === false) $wgStyleDirectory = "$IP/skins"; - - return $suite; - } - - public function tearDown() { - $this->teardownDatabase(); - $this->recorder->report(); - $this->recorder->end(); - $this->teardownUploadDir($this->uploadDir); - } - - public function count() {return self::$count;} - - public function toString() { - return "MediaWiki Parser Tests"; - } - - - private $db; - private $uploadDir; - private $keepUploads; - /** - * Remove the dummy uploads directory - */ - private function teardownUploadDir( $dir ) { - if ( $this->keepUploads ) { - return; - } - - // delete the files first, then the dirs. - self::deleteFiles( - array ( - "$dir/3/3a/Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", - "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", - - "$dir/0/09/Bad.jpg", - ) - ); - - self::deleteDirs( - array ( - "$dir/3/3a", - "$dir/3", - "$dir/thumb/6/65", - "$dir/thumb/6", - "$dir/thumb/3/3a/Foobar.jpg", - "$dir/thumb/3/3a", - "$dir/thumb/3", - - "$dir/0/09/", - "$dir/0/", - - "$dir/thumb", - "$dir", - ) - ); - } - - /** - * Delete the specified files, if they exist. - * @param array $files full paths to files to delete. - */ - private static function deleteFiles( $files ) { - foreach( $files as $file ) { - if( file_exists( $file ) ) { - unlink( $file ); - } - } - } - /** - * Delete the specified directories, if they exist. Must be empty. - * @param array $dirs full paths to directories to delete. - */ - private static function deleteDirs( $dirs ) { - foreach( $dirs as $dir ) { - if( is_dir( $dir ) ) { - rmdir( $dir ); - } - } - } - - /** - * Create a dummy uploads directory which will contain a couple - * of files in order to pass existence tests. - * @return string The directory - */ - private function setupUploadDir() { - global $IP; - if ( $this->keepUploads ) { - $dir = wfTempDir() . '/mwParser-images'; - if ( is_dir( $dir ) ) { - return $dir; - } - } else { - $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images"; - } - - wfDebug( "Creating upload directory $dir\n" ); - if ( file_exists( $dir ) ) { - wfDebug( "Already exists!\n" ); - return $dir; - } - wfMkdirParents( $dir . '/3/3a' ); - copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); - - wfMkdirParents( $dir . '/0/09' ); - copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); - return $dir; - } -} - -class ParserUnitTest extends PHPUnit_Framework_TestCase { - private $number = 0; - private $test = ""; - - public function __construct($number, $test) { - $this->number = $number; - $this->test = $test; - } - - function count() {return 1;} - - public function run(PHPUnit_Framework_TestResult $result = NULL) { - PHPUnit_Framework_Assert::resetCount(); - if ($result === NULL) { - $result = new PHPUnit_Framework_TestResult; - } - - $t = MediaWikiParserTestSuite::$iter->current(); - $k = MediaWikiParserTestSuite::$iter->key(); - - if(!MediaWikiParserTestSuite::$iter->valid()) { - return; - } - - // The only way this should happen is if the parserTest.txt - // file were modified while the script is running. - if($k != $this->number) { - $i = $this->number; - wfDie("I got confused!\n"); - } - - $result->startTest($this); - PHPUnit_Util_Timer::start(); - - $r = false; - try { - $r = MediaWikiParserTestSuite::$parser->runTest( - $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] - ); - PHPUnit_Framework_Assert::assertTrue(true, $t['test']); - } - catch (PHPUnit_Framework_AssertionFailedError $e) { - $result->addFailure($this, $e, PHPUnit_Util_Timer::stop()); - } - catch (Exception $e) { - $result->addError($this, $e, PHPUnit_Util_Timer::stop()); - } - PHPUnit_Framework_Assert::assertTrue(true, $t['test']); - - $result->endTest($this, PHPUnit_Util_Timer::stop()); - - MediaWikiParserTestSuite::$parser->recorder->record($t['test'], $r); - MediaWikiParserTestSuite::$iter->next(); - $this->addToAssertionCount(PHPUnit_Framework_Assert::getCount()); - - return $result; - } - -} - -class PTShell extends ParserTest { - function showTesting( $desc ) { - } - - function showRunFile( $path ) { - } - - function showSuccess( $desc ) { - PHPUnit_Framework_Assert::assertTrue(true, $desc); - return true; - } - - function showFailure( $desc, $expected, $got ) { - PHPUnit_Framework_Assert::assertEquals($expected, $got, $desc); - } - -} - - diff --git a/maintenance/tests/MediaWiki_Setup.php b/maintenance/tests/MediaWiki_Setup.php deleted file mode 100644 index e7acc338..00000000 --- a/maintenance/tests/MediaWiki_Setup.php +++ /dev/null @@ -1,28 +0,0 @@ -<?php - -abstract class MediaWiki_Setup extends PHPUnit_Framework_TestCase { - - protected function buildTestDatabase( $tables ) { - global $wgDBprefix; - - $db = wfGetDB( DB_MASTER ); - $oldTableNames = array(); - foreach( $tables as $table ) - $oldTableNames[$table] = $db->tableName( $table ); - $db->tablePrefix( 'parsertest_' ); - - if( $db->isOpen() ) { - foreach ( $tables as $tbl ) { - $newTableName = $db->tableName( $tbl ); - $tableName = $oldTableNames[$tbl]; - $db->query( "DROP TABLE IF EXISTS $newTableName", __METHOD__ ); - $db->duplicateTableStructure( $tableName, $newTableName, __METHOD__ ); - } - return $db; - } else { - // Something amiss - return null; - } - } -} - diff --git a/maintenance/tests/README b/maintenance/tests/README deleted file mode 100644 index b52e790e..00000000 --- a/maintenance/tests/README +++ /dev/null @@ -1,24 +0,0 @@ -Some quickie unit tests done with the PHPUnit testing framework. To run the -test suite, run 'make test' in this dir. This directly invokes 'phpunit.' - -PHPUnit is no longer maintained by PEAR. To get the current version of -PHPUnit, first uninstall any old version of PHPUnit or PHPUnit2 from PEAR, -then install the current version from phpunit.de like this: - -# pear channel-discover pear.phpunit.de -# pear install phpunit/PHPUnit - -You also may wish to install this via your normal package mechanism: - -# aptitude install phpunit - - or - -# yum install phpunit - -Notes: -- Label currently broken tests in the group Broken and they will not - be run by phpunit. You can add them to the group by putting the - following comment at the top of the file: - /** - * @group Broken - */ -- Need to fix some broken tests diff --git a/maintenance/tests/RevisionTest.php b/maintenance/tests/RevisionTest.php deleted file mode 100644 index 78fcc7c3..00000000 --- a/maintenance/tests/RevisionTest.php +++ /dev/null @@ -1,114 +0,0 @@ -<?php - -class RevisionTest extends PHPUnit_Framework_TestCase { - var $saveGlobals = array(); - - function setUp() { - global $wgContLang; - $wgContLang = Language::factory( 'en' ); - $globalSet = array( - 'wgLegacyEncoding' => false, - 'wgCompressRevisions' => false, - 'wgInputEncoding' => 'utf-8', - 'wgOutputEncoding' => 'utf-8' ); - foreach( $globalSet as $var => $data ) { - $this->saveGlobals[$var] = $GLOBALS[$var]; - $GLOBALS[$var] = $data; - } - } - - function tearDown() { - foreach( $this->saveGlobals as $var => $data ) { - $GLOBALS[$var] = $data; - } - } - - function testGetRevisionText() { - $row = new stdClass; - $row->old_flags = ''; - $row->old_text = 'This is a bunch of revision text.'; - $this->assertEquals( - 'This is a bunch of revision text.', - Revision::getRevisionText( $row ) ); - } - - function testGetRevisionTextGzip() { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( 'This is a bunch of revision text.' ); - $this->assertEquals( - 'This is a bunch of revision text.', - Revision::getRevisionText( $row ) ); - } - - function testGetRevisionTextUtf8Native() { - $row = new stdClass; - $row->old_flags = 'utf-8'; - $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } - - function testGetRevisionTextUtf8Legacy() { - $row = new stdClass; - $row->old_flags = ''; - $row->old_text = "Wiki est l'\xe9cole superieur !"; - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } - - function testGetRevisionTextUtf8NativeGzip() { - $row = new stdClass; - $row->old_flags = 'gzip,utf-8'; - $row->old_text = gzdeflate( "Wiki est l'\xc3\xa9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } - - function testGetRevisionTextUtf8LegacyGzip() { - $row = new stdClass; - $row->old_flags = 'gzip'; - $row->old_text = gzdeflate( "Wiki est l'\xe9cole superieur !" ); - $GLOBALS['wgLegacyEncoding'] = 'iso-8859-1'; - $this->assertEquals( - "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ) ); - } - - function testCompressRevisionTextUtf8() { - $row = new stdClass; - $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; - $row->old_flags = Revision::compressRevisionText( $row->old_text ); - $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ), - "Flags should contain 'utf-8'" ); - $this->assertFalse( false !== strpos( $row->old_flags, 'gzip' ), - "Flags should not contain 'gzip'" ); - $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", - $row->old_text, "Direct check" ); - $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ), "getRevisionText" ); - } - - function testCompressRevisionTextUtf8Gzip() { - $GLOBALS['wgCompressRevisions'] = true; - $row = new stdClass; - $row->old_text = "Wiki est l'\xc3\xa9cole superieur !"; - $row->old_flags = Revision::compressRevisionText( $row->old_text ); - $this->assertTrue( false !== strpos( $row->old_flags, 'utf-8' ), - "Flags should contain 'utf-8'" ); - $this->assertTrue( false !== strpos( $row->old_flags, 'gzip' ), - "Flags should contain 'gzip'" ); - $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", - gzinflate( $row->old_text ), "Direct check" ); - $this->assertEquals( "Wiki est l'\xc3\xa9cole superieur !", - Revision::getRevisionText( $row ), "getRevisionText" ); - } -} - - diff --git a/maintenance/tests/RunSeleniumTests.php b/maintenance/tests/RunSeleniumTests.php new file mode 100644 index 00000000..2574f4b2 --- /dev/null +++ b/maintenance/tests/RunSeleniumTests.php @@ -0,0 +1,220 @@ +#!/usr/bin/php +<?php +/** + * @file + * @ingroup Maintenance + * @copyright Copyright © Wikimedia Deuschland, 2009 + * @author Hallo Welt! Medienwerkstatt GmbH + * @author Markus Glaser, Dan Nessett, Priyanka Dhanda + * initial idea by Daniel Kinzler + * + * 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 + */ + +define( 'SELENIUMTEST', true ); + +require_once( dirname( dirname( __FILE__ ) )."/Maintenance.php" ); +require_once( 'PHPUnit/Framework.php' ); +require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); +include_once( 'PHPUnit/Util/Log/JUnit.php' ); +require_once( dirname( __FILE__ ) . "/selenium/SeleniumServerManager.php" ); + +class SeleniumTester extends Maintenance { + protected $selenium; + protected $serverManager; + protected $seleniumServerExecPath; + + public function __construct() { + parent::__construct(); + $this->mDescription = "Selenium Test Runner. For documentation, visit http://www.mediawiki.org/wiki/SeleniumFramework"; + $this->addOption( 'port', 'Port used by selenium server. Default: 4444', false, true ); + $this->addOption( 'host', 'Host selenium server. Default: $wgServer . $wgScriptPath', false, true ); + $this->addOption( 'testBrowser', 'The browser used during testing. Default: firefox', false, true ); + $this->addOption( 'wikiUrl', 'The Mediawiki installation to point to. Default: http://localhost', false, true ); + $this->addOption( 'username', 'The login username for sunning tests. Default: empty', false, true ); + $this->addOption( 'userPassword', 'The login password for running tests. Default: empty', false, true ); + $this->addOption( 'seleniumConfig', 'Location of the selenium config file. Default: empty', false, true ); + $this->addOption( 'list-browsers', 'List the available browsers.' ); + $this->addOption( 'verbose', 'Be noisier.' ); + $this->addOption( 'startserver', 'Start Selenium Server (on localhost) before the run.' ); + $this->addOption( 'stopserver', 'Stop Selenium Server (on localhost) after the run.' ); + $this->addOption( 'jUnitLogFile', 'Log results in a specified JUnit log file. Default: empty', false, true ); + $this->addOption( 'runAgainstGrid', 'The test will be run against a Selenium Grid. Default: false.', false, true ); + $this->deleteOption( 'dbpass' ); + $this->deleteOption( 'dbuser' ); + $this->deleteOption( 'globals' ); + $this->deleteOption( 'wiki' ); + } + + public function listBrowsers() { + $desc = "Available browsers:\n"; + + foreach ($this->selenium->getAvailableBrowsers() as $k => $v) { + $desc .= " $k => $v\n"; + } + + echo $desc; + } + + protected function startServer() { + if ( $this->seleniumServerExecPath == '' ) { + die ( "The selenium server exec path is not set in " . + "selenium_settings.ini. Cannot start server \n" . + "as requested - terminating RunSeleniumTests\n" ); + } + $this->serverManager = new SeleniumServerManager( 'true', + $this->selenium->getPort(), + $this->seleniumServerExecPath ); + switch ( $this->serverManager->start() ) { + case 'started': + break; + case 'failed': + die ( "Unable to start the Selenium Server - " . + "terminating RunSeleniumTests\n" ); + case 'running': + echo ( "Warning: The Selenium Server is " . + "already running\n" ); + break; + } + + return; + } + + protected function stopServer() { + if ( !isset ( $this->serverManager ) ) { + echo ( "Warning: Request to stop Selenium Server, but it was " . + "not stared by RunSeleniumTests\n" . + "RunSeleniumTests cannot stop a Selenium Server it " . + "did not start\n" ); + } else { + switch ( $this->serverManager->stop() ) { + case 'stopped': + break; + case 'failed': + echo ( "unable to stop the Selenium Server\n" ); + } + } + return; + } + + protected function runTests( $seleniumTestSuites = array() ) { + $result = new PHPUnit_Framework_TestResult; + $result->addListener( new SeleniumTestListener( $this->selenium->getLogger() ) ); + if ( $this->selenium->getJUnitLogFile() ) { + $jUnitListener = new PHPUnit_Util_Log_JUnit( $this->selenium->getJUnitLogFile(), true ); + $result->addListener( $jUnitListener ); + } + + foreach ( $seleniumTestSuites as $testSuiteName => $testSuiteFile ) { + require( $testSuiteFile ); + $suite = new $testSuiteName(); + $suite->setName( $testSuiteName ); + $suite->addTests(); + + try { + $suite->run( $result ); + } catch ( Testing_Selenium_Exception $e ) { + $suite->tearDown(); + throw new MWException( $e->getMessage() ); + } + } + + if ( $this->selenium->getJUnitLogFile() ) { + $jUnitListener->flush(); + } + } + + public function execute() { + global $wgServer, $wgScriptPath, $wgHooks; + + $seleniumSettings = array(); + $seleniumBrowsers = array(); + $seleniumTestSuites = array(); + + $configFile = $this->getOption( 'seleniumConfig', '' ); + if ( strlen( $configFile ) > 0 ) { + $this->output("Using Selenium Configuration file: " . $configFile . "\n"); + SeleniumConfig::getSeleniumSettings( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites, + $configFile ); + } else if ( !isset( $wgHooks['SeleniumSettings'] ) ) { + $this->output("No command line configuration file or configuration hook found.\n"); + SeleniumConfig::getSeleniumSettings( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites + ); + } else { + $this->output("Using 'SeleniumSettings' hook for configuration.\n"); + wfRunHooks('SeleniumSettings', array( $seleniumSettings, + $seleniumBrowsers, + $seleniumTestSuites ) ); + } + + // State for starting/stopping the Selenium server has nothing to do with the Selenium + // class. Keep this state local to SeleniumTester class. Using getOption() is clumsy, but + // the Maintenance class does not have a setOption() + if ( isset( $seleniumSettings['startserver'] ) ) $this->getOption( 'startserver', true ); + if ( isset( $seleniumSettings['stopserver'] ) ) $this->getOption( 'stopserver', true ); + if ( !isset( $seleniumSettings['seleniumserverexecpath'] ) ) $seleniumSettings['seleniumserverexecpath'] = ''; + $this->seleniumServerExecPath = $seleniumSettings['seleniumserverexecpath']; + + //set reasonable defaults if we did not find the settings + if ( !isset( $seleniumBrowsers ) ) $seleniumBrowsers = array ('firefox' => '*firefox'); + if ( !isset( $seleniumSettings['host'] ) ) $seleniumSettings['host'] = $wgServer . $wgScriptPath; + if ( !isset( $seleniumSettings['port'] ) ) $seleniumSettings['port'] = '4444'; + if ( !isset( $seleniumSettings['wikiUrl'] ) ) $seleniumSettings['wikiUrl'] = 'http://localhost'; + if ( !isset( $seleniumSettings['username'] ) ) $seleniumSettings['username'] = ''; + if ( !isset( $seleniumSettings['userPassword'] ) ) $seleniumSettings['userPassword'] = ''; + if ( !isset( $seleniumSettings['testBrowser'] ) ) $seleniumSettings['testBrowser'] = 'firefox'; + if ( !isset( $seleniumSettings['jUnitLogFile'] ) ) $seleniumSettings['jUnitLogFile'] = false; + if ( !isset( $seleniumSettings['runAgainstGrid'] ) ) $seleniumSettings['runAgainstGrid'] = false; + + // Setup Selenium class + $this->selenium = new Selenium( ); + $this->selenium->setAvailableBrowsers( $seleniumBrowsers ); + $this->selenium->setRunAgainstGrid( $this->getOption( 'runAgainstGrid', $seleniumSettings['runAgainstGrid'] ) ); + $this->selenium->setUrl( $this->getOption( 'wikiUrl', $seleniumSettings['wikiUrl'] ) ); + $this->selenium->setBrowser( $this->getOption( 'testBrowser', $seleniumSettings['testBrowser'] ) ); + $this->selenium->setPort( $this->getOption( 'port', $seleniumSettings['port'] ) ); + $this->selenium->setHost( $this->getOption( 'host', $seleniumSettings['host'] ) ); + $this->selenium->setUser( $this->getOption( 'username', $seleniumSettings['username'] ) ); + $this->selenium->setPass( $this->getOption( 'userPassword', $seleniumSettings['userPassword'] ) ); + $this->selenium->setVerbose( $this->hasOption( 'verbose' ) ); + $this->selenium->setJUnitLogFile( $this->getOption( 'jUnitLogFile', $seleniumSettings['jUnitLogFile'] ) ); + + if( $this->hasOption( 'list-browsers' ) ) { + $this->listBrowsers(); + exit(0); + } + if ( $this->hasOption( 'startserver' ) ) { + $this->startServer(); + } + + $logger = new SeleniumTestConsoleLogger; + $this->selenium->setLogger( $logger ); + + $this->runTests( $seleniumTestSuites ); + + if ( $this->hasOption( 'stopserver' ) ) { + $this->stopServer(); + } + } +} + +$maintClass = "SeleniumTester"; + +require_once( DO_MAINTENANCE ); diff --git a/maintenance/tests/SanitizerTest.php b/maintenance/tests/SanitizerTest.php deleted file mode 100644 index 8a2287d5..00000000 --- a/maintenance/tests/SanitizerTest.php +++ /dev/null @@ -1,73 +0,0 @@ -<?php - - -class SanitizerTest extends PHPUnit_Framework_TestCase { - - function setUp() { - AutoLoader::loadClass( 'Sanitizer' ); - } - - function testDecodeNamedEntities() { - $this->assertEquals( - "\xc3\xa9cole", - Sanitizer::decodeCharReferences( 'école' ), - 'decode named entities' - ); - } - - function testDecodeNumericEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole!", - Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), - 'decode numeric entities' - ); - } - - function testDecodeMixedEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole!", - Sanitizer::decodeCharReferences( "Ĉio bonas dans l'école!" ), - 'decode mixed numeric/named entities' - ); - } - - function testDecodeMixedComplexEntities() { - $this->assertEquals( - "\xc4\x88io bonas dans l'\xc3\xa9cole! (mais pas Ĉio dans l'école)", - Sanitizer::decodeCharReferences( - "Ĉio bonas dans l'école! (mais pas &#x108;io dans l'&eacute;cole)" - ), - 'decode mixed complex entities' - ); - } - - function testInvalidAmpersand() { - $this->assertEquals( - 'a & b', - Sanitizer::decodeCharReferences( 'a & b' ), - 'Invalid ampersand' - ); - } - - function testInvalidEntities() { - $this->assertEquals( - '&foo;', - Sanitizer::decodeCharReferences( '&foo;' ), - 'Invalid named entity' - ); - } - - function testInvalidNumberedEntities() { - $this->assertEquals( UTF8_REPLACEMENT, Sanitizer::decodeCharReferences( "�" ), 'Invalid numbered entity' ); - } - - function testSelfClosingTag() { - $GLOBALS['wgUseTidy'] = false; - $this->assertEquals( - '<div>Hello world</div>', - Sanitizer::removeHTMLtags( '<div>Hello world</div />' ), - 'Self-closing closing div' - ); - } -} - diff --git a/maintenance/tests/SearchEngineTest.php b/maintenance/tests/SearchEngineTest.php deleted file mode 100644 index 0cae2d42..00000000 --- a/maintenance/tests/SearchEngineTest.php +++ /dev/null @@ -1,138 +0,0 @@ -<?php - -require_once( 'MediaWiki_Setup.php' ); - -/** - * @group Stub - */ -class SearchEngineTest extends MediaWiki_Setup { - var $db, $search; - - function insertSearchData() { - $this->db->safeQuery( <<<SQL - INSERT INTO ! (page_id,page_namespace,page_title,page_latest) - VALUES (1, 0, 'Main_Page', 1), - (2, 1, 'Main_Page', 2), - (3, 0, 'Smithee', 3), - (4, 1, 'Smithee', 4), - (5, 0, 'Unrelated_page', 5), - (6, 0, 'Another_page', 6), - (7, 4, 'Help', 7), - (8, 0, 'Thppt', 8), - (9, 0, 'Alan_Smithee', 9), - (10, 0, 'Pages', 10) -SQL - , $this->db->tableName( 'page' ) ); - $this->db->safeQuery( <<<SQL - INSERT INTO ! (rev_id,rev_page) - VALUES (1, 1), - (2, 2), - (3, 3), - (4, 4), - (5, 5), - (6, 6), - (7, 7), - (8, 8), - (9, 9), - (10, 10) -SQL - , $this->db->tableName( 'revision' ) ); - $this->db->safeQuery( <<<SQL - INSERT INTO ! (old_id,old_text) - VALUES (1, 'This is a main page'), - (2, 'This is a talk page to the main page, see [[smithee]]'), - (3, 'A smithee is one who smiths. See also [[Alan Smithee]]'), - (4, 'This article sucks.'), - (5, 'Nothing in this page is about the S word.'), - (6, 'This page also is unrelated.'), - (7, 'Help me!'), - (8, 'Blah blah'), - (9, 'yum'), - (10,'are food') -SQL - , $this->db->tableName( 'text' ) ); - $this->db->safeQuery( <<<SQL - INSERT INTO ! (si_page,si_title,si_text) - VALUES (1, 'main page', 'this is a main page'), - (2, 'main page', 'this is a talk page to the main page, see smithee'), - (3, 'smithee', 'a smithee is one who smiths see also alan smithee'), - (4, 'smithee', 'this article sucks'), - (5, 'unrelated page', 'nothing in this page is about the s word'), - (6, 'another page', 'this page also is unrelated'), - (7, 'help', 'help me'), - (8, 'thppt', 'blah blah'), - (9, 'alan smithee', 'yum'), - (10, 'pages', 'are food') -SQL - , $this->db->tableName( 'searchindex' ) ); - } - - function fetchIds( $results ) { - $matches = array(); - while( $row = $results->next() ) { - $matches[] = $row->getTitle()->getPrefixedText(); - } - $results->free(); - # Search is not guaranteed to return results in a certain order; - # sort them numerically so we will compare simply that we received - # the expected matches. - sort( $matches ); - return $matches; - } - - function testTextSearch() { - if( is_null( $this->db ) ) { - $this->markTestIncomplete( "Can't find a database to test with." ); - } - $this->assertEquals( - array( 'Smithee' ), - $this->fetchIds( $this->search->searchText( 'smithee' ) ), - "Plain search failed" ); - } - - function testTextPowerSearch() { - if( is_null( $this->db ) ) { - $this->markTestIncomplete( "Can't find a database to test with." ); - } - $this->search->setNamespaces( array( 0, 1, 4 ) ); - $this->assertEquals( - array( - 'Smithee', - 'Talk:Main Page', - ), - $this->fetchIds( $this->search->searchText( 'smithee' ) ), - "Power search failed" ); - } - - function testTitleSearch() { - if( is_null( $this->db ) ) { - $this->markTestIncomplete( "Can't find a database to test with." ); - } - $this->assertEquals( - array( - 'Alan Smithee', - 'Smithee', - ), - $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), - "Title search failed" ); - } - - function testTextTitlePowerSearch() { - if( is_null( $this->db ) ) { - $this->markTestIncomplete( "Can't find a database to test with." ); - } - $this->search->setNamespaces( array( 0, 1, 4 ) ); - $this->assertEquals( - array( - 'Alan Smithee', - 'Smithee', - 'Talk:Smithee', - ), - $this->fetchIds( $this->search->searchTitle( 'smithee' ) ), - "Title power search failed" ); - } - -} - - - diff --git a/maintenance/tests/SearchMySQLTest.php b/maintenance/tests/SearchMySQLTest.php deleted file mode 100644 index 526f6216..00000000 --- a/maintenance/tests/SearchMySQLTest.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -require_once( 'SearchEngineTest.php' ); - -class SearchMySQLTest extends SearchEngineTest { - var $db; - - function setUp() { - $GLOBALS['wgContLang'] = new Language; - $this->db = $this->buildTestDatabase( - array( 'page', 'revision', 'text', 'searchindex', 'user' ) ); - if( $this->db ) { - $this->insertSearchData(); - } - $this->search = new SearchMySQL( $this->db ); - } - - function tearDown() { - if( !is_null( $this->db ) ) { - wfGetLB()->closeConnecton( $this->db ); - } - unset( $this->db ); - unset( $this->search ); - } -} - - diff --git a/maintenance/tests/SearchUpdateTest.php b/maintenance/tests/SearchUpdateTest.php deleted file mode 100644 index d21319a4..00000000 --- a/maintenance/tests/SearchUpdateTest.php +++ /dev/null @@ -1,103 +0,0 @@ -<?php - -class DatabaseMock extends DatabaseBase { - function __construct( $server = false, $user = false, $password = false, $dbName = false, - $failFunction = false, $flags = 0, $tablePrefix = 'get from global' ) - { - $this->mConn = true; - $this->mOpened = true; - } - - function open( $server, $user, $password, $dbName ) { return true; } - function doQuery( $sql ) {} - function fetchObject( $res ) {} - function fetchRow( $res ) {} - function numRows( $res ) {} - function numFields( $res ) {} - function fieldName( $res, $n ) {} - function insertId() {} - function dataSeek( $res, $row ) {} - function lastErrno() { return 0; } - function lastError() { return ''; } - function affectedRows() {} - function fieldInfo( $table, $field ) {} - function strencode( $s ) {} - function getSoftwareLink() {} - function getServerVersion() {} - function getType() {} -} - -class MockSearch extends SearchEngine { - public static $id; - public static $title; - public static $text; - - public function __construct( $db ) { - } - - public function update( $id, $title, $text ) { - self::$id = $id; - self::$title = $title; - self::$text = $text; - } -} - -class SearchUpdateTest extends PHPUnit_Framework_TestCase { - - function update( $text, $title = 'Test', $id = 1 ) { - $u = new SearchUpdate( $id, $title, $text ); - $u->doUpdate(); - return array( MockSearch::$title, MockSearch::$text ); - } - - function updateText( $text ) { - list( $title, $resultText ) = $this->update( $text ); - $resultText = trim( $resultText ); // abstract from some implementation details - return $resultText; - } - - function setUp() { - global $wgSearchType, $wgDBtype, $wgLBFactoryConf, $wgDBservers; - $wgSearchType = 'MockSearch'; - $wgDBtype = 'mock'; - $wgLBFactoryConf['class'] = 'LBFactory_Simple'; - $wgDBservers = null; - LBFactory::destroyInstance(); - } - - function tearDown() { - LBFactory::destroyInstance(); - } - - function testUpdateText() { - $this->assertEquals( - 'test', - $this->updateText( '<div>TeSt</div>' ), - 'HTML stripped, text lowercased' - ); - - $this->assertEquals( - 'foo bar boz quux', - $this->updateText( <<<EOT -<table style="color:red; font-size:100px"> - <tr class="scary"><td><div>foo</div></td><tr>bar</td></tr> - <tr><td>boz</td><tr>quux</td></tr> -</table> -EOT - ), 'Stripping HTML tables' ); - - $this->assertEquals( - 'a b', - $this->updateText( 'a > b' ), - 'Handle unclosed tags' - ); - - $text = str_pad( "foo <barbarbar \n", 10000, 'x' ); - - $this->assertNotEquals( - '', - $this->updateText( $text ), - 'Bug 18609' - ); - } -} diff --git a/maintenance/tests/SiteConfigurationTest.php b/maintenance/tests/SiteConfigurationTest.php deleted file mode 100644 index 791b6fe5..00000000 --- a/maintenance/tests/SiteConfigurationTest.php +++ /dev/null @@ -1,311 +0,0 @@ -<?php - -function getSiteParams( $conf, $wiki ) { - $site = null; - $lang = null; - foreach( $conf->suffixes as $suffix ) { - if ( substr( $wiki, -strlen( $suffix ) ) == $suffix ) { - $site = $suffix; - $lang = substr( $wiki, 0, -strlen( $suffix ) ); - break; - } - } - return array( - 'suffix' => $site, - 'lang' => $lang, - 'params' => array( - 'lang' => $lang, - 'site' => $site, - 'wiki' => $wiki, - ), - 'tags' => array( 'tag' ), - ); -} - -class SiteConfigurationTest extends PHPUnit_Framework_TestCase { - var $mConf; - - function setUp() { - $this->mConf = new SiteConfiguration; - - $this->mConf->suffixes = array( 'wiki' ); - $this->mConf->wikis = array( 'enwiki', 'dewiki', 'frwiki' ); - $this->mConf->settings = array( - 'simple' => array( - 'wiki' => 'wiki', - 'tag' => 'tag', - 'enwiki' => 'enwiki', - 'dewiki' => 'dewiki', - 'frwiki' => 'frwiki', - ), - - 'fallback' => array( - 'default' => 'default', - 'wiki' => 'wiki', - 'tag' => 'tag', - ), - - 'params' => array( - 'default' => '$lang $site $wiki', - ), - - '+global' => array( - 'wiki' => array( - 'wiki' => 'wiki', - ), - 'tag' => array( - 'tag' => 'tag', - ), - 'enwiki' => array( - 'enwiki' => 'enwiki', - ), - 'dewiki' => array( - 'dewiki' => 'dewiki', - ), - 'frwiki' => array( - 'frwiki' => 'frwiki', - ), - ), - - 'merge' => array( - '+wiki' => array( - 'wiki' => 'wiki', - ), - '+tag' => array( - 'tag' => 'tag', - ), - 'default' => array( - 'default' => 'default', - ), - '+enwiki' => array( - 'enwiki' => 'enwiki', - ), - '+dewiki' => array( - 'dewiki' => 'dewiki', - ), - '+frwiki' => array( - 'frwiki' => 'frwiki', - ), - ), - ); - - $GLOBALS['global'] = array( 'global' => 'global' ); - } - - - function testSiteFromDB() { - $this->assertEquals( - array( 'wikipedia', 'en' ), - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB()' - ); - $this->assertEquals( - array( 'wikipedia', '' ), - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() on a suffix' - ); - $this->assertEquals( - array( null, null ), - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki' - ); - - $this->mConf->suffixes = array( 'wiki', '' ); - $this->assertEquals( - array( '', 'wikien' ), - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() on a non-existing wiki (2)' - ); - } - - function testGetLocalDatabases() { - $this->assertEquals( - array( 'enwiki', 'dewiki', 'frwiki' ), - $this->mConf->getLocalDatabases(), - 'getLocalDatabases()' - ); - } - - function testGet() { - $this->assertEquals( - 'enwiki', - $this->mConf->get( 'simple', 'enwiki', 'wiki' ), - 'get(): simple setting on an existing wiki' - ); - $this->assertEquals( - 'dewiki', - $this->mConf->get( 'simple', 'dewiki', 'wiki' ), - 'get(): simple setting on an existing wiki (2)' - ); - $this->assertEquals( - 'frwiki', - $this->mConf->get( 'simple', 'frwiki', 'wiki' ), - 'get(): simple setting on an existing wiki (3)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'simple', 'wiki', 'wiki' ), - 'get(): simple setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'simple', 'eswiki', 'wiki' ), - 'get(): simple setting on an non-existing wiki' - ); - - $this->assertEquals( - 'wiki', - $this->mConf->get( 'fallback', 'enwiki', 'wiki' ), - 'get(): fallback setting on an existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'fallback', 'dewiki', 'wiki', array(), array( 'tag' ) ), - 'get(): fallback setting on an existing wiki (with wiki tag)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'fallback', 'wiki', 'wiki' ), - 'get(): fallback setting on an suffix' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'fallback', 'wiki', 'wiki', array(), array( 'tag' ) ), - 'get(): fallback setting on an suffix (with wiki tag)' - ); - $this->assertEquals( - 'wiki', - $this->mConf->get( 'fallback', 'eswiki', 'wiki' ), - 'get(): fallback setting on an non-existing wiki' - ); - $this->assertEquals( - 'tag', - $this->mConf->get( 'fallback', 'eswiki', 'wiki', array(), array( 'tag' ) ), - 'get(): fallback setting on an non-existing wiki (with wiki tag)' - ); - - $common = array( 'wiki' => 'wiki', 'default' => 'default' ); - $commonTag = array( 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ); - $this->assertEquals( - array( 'enwiki' => 'enwiki' ) + $common, - $this->mConf->get( 'merge', 'enwiki', 'wiki' ), - 'get(): merging setting on an existing wiki' - ); - $this->assertEquals( - array( 'enwiki' => 'enwiki' ) + $commonTag, - $this->mConf->get( 'merge', 'enwiki', 'wiki', array(), array( 'tag' ) ), - 'get(): merging setting on an existing wiki (with tag)' - ); - $this->assertEquals( - array( 'dewiki' => 'dewiki' ) + $common, - $this->mConf->get( 'merge', 'dewiki', 'wiki' ), - 'get(): merging setting on an existing wiki (2)' - ); - $this->assertEquals( - array( 'dewiki' => 'dewiki' ) + $commonTag, - $this->mConf->get( 'merge', 'dewiki', 'wiki', array(), array( 'tag' ) ), - 'get(): merging setting on an existing wiki (2) (with tag)' - ); - $this->assertEquals( - array( 'frwiki' => 'frwiki' ) + $common, - $this->mConf->get( 'merge', 'frwiki', 'wiki' ), - 'get(): merging setting on an existing wiki (3)' - ); - $this->assertEquals( - array( 'frwiki' => 'frwiki' ) + $commonTag, - $this->mConf->get( 'merge', 'frwiki', 'wiki', array(), array( 'tag' ) ), - 'get(): merging setting on an existing wiki (3) (with tag)' - ); - $this->assertEquals( - array( 'wiki' => 'wiki' ) + $common, - $this->mConf->get( 'merge', 'wiki', 'wiki' ), - 'get(): merging setting on an suffix' - ); - $this->assertEquals( - array( 'wiki' => 'wiki' ) + $commonTag, - $this->mConf->get( 'merge', 'wiki', 'wiki', array(), array( 'tag' ) ), - 'get(): merging setting on an suffix (with tag)' - ); - $this->assertEquals( - $common, - $this->mConf->get( 'merge', 'eswiki', 'wiki' ), - 'get(): merging setting on an non-existing wiki' - ); - $this->assertEquals( - $commonTag, - $this->mConf->get( 'merge', 'eswiki', 'wiki', array(), array( 'tag' ) ), - 'get(): merging setting on an non-existing wiki (with tag)' - ); - } - - function testSiteFromDBWithCallback() { - $this->mConf->siteParamsCallback = 'getSiteParams'; - - $this->assertEquals( - array( 'wiki', 'en' ), - $this->mConf->siteFromDB( 'enwiki' ), - 'siteFromDB() with callback' - ); - $this->assertEquals( - array( 'wiki', '' ), - $this->mConf->siteFromDB( 'wiki' ), - 'siteFromDB() with callback on a suffix' - ); - $this->assertEquals( - array( null, null ), - $this->mConf->siteFromDB( 'wikien' ), - 'siteFromDB() with callback on a non-existing wiki' - ); - } - - function testParamReplacement() { - $this->mConf->siteParamsCallback = 'getSiteParams'; - - $this->assertEquals( - 'en wiki enwiki', - $this->mConf->get( 'params', 'enwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki' - ); - $this->assertEquals( - 'de wiki dewiki', - $this->mConf->get( 'params', 'dewiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (2)' - ); - $this->assertEquals( - 'fr wiki frwiki', - $this->mConf->get( 'params', 'frwiki', 'wiki' ), - 'get(): parameter replacement on an existing wiki (3)' - ); - $this->assertEquals( - ' wiki wiki', - $this->mConf->get( 'params', 'wiki', 'wiki' ), - 'get(): parameter replacement on an suffix' - ); - $this->assertEquals( - 'es wiki eswiki', - $this->mConf->get( 'params', 'eswiki', 'wiki' ), - 'get(): parameter replacement on an non-existing wiki' - ); - } - - function testGetAll() { - $this->mConf->siteParamsCallback = 'getSiteParams'; - - $getall = array( - 'simple' => 'enwiki', - 'fallback' => 'tag', - 'params' => 'en wiki enwiki', - 'global' => array( 'enwiki' => 'enwiki' ) + $GLOBALS['global'], - 'merge' => array( 'enwiki' => 'enwiki', 'tag' => 'tag', 'wiki' => 'wiki', 'default' => 'default' ), - ); - $this->assertEquals( $getall, $this->mConf->getAll( 'enwiki' ), 'getAll()' ); - - $this->mConf->extractAllGlobals( 'enwiki', 'wiki' ); - - $this->assertEquals( $getall['simple'], $GLOBALS['simple'], 'extractAllGlobals(): simple setting' ); - $this->assertEquals( $getall['fallback'], $GLOBALS['fallback'], 'extractAllGlobals(): fallback setting' ); - $this->assertEquals( $getall['params'], $GLOBALS['params'], 'extractAllGlobals(): parameter replacement' ); - $this->assertEquals( $getall['global'], $GLOBALS['global'], 'extractAllGlobals(): merging with global' ); - $this->assertEquals( $getall['merge'], $GLOBALS['merge'], 'extractAllGlobals(): merging setting' ); - } -} diff --git a/maintenance/tests/TimeAdjustTest.php b/maintenance/tests/TimeAdjustTest.php deleted file mode 100644 index bbd697bf..00000000 --- a/maintenance/tests/TimeAdjustTest.php +++ /dev/null @@ -1,40 +0,0 @@ -<?php - -class TimeAdjustTest extends PHPUnit_Framework_TestCase { - - public function setUp() { - $this->iniSet( 'precision', 15 ); - } - - # Test offset usage for a given language::userAdjust - function testUserAdjust() { - global $wgLocalTZoffset, $wgContLang, $wgUser; - - $wgContLang = $en = Language::factory( 'en' ); - - # Collection of parameters for Language_t_Offset. - # Format: date to be formatted, localTZoffset value, expected date - $userAdjust_tests = array( - array( 20061231235959, 0, 20061231235959 ), - array( 20061231235959, 5, 20070101000459 ), - array( 20061231235959, 15, 20070101001459 ), - array( 20061231235959, 60, 20070101005959 ), - array( 20061231235959, 90, 20070101012959 ), - array( 20061231235959, 120, 20070101015959 ), - array( 20061231235959, 540, 20070101085959 ), - array( 20061231235959, -5, 20061231235459 ), - array( 20061231235959, -30, 20061231232959 ), - array( 20061231235959, -60, 20061231225959 ), - ); - - foreach( $userAdjust_tests as $data ) { - $wgLocalTZoffset = $data[1]; - - $this->assertEquals( - strval( $data[2] ), - strval( $en->userAdjust( $data[0], '' ) ), - "User adjust {$data[0]} by {$data[1]} minutes should give {$data[2]}" - ); - } - } -} diff --git a/maintenance/tests/TitleTest.php b/maintenance/tests/TitleTest.php deleted file mode 100644 index 5b42c1c5..00000000 --- a/maintenance/tests/TitleTest.php +++ /dev/null @@ -1,17 +0,0 @@ -<?php - -class TitleTest extends PHPUnit_Framework_TestCase { - - function testLegalChars() { - $titlechars = Title::legalChars(); - - foreach ( range( 1, 255 ) as $num ) { - $chr = chr( $num ); - if ( strpos( "#[]{}<>|", $chr ) !== false || preg_match( "/[\\x00-\\x1f\\x7f]/", $chr ) ) { - $this->assertFalse( (bool)preg_match( "/[$titlechars]/", $chr ), "chr($num) = $chr is not a valid titlechar" ); - } else { - $this->assertTrue( (bool)preg_match( "/[$titlechars]/", $chr ), "chr($num) = $chr is a valid titlechar" ); - } - } - } -} diff --git a/maintenance/tests/XmlTest.php b/maintenance/tests/XmlTest.php deleted file mode 100644 index 330e60c6..00000000 --- a/maintenance/tests/XmlTest.php +++ /dev/null @@ -1,115 +0,0 @@ -<?php - -class XmlTest extends PHPUnit_Framework_TestCase { - - function testElementOpen() { - $this->assertEquals( - '<element>', - Xml::element( 'element', null, null ), - 'Opening element with no attributes' - ); - } - - function testElementEmpty() { - $this->assertEquals( - '<element />', - Xml::element( 'element', null, '' ), - 'Terminated empty element' - ); - } - - function testElementEscaping() { - $this->assertEquals( - '<element>hello <there> you & you</element>', - Xml::element( 'element', null, 'hello <there> you & you' ), - 'Element with no attributes and content that needs escaping' - ); - } - - function testElementAttributes() { - $this->assertEquals( - '<element key="value" <>="<>">', - Xml::element( 'element', array( 'key' => 'value', '<>' => '<>' ), null ), - 'Element attributes, keys are not escaped' - ); - } - - function testOpenElement() { - $this->assertEquals( - '<element k="v">', - Xml::openElement( 'element', array( 'k' => 'v' ) ), - 'openElement() shortcut' - ); - } - - function testCloseElement() { - $this->assertEquals( '</element>', Xml::closeElement( 'element' ), 'closeElement() shortcut' ); - } - - # - # textarea - # - function testTextareaNoContent() { - $this->assertEquals( - '<textarea name="name" id="name" cols="40" rows="5"></textarea>', - Xml::textarea( 'name', '' ), - 'textarea() with not content' - ); - } - - function testTextareaAttribs() { - $this->assertEquals( - '<textarea name="name" id="name" cols="20" rows="10"><txt></textarea>', - Xml::textarea( 'name', '<txt>', 20, 10 ), - 'textarea() with custom attribs' - ); - } - - # - # JS - # - function testEscapeJsStringSpecialChars() { - $this->assertEquals( - '\\\\\r\n', - Xml::escapeJsString( "\\\r\n" ), - 'escapeJsString() with special characters' - ); - } - - function testEncodeJsVarBoolean() { - $this->assertEquals( - 'true', - Xml::encodeJsVar( true ), - 'encodeJsVar() with boolean' - ); - } - - function testEncodeJsVarNull() { - $this->assertEquals( - 'null', - Xml::encodeJsVar( null ), - 'encodeJsVar() with null' - ); - } - - function testEncodeJsVarArray() { - $this->assertEquals( - '["a", 1]', - Xml::encodeJsVar( array( 'a', 1 ) ), - 'encodeJsVar() with array' - ); - $this->assertEquals( - '{"a": "a", "b": 1}', - Xml::encodeJsVar( array( 'a' => 'a', 'b' => 1 ) ), - 'encodeJsVar() with associative array' - ); - } - - function testEncodeJsVarObject() { - $this->assertEquals( - '{"a": "a", "b": 1}', - Xml::encodeJsVar( (object)array( 'a' => 'a', 'b' => 1 ) ), - 'encodeJsVar() with object' - ); - } -} diff --git a/maintenance/tests/bootstrap.php b/maintenance/tests/bootstrap.php deleted file mode 100644 index 019bee07..00000000 --- a/maintenance/tests/bootstrap.php +++ /dev/null @@ -1,15 +0,0 @@ -<?php - -/** - * Set up the MediaWiki environment when running tests with "phpunit" command - * - * Warning: this file is not included from global scope! - * @file - */ - -global $wgCommandLineMode, $IP, $optionsWithArgs; -$IP = dirname( dirname( dirname( __FILE__ ) ) ); -define( 'MW_PHPUNIT_TEST', true ); - -require_once( "$IP/maintenance/commandLine.inc" ); - diff --git a/maintenance/tests/parser/ExtraParserTests.txt b/maintenance/tests/parser/ExtraParserTests.txt Binary files differnew file mode 100644 index 00000000..66b8032d --- /dev/null +++ b/maintenance/tests/parser/ExtraParserTests.txt diff --git a/maintenance/tests/parser/parserTest.inc b/maintenance/tests/parser/parserTest.inc new file mode 100644 index 00000000..c553550c --- /dev/null +++ b/maintenance/tests/parser/parserTest.inc @@ -0,0 +1,1305 @@ +<?php +# Copyright (C) 2004, 2010 Brion Vibber <brion@pobox.com> +# http://www.mediawiki.org/ +# +# 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 + +/** + * @todo Make this more independent of the configuration (and if possible the database) + * @todo document + * @file + * @ingroup Maintenance + */ + +/** + * @ingroup Maintenance + */ +class ParserTest { + /** + * boolean $color whereas output should be colorized + */ + private $color; + + /** + * boolean $showOutput Show test output + */ + private $showOutput; + + /** + * boolean $useTemporaryTables Use temporary tables for the temporary database + */ + private $useTemporaryTables = true; + + /** + * boolean $databaseSetupDone True if the database has been set up + */ + private $databaseSetupDone = false; + + /** + * string $oldTablePrefix Original table prefix + */ + private $oldTablePrefix; + + private $maxFuzzTestLength = 300; + private $fuzzSeed = 0; + private $memoryLimit = 50; + private $uploadDir = null; + + public $regex = ""; + private $savedGlobals = array(); + /** + * Sets terminal colorization and diff/quick modes depending on OS and + * command-line options (--color and --quick). + */ + public function ParserTest( $options = array() ) { + # Only colorize output if stdout is a terminal. + $this->color = !wfIsWindows() && posix_isatty( 1 ); + + if ( isset( $options['color'] ) ) { + switch( $options['color'] ) { + case 'no': + $this->color = false; + break; + case 'yes': + default: + $this->color = true; + break; + } + } + + $this->term = $this->color + ? new AnsiTermColorer() + : new DummyTermColorer(); + + $this->showDiffs = !isset( $options['quick'] ); + $this->showProgress = !isset( $options['quiet'] ); + $this->showFailure = !( + isset( $options['quiet'] ) + && ( isset( $options['record'] ) + || isset( $options['compare'] ) ) ); // redundant output + + $this->showOutput = isset( $options['show-output'] ); + + + if ( isset( $options['regex'] ) ) { + if ( isset( $options['record'] ) ) { + echo "Warning: --record cannot be used with --regex, disabling --record\n"; + unset( $options['record'] ); + } + $this->regex = $options['regex']; + } else { + # Matches anything + $this->regex = ''; + } + + $this->setupRecorder( $options ); + $this->keepUploads = isset( $options['keep-uploads'] ); + + if ( isset( $options['seed'] ) ) { + $this->fuzzSeed = intval( $options['seed'] ) - 1; + } + + $this->runDisabled = isset( $options['run-disabled'] ); + + $this->hooks = array(); + $this->functionHooks = array(); + self::setUp(); + } + + static function setUp() { + global $wgParser, $wgParserConf, $IP, $messageMemc, $wgMemc, $wgDeferredUpdateList, + $wgUser, $wgLang, $wgOut, $wgRequest, $wgStyleDirectory, $wgEnableParserCache, + $wgMessageCache, $wgUseDatabaseMessages, $wgMsgCacheExpiry, $parserMemc, + $wgNamespaceAliases, $wgNamespaceProtection, $wgLocalFileRepo, + $wgThumbnailScriptPath, $wgScriptPath, + $wgArticlePath, $wgStyleSheetPath, $wgScript, $wgStylePath; + + $wgScript = '/index.php'; + $wgScriptPath = '/'; + $wgArticlePath = '/wiki/$1'; + $wgStyleSheetPath = '/skins'; + $wgStylePath = '/skins'; + $wgThumbnailScriptPath = false; + $wgLocalFileRepo = array( + 'class' => 'LocalRepo', + 'name' => 'local', + 'directory' => wfTempDir() . '/test-repo', + 'url' => 'http://example.com/images', + 'deletedDir' => wfTempDir() . '/test-repo/delete', + 'hashLevels' => 2, + 'transformVia404' => false, + ); + $wgNamespaceProtection[NS_MEDIAWIKI] = 'editinterface'; + $wgNamespaceAliases['Image'] = NS_FILE; + $wgNamespaceAliases['Image_talk'] = NS_FILE_TALK; + + + $wgEnableParserCache = false; + $wgDeferredUpdateList = array(); + $wgMemc = &wfGetMainCache(); + $messageMemc = &wfGetMessageCacheStorage(); + $parserMemc = &wfGetParserCacheStorage(); + + // $wgContLang = new StubContLang; + $wgUser = new User; + $wgLang = new StubUserLang; + $wgOut = new StubObject( 'wgOut', 'OutputPage' ); + $wgParser = new StubObject( 'wgParser', $wgParserConf['class'], array( $wgParserConf ) ); + $wgRequest = new WebRequest; + + $wgMessageCache = new StubObject( 'wgMessageCache', 'MessageCache', + array( $messageMemc, $wgUseDatabaseMessages, + $wgMsgCacheExpiry ) ); + if ( $wgStyleDirectory === false ) { + $wgStyleDirectory = "$IP/skins"; + } + + } + + public function setupRecorder ( $options ) { + if ( isset( $options['record'] ) ) { + $this->recorder = new DbTestRecorder( $this ); + $this->recorder->version = isset( $options['setversion'] ) ? + $options['setversion'] : SpecialVersion::getVersion(); + } elseif ( isset( $options['compare'] ) ) { + $this->recorder = new DbTestPreviewer( $this ); + } elseif ( isset( $options['upload'] ) ) { + $this->recorder = new RemoteTestRecorder( $this ); + } else { + $this->recorder = new TestRecorder( $this ); + } + } + + /** + * Remove last character if it is a newline + * @group utility + */ + static public function chomp( $s ) { + if ( substr( $s, -1 ) === "\n" ) { + return substr( $s, 0, -1 ); + } + else { + return $s; + } + } + + /** + * Run a fuzz test series + * Draw input from a set of test files + */ + function fuzzTest( $filenames ) { + $GLOBALS['wgContLang'] = Language::factory( 'en' ); + $dict = $this->getFuzzInput( $filenames ); + $dictSize = strlen( $dict ); + $logMaxLength = log( $this->maxFuzzTestLength ); + $this->setupDatabase(); + ini_set( 'memory_limit', $this->memoryLimit * 1048576 ); + + $numTotal = 0; + $numSuccess = 0; + $user = new User; + $opts = ParserOptions::newFromUser( $user ); + $title = Title::makeTitle( NS_MAIN, 'Parser_test' ); + + while ( true ) { + // Generate test input + mt_srand( ++$this->fuzzSeed ); + $totalLength = mt_rand( 1, $this->maxFuzzTestLength ); + $input = ''; + + while ( strlen( $input ) < $totalLength ) { + $logHairLength = mt_rand( 0, 1000000 ) / 1000000 * $logMaxLength; + $hairLength = min( intval( exp( $logHairLength ) ), $dictSize ); + $offset = mt_rand( 0, $dictSize - $hairLength ); + $input .= substr( $dict, $offset, $hairLength ); + } + + $this->setupGlobals(); + $parser = $this->getParser(); + + // Run the test + try { + $parser->parse( $input, $title, $opts ); + $fail = false; + } catch ( Exception $exception ) { + $fail = true; + } + + if ( $fail ) { + echo "Test failed with seed {$this->fuzzSeed}\n"; + echo "Input:\n"; + var_dump( $input ); + echo "\n\n"; + echo "$exception\n"; + } else { + $numSuccess++; + } + + $numTotal++; + $this->teardownGlobals(); + $parser->__destruct(); + + if ( $numTotal % 100 == 0 ) { + $usage = intval( memory_get_usage( true ) / $this->memoryLimit / 1048576 * 100 ); + echo "{$this->fuzzSeed}: $numSuccess/$numTotal (mem: $usage%)\n"; + if ( $usage > 90 ) { + echo "Out of memory:\n"; + $memStats = $this->getMemoryBreakdown(); + + foreach ( $memStats as $name => $usage ) { + echo "$name: $usage\n"; + } + $this->abort(); + } + } + } + } + + /** + * Get an input dictionary from a set of parser test files + */ + function getFuzzInput( $filenames ) { + $dict = ''; + + foreach ( $filenames as $filename ) { + $contents = file_get_contents( $filename ); + preg_match_all( '/!!\s*input\n(.*?)\n!!\s*result/s', $contents, $matches ); + + foreach ( $matches[1] as $match ) { + $dict .= $match . "\n"; + } + } + + return $dict; + } + + /** + * Get a memory usage breakdown + */ + function getMemoryBreakdown() { + $memStats = array(); + + foreach ( $GLOBALS as $name => $value ) { + $memStats['$' . $name] = strlen( serialize( $value ) ); + } + + $classes = get_declared_classes(); + + foreach ( $classes as $class ) { + $rc = new ReflectionClass( $class ); + $props = $rc->getStaticProperties(); + $memStats[$class] = strlen( serialize( $props ) ); + $methods = $rc->getMethods(); + + foreach ( $methods as $method ) { + $memStats[$class] += strlen( serialize( $method->getStaticVariables() ) ); + } + } + + $functions = get_defined_functions(); + + foreach ( $functions['user'] as $function ) { + $rf = new ReflectionFunction( $function ); + $memStats["$function()"] = strlen( serialize( $rf->getStaticVariables() ) ); + } + + asort( $memStats ); + + return $memStats; + } + + function abort() { + $this->abort(); + } + + /** + * Run a series of tests listed in the given text files. + * Each test consists of a brief description, wikitext input, + * and the expected HTML output. + * + * Prints status updates on stdout and counts up the total + * number and percentage of passed tests. + * + * @param $filenames Array of strings + * @return Boolean: true if passed all tests, false if any tests failed. + */ + public function runTestsFromFiles( $filenames ) { + $ok = false; + $GLOBALS['wgContLang'] = Language::factory( 'en' ); + $this->recorder->start(); + try { + $this->setupDatabase(); + $ok = true; + + foreach ( $filenames as $filename ) { + $tests = new TestFileIterator( $filename, $this ); + $ok = $this->runTests( $tests ) && $ok; + } + + $this->teardownDatabase(); + $this->recorder->report(); + } catch (DBError $e) { + echo $e->getMessage(); + } + $this->recorder->end(); + + return $ok; + } + + function runTests( $tests ) { + $ok = true; + + foreach ( $tests as $t ) { + $result = + $this->runTest( $t['test'], $t['input'], $t['result'], $t['options'], $t['config'] ); + $ok = $ok && $result; + $this->recorder->record( $t['test'], $result ); + } + + if ( $this->showProgress ) { + print "\n"; + } + + return $ok; + } + + /** + * Get a Parser object + */ + function getParser( $preprocessor = null ) { + global $wgParserConf; + + $class = $wgParserConf['class']; + $parser = new $class( array( 'preprocessorClass' => $preprocessor ) + $wgParserConf ); + + foreach ( $this->hooks as $tag => $callback ) { + $parser->setHook( $tag, $callback ); + } + + foreach ( $this->functionHooks as $tag => $bits ) { + list( $callback, $flags ) = $bits; + $parser->setFunctionHook( $tag, $callback, $flags ); + } + + wfRunHooks( 'ParserTestParser', array( &$parser ) ); + + return $parser; + } + + /** + * Run a given wikitext input through a freshly-constructed wiki parser, + * and compare the output against the expected results. + * Prints status and explanatory messages to stdout. + * + * @param $desc String: test's description + * @param $input String: wikitext to try rendering + * @param $result String: result to output + * @param $opts Array: test's options + * @param $config String: overrides for global variables, one per line + * @return Boolean + */ + public function runTest( $desc, $input, $result, $opts, $config ) { + if ( $this->showProgress ) { + $this->showTesting( $desc ); + } + + $opts = $this->parseOptions( $opts ); + $this->setupGlobals( $opts, $config ); + + $user = new User(); + $options = ParserOptions::newFromUser( $user ); + + if ( isset( $opts['title'] ) ) { + $titleText = $opts['title']; + } + else { + $titleText = 'Parser test'; + } + + $local = isset( $opts['local'] ); + $preprocessor = isset( $opts['preprocessor'] ) ? $opts['preprocessor'] : null; + $parser = $this->getParser( $preprocessor ); + $title = Title::newFromText( $titleText ); + + if ( isset( $opts['pst'] ) ) { + $out = $parser->preSaveTransform( $input, $title, $user, $options ); + } elseif ( isset( $opts['msg'] ) ) { + $out = $parser->transformMsg( $input, $options ); + } elseif ( isset( $opts['section'] ) ) { + $section = $opts['section']; + $out = $parser->getSection( $input, $section ); + } elseif ( isset( $opts['replace'] ) ) { + $section = $opts['replace'][0]; + $replace = $opts['replace'][1]; + $out = $parser->replaceSection( $input, $section, $replace ); + } elseif ( isset( $opts['comment'] ) ) { + $linker = $user->getSkin(); + $out = $linker->formatComment( $input, $title, $local ); + } elseif ( isset( $opts['preload'] ) ) { + $out = $parser->getpreloadText( $input, $title, $options ); + } else { + $output = $parser->parse( $input, $title, $options, true, true, 1337 ); + $out = $output->getText(); + + if ( isset( $opts['showtitle'] ) ) { + if ( $output->getTitleText() ) { + $title = $output->getTitleText(); + } + + $out = "$title\n$out"; + } + + if ( isset( $opts['ill'] ) ) { + $out = $this->tidy( implode( ' ', $output->getLanguageLinks() ) ); + } elseif ( isset( $opts['cat'] ) ) { + global $wgOut; + + $wgOut->addCategoryLinks( $output->getCategories() ); + $cats = $wgOut->getCategoryLinks(); + + if ( isset( $cats['normal'] ) ) { + $out = $this->tidy( implode( ' ', $cats['normal'] ) ); + } else { + $out = ''; + } + } + + $result = $this->tidy( $result ); + } + + $this->teardownGlobals(); + return $this->showTestResult( $desc, $result, $out ); + } + + /** + * + */ + function showTestResult( $desc, $result, $out ) { + if ( $result === $out ) { + $this->showSuccess( $desc ); + return true; + } else { + $this->showFailure( $desc, $result, $out ); + return false; + } + } + + /** + * Use a regex to find out the value of an option + * @param $key String: name of option val to retrieve + * @param $opts Options array to look in + * @param $default Mixed: default value returned if not found + */ + private static function getOptionValue( $key, $opts, $default ) { + $key = strtolower( $key ); + + if ( isset( $opts[$key] ) ) { + return $opts[$key]; + } else { + return $default; + } + } + + private function parseOptions( $instring ) { + $opts = array(); + // foo + // foo=bar + // foo="bar baz" + // foo=[[bar baz]] + // foo=bar,"baz quux" + $regex = '/\b + ([\w-]+) # Key + \b + (?:\s* + = # First sub-value + \s* + ( + " + [^"]* # Quoted val + " + | + \[\[ + [^]]* # Link target + \]\] + | + [\w-]+ # Plain word + ) + (?:\s* + , # Sub-vals 1..N + \s* + ( + "[^"]*" # Quoted val + | + \[\[[^]]*\]\] # Link target + | + [\w-]+ # Plain word + ) + )* + )? + /x'; + + if ( preg_match_all( $regex, $instring, $matches, PREG_SET_ORDER ) ) { + foreach ( $matches as $bits ) { + array_shift( $bits ); + $key = strtolower( array_shift( $bits ) ); + if ( count( $bits ) == 0 ) { + $opts[$key] = true; + } elseif ( count( $bits ) == 1 ) { + $opts[$key] = $this->cleanupOption( array_shift( $bits ) ); + } else { + // Array! + $opts[$key] = array_map( array( $this, 'cleanupOption' ), $bits ); + } + } + } + return $opts; + } + + private function cleanupOption( $opt ) { + if ( substr( $opt, 0, 1 ) == '"' ) { + return substr( $opt, 1, -1 ); + } + + if ( substr( $opt, 0, 2 ) == '[[' ) { + return substr( $opt, 2, -2 ); + } + return $opt; + } + + /** + * Set up the global variables for a consistent environment for each test. + * Ideally this should replace the global configuration entirely. + */ + private function setupGlobals( $opts = '', $config = '' ) { + global $wgDBtype; + + # Find out values for some special options. + $lang = + self::getOptionValue( 'language', $opts, 'en' ); + $variant = + self::getOptionValue( 'variant', $opts, false ); + $maxtoclevel = + self::getOptionValue( 'wgMaxTocLevel', $opts, 999 ); + $linkHolderBatchSize = + self::getOptionValue( 'wgLinkHolderBatchSize', $opts, 1000 ); + + $settings = array( + 'wgServer' => 'http://Britney-Spears', + 'wgScript' => '/index.php', + 'wgScriptPath' => '/', + 'wgArticlePath' => '/wiki/$1', + 'wgActionPaths' => array(), + 'wgLocalFileRepo' => array( + 'class' => 'LocalRepo', + 'name' => 'local', + 'directory' => $this->uploadDir, + 'url' => 'http://example.com/images', + 'hashLevels' => 2, + 'transformVia404' => false, + ), + 'wgEnableUploads' => self::getOptionValue( 'wgEnableUploads', $opts, true ), + 'wgStylePath' => '/skins', + 'wgStyleSheetPath' => '/skins', + 'wgSitename' => 'MediaWiki', + 'wgLanguageCode' => $lang, + 'wgDBprefix' => $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_', + 'wgRawHtml' => isset( $opts['rawhtml'] ), + 'wgLang' => null, + 'wgContLang' => null, + 'wgNamespacesWithSubpages' => array( 0 => isset( $opts['subpage'] ) ), + 'wgMaxTocLevel' => $maxtoclevel, + 'wgCapitalLinks' => true, + 'wgNoFollowLinks' => true, + 'wgNoFollowDomainExceptions' => array(), + 'wgThumbnailScriptPath' => false, + 'wgUseImageResize' => false, + 'wgUseTeX' => isset( $opts['math'] ), + 'wgMathDirectory' => $this->uploadDir . '/math', + 'wgLocaltimezone' => 'UTC', + 'wgAllowExternalImages' => true, + 'wgUseTidy' => false, + 'wgDefaultLanguageVariant' => $variant, + 'wgVariantArticlePath' => false, + 'wgGroupPermissions' => array( '*' => array( + 'createaccount' => true, + 'read' => true, + 'edit' => true, + 'createpage' => true, + 'createtalk' => true, + ) ), + 'wgNamespaceProtection' => array( NS_MEDIAWIKI => 'editinterface' ), + 'wgDefaultExternalStore' => array(), + 'wgForeignFileRepos' => array(), + 'wgLinkHolderBatchSize' => $linkHolderBatchSize, + 'wgExperimentalHtmlIds' => false, + 'wgExternalLinkTarget' => false, + 'wgAlwaysUseTidy' => false, + 'wgHtml5' => true, + 'wgWellFormedXml' => true, + 'wgAllowMicrodataAttributes' => true, + ); + + if ( $config ) { + $configLines = explode( "\n", $config ); + + foreach ( $configLines as $line ) { + list( $var, $value ) = explode( '=', $line, 2 ); + + $settings[$var] = eval( "return $value;" ); + } + } + + $this->savedGlobals = array(); + + foreach ( $settings as $var => $val ) { + if ( array_key_exists( $var, $GLOBALS ) ) { + $this->savedGlobals[$var] = $GLOBALS[$var]; + } + + $GLOBALS[$var] = $val; + } + + $langObj = Language::factory( $lang ); + $GLOBALS['wgLang'] = $langObj; + $GLOBALS['wgContLang'] = $langObj; + $GLOBALS['wgMemc'] = new FakeMemCachedClient; + $GLOBALS['wgOut'] = new OutputPage; + + global $wgHooks; + + $wgHooks['ParserTestParser'][] = 'ParserTestParserHook::setup'; + $wgHooks['ParserTestParser'][] = 'ParserTestStaticParserHook::setup'; + $wgHooks['ParserGetVariableValueTs'][] = 'ParserTest::getFakeTimestamp'; + + MagicWord::clearCache(); + + global $wgUser; + $wgUser = new User(); + } + + /** + * List of temporary tables to create, without prefix. + * Some of these probably aren't necessary. + */ + private function listTables() { + global $wgDBtype; + + $tables = array( 'user', 'user_properties', 'page', 'page_restrictions', + 'protected_titles', 'revision', 'text', 'pagelinks', 'imagelinks', + 'categorylinks', 'templatelinks', 'externallinks', 'langlinks', 'iwlinks', + 'site_stats', 'hitcounter', 'ipblocks', 'image', 'oldimage', + 'recentchanges', 'watchlist', 'math', 'interwiki', 'logging', + 'querycache', 'objectcache', 'job', 'l10n_cache', 'redirect', 'querycachetwo', + 'archive', 'user_groups', 'page_props', 'category', 'msg_resource', 'msg_resource_links' + ); + + if ( in_array( $wgDBtype, array( 'mysql', 'sqlite', 'oracle' ) ) ) + array_push( $tables, 'searchindex' ); + + // Allow extensions to add to the list of tables to duplicate; + // may be necessary if they hook into page save or other code + // which will require them while running tests. + wfRunHooks( 'ParserTestTables', array( &$tables ) ); + + return $tables; + } + + /** + * Set up a temporary set of wiki tables to work with for the tests. + * Currently this will only be done once per run, and any changes to + * the db will be visible to later tests in the run. + */ + public function setupDatabase() { + global $wgDBprefix, $wgDBtype; + + if ( $this->databaseSetupDone ) { + return; + } + + if ( $wgDBprefix === 'parsertest_' || ( $wgDBtype == 'oracle' && $wgDBprefix === 'pt_' ) ) { + throw new MWException( 'setupDatabase should be called before setupGlobals' ); + } + + $this->databaseSetupDone = true; + $this->oldTablePrefix = $wgDBprefix; + + # SqlBagOStuff broke when using temporary tables on r40209 (bug 15892). + # It seems to have been fixed since (r55079?). + # If it fails, $wgCaches[CACHE_DB] = new HashBagOStuff(); should work around it. + + # CREATE TEMPORARY TABLE breaks if there is more than one server + if ( wfGetLB()->getServerCount() != 1 ) { + $this->useTemporaryTables = false; + } + + $temporary = $this->useTemporaryTables || $wgDBtype == 'postgres'; + + $db = wfGetDB( DB_MASTER ); + $tables = $this->listTables(); + + foreach ( $tables as $tbl ) { + # Clean up from previous aborted run. So that table escaping + # works correctly across DB engines, we need to change the pre- + # fix back and forth so tableName() works right. + $this->changePrefix( $this->oldTablePrefix ); + $oldTableName = $db->tableName( $tbl ); + $this->changePrefix( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' ); + $newTableName = $db->tableName( $tbl ); + + if ( $wgDBtype == 'mysql' ) { + $db->query( "DROP TABLE IF EXISTS $newTableName" ); + } elseif ( in_array( $wgDBtype, array( 'postgres', 'oracle' ) ) ) { + /* DROPs wouldn't work due to Foreign Key Constraints (bug 14990, r58669) + * Use "DROP TABLE IF EXISTS $newTableName CASCADE" for postgres? That + * syntax would also work for mysql. + */ + } elseif ( $db->tableExists( $tbl ) ) { + $db->query( "DROP TABLE $newTableName" ); + } + + # Create new table + $db->duplicateTableStructure( $oldTableName, $newTableName, $temporary ); + } + + if ( $wgDBtype == 'oracle' ) + $db->query( 'BEGIN FILL_WIKI_INFO; END;' ); + + $this->changePrefix( $wgDBtype != 'oracle' ? 'parsertest_' : 'pt_' ); + + if ( $wgDBtype == 'oracle' ) { + # Insert 0 user to prevent FK violations + + # Anonymous user + $db->insert( 'user', array( + 'user_id' => 0, + 'user_name' => 'Anonymous' ) ); + } + + # Hack: insert a few Wikipedia in-project interwiki prefixes, + # for testing inter-language links + $db->insert( 'interwiki', array( + array( 'iw_prefix' => 'wikipedia', + 'iw_url' => 'http://en.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + array( 'iw_prefix' => 'meatball', + 'iw_url' => 'http://www.usemod.com/cgi-bin/mb.pl?$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 0 ), + array( 'iw_prefix' => 'zh', + 'iw_url' => 'http://zh.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'es', + 'iw_url' => 'http://es.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'fr', + 'iw_url' => 'http://fr.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + array( 'iw_prefix' => 'ru', + 'iw_url' => 'http://ru.wikipedia.org/wiki/$1', + 'iw_api' => '', + 'iw_wikiid' => '', + 'iw_local' => 1 ), + ) ); + + + # Update certain things in site_stats + $db->insert( 'site_stats', array( 'ss_row_id' => 1, 'ss_images' => 2, 'ss_good_articles' => 1 ) ); + + # Reinitialise the LocalisationCache to match the database state + Language::getLocalisationCache()->unloadAll(); + + # Make a new message cache + global $wgMessageCache, $wgMemc; + $wgMessageCache = new MessageCache( $wgMemc, true, 3600 ); + + $this->uploadDir = $this->setupUploadDir(); + $user = User::createNew( 'WikiSysop' ); + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Foobar.jpg' ) ); + $image->recordUpload2( '', 'Upload of some lame file', 'Some lame file', array( + 'size' => 12345, + 'width' => 1941, + 'height' => 220, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true + ), $db->timestamp( '20010115123500' ), $user ); + + # This image will be blacklisted in [[MediaWiki:Bad image list]] + $image = wfLocalFile( Title::makeTitle( NS_FILE, 'Bad.jpg' ) ); + $image->recordUpload2( '', 'zomgnotcensored', 'Borderline image', array( + 'size' => 12345, + 'width' => 320, + 'height' => 240, + 'bits' => 24, + 'media_type' => MEDIATYPE_BITMAP, + 'mime' => 'image/jpeg', + 'metadata' => serialize( array() ), + 'sha1' => wfBaseConvert( '', 16, 36, 31 ), + 'fileExists' => true + ), $db->timestamp( '20010115123500' ), $user ); + } + + /** + * Change the table prefix on all open DB connections/ + */ + protected function changePrefix( $prefix ) { + global $wgDBprefix; + wfGetLBFactory()->forEachLB( array( $this, 'changeLBPrefix' ), array( $prefix ) ); + $wgDBprefix = $prefix; + } + + public function changeLBPrefix( $lb, $prefix ) { + $lb->forEachOpenConnection( array( $this, 'changeDBPrefix' ), array( $prefix ) ); + } + + public function changeDBPrefix( $db, $prefix ) { + $db->tablePrefix( $prefix ); + } + + public function teardownDatabase() { + global $wgDBtype; + + if ( !$this->databaseSetupDone ) { + $this->teardownGlobals(); + return; + } + $this->teardownUploadDir( $this->uploadDir ); + + $this->changePrefix( $this->oldTablePrefix ); + $this->databaseSetupDone = false; + + if ( $this->useTemporaryTables ) { + # Don't need to do anything + $this->teardownGlobals(); + return; + } + + $tables = $this->listTables(); + $db = wfGetDB( DB_MASTER ); + + foreach ( $tables as $table ) { + $sql = $wgDBtype == 'oracle' ? "DROP TABLE pt_$table DROP CONSTRAINTS" : "DROP TABLE `parsertest_$table`"; + $db->query( $sql ); + } + + if ( $wgDBtype == 'oracle' ) + $db->query( 'BEGIN FILL_WIKI_INFO; END;' ); + + $this->teardownGlobals(); + } + + /** + * Create a dummy uploads directory which will contain a couple + * of files in order to pass existence tests. + * + * @return String: the directory + */ + private function setupUploadDir() { + global $IP; + + if ( $this->keepUploads ) { + $dir = wfTempDir() . '/mwParser-images'; + + if ( is_dir( $dir ) ) { + return $dir; + } + } else { + $dir = wfTempDir() . "/mwParser-" . mt_rand() . "-images"; + } + + // wfDebug( "Creating upload directory $dir\n" ); + if ( file_exists( $dir ) ) { + wfDebug( "Already exists!\n" ); + return $dir; + } + + wfMkdirParents( $dir . '/3/3a' ); + copy( "$IP/skins/monobook/headbg.jpg", "$dir/3/3a/Foobar.jpg" ); + wfMkdirParents( $dir . '/0/09' ); + copy( "$IP/skins/monobook/headbg.jpg", "$dir/0/09/Bad.jpg" ); + + return $dir; + } + + /** + * Restore default values and perform any necessary clean-up + * after each test runs. + */ + private function teardownGlobals() { + RepoGroup::destroySingleton(); + LinkCache::singleton()->clear(); + + foreach ( $this->savedGlobals as $var => $val ) { + $GLOBALS[$var] = $val; + } + } + + /** + * Remove the dummy uploads directory + */ + private function teardownUploadDir( $dir ) { + if ( $this->keepUploads ) { + return; + } + + // delete the files first, then the dirs. + self::deleteFiles( + array ( + "$dir/3/3a/Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/180px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/200px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/640px-Foobar.jpg", + "$dir/thumb/3/3a/Foobar.jpg/120px-Foobar.jpg", + + "$dir/0/09/Bad.jpg", + + "$dir/math/f/a/5/fa50b8b616463173474302ca3e63586b.png", + ) + ); + + self::deleteDirs( + array ( + "$dir/3/3a", + "$dir/3", + "$dir/thumb/6/65", + "$dir/thumb/6", + "$dir/thumb/3/3a/Foobar.jpg", + "$dir/thumb/3/3a", + "$dir/thumb/3", + + "$dir/0/09/", + "$dir/0/", + "$dir/thumb", + "$dir/math/f/a/5", + "$dir/math/f/a", + "$dir/math/f", + "$dir/math", + "$dir", + ) + ); + } + + /** + * Delete the specified files, if they exist. + * @param $files Array: full paths to files to delete. + */ + private static function deleteFiles( $files ) { + foreach ( $files as $file ) { + if ( file_exists( $file ) ) { + unlink( $file ); + } + } + } + + /** + * Delete the specified directories, if they exist. Must be empty. + * @param $dirs Array: full paths to directories to delete. + */ + private static function deleteDirs( $dirs ) { + foreach ( $dirs as $dir ) { + if ( is_dir( $dir ) ) { + rmdir( $dir ); + } + } + } + + /** + * "Running test $desc..." + */ + protected function showTesting( $desc ) { + print "Running test $desc... "; + } + + /** + * Print a happy success message. + * + * @param $desc String: the test name + * @return Boolean + */ + protected function showSuccess( $desc ) { + if ( $this->showProgress ) { + print $this->term->color( '1;32' ) . 'PASSED' . $this->term->reset() . "\n"; + } + + return true; + } + + /** + * Print a failure message and provide some explanatory output + * about what went wrong if so configured. + * + * @param $desc String: the test name + * @param $result String: expected HTML output + * @param $html String: actual HTML output + * @return Boolean + */ + protected function showFailure( $desc, $result, $html ) { + if ( $this->showFailure ) { + if ( !$this->showProgress ) { + # In quiet mode we didn't show the 'Testing' message before the + # test, in case it succeeded. Show it now: + $this->showTesting( $desc ); + } + + print $this->term->color( '31' ) . 'FAILED!' . $this->term->reset() . "\n"; + + if ( $this->showOutput ) { + print "--- Expected ---\n$result\n--- Actual ---\n$html\n"; + } + + if ( $this->showDiffs ) { + print $this->quickDiff( $result, $html ); + if ( !$this->wellFormed( $html ) ) { + print "XML error: $this->mXmlError\n"; + } + } + } + + return false; + } + + /** + * Run given strings through a diff and return the (colorized) output. + * Requires writable /tmp directory and a 'diff' command in the PATH. + * + * @param $input String + * @param $output String + * @param $inFileTail String: tailing for the input file name + * @param $outFileTail String: tailing for the output file name + * @return String + */ + protected function quickDiff( $input, $output, $inFileTail = 'expected', $outFileTail = 'actual' ) { + $prefix = wfTempDir() . "/mwParser-" . mt_rand(); + + $infile = "$prefix-$inFileTail"; + $this->dumpToFile( $input, $infile ); + + $outfile = "$prefix-$outFileTail"; + $this->dumpToFile( $output, $outfile ); + + $diff = `diff -au $infile $outfile`; + unlink( $infile ); + unlink( $outfile ); + + return $this->colorDiff( $diff ); + } + + /** + * Write the given string to a file, adding a final newline. + * + * @param $data String + * @param $filename String + */ + private function dumpToFile( $data, $filename ) { + $file = fopen( $filename, "wt" ); + fwrite( $file, $data . "\n" ); + fclose( $file ); + } + + /** + * Colorize unified diff output if set for ANSI color output. + * Subtractions are colored blue, additions red. + * + * @param $text String + * @return String + */ + protected function colorDiff( $text ) { + return preg_replace( + array( '/^(-.*)$/m', '/^(\+.*)$/m' ), + array( $this->term->color( 34 ) . '$1' . $this->term->reset(), + $this->term->color( 31 ) . '$1' . $this->term->reset() ), + $text ); + } + + /** + * Show "Reading tests from ..." + * + * @param $path String + */ + public function showRunFile( $path ) { + print $this->term->color( 1 ) . + "Reading tests from \"$path\"..." . + $this->term->reset() . + "\n"; + } + + /** + * Insert a temporary test article + * @param $name String: the title, including any prefix + * @param $text String: the article text + * @param $line Integer: the input line number, for reporting errors + */ + static public function addArticle( $name, $text, $line = 'unknown' ) { + global $wgCapitalLinks; + + $text = self::chomp($text); + + $oldCapitalLinks = $wgCapitalLinks; + $wgCapitalLinks = true; // We only need this from SetupGlobals() See r70917#c8637 + + $name = self::chomp( $name ); + $title = Title::newFromText( $name ); + + if ( is_null( $title ) ) { + wfDie( "invalid title ('$name' => '$title') at line $line\n" ); + } + + $aid = $title->getArticleID( Title::GAID_FOR_UPDATE ); + + if ( $aid != 0 ) { + debug_print_backtrace(); + wfDie( "duplicate article '$name' at line $line\n" ); + } + + $art = new Article( $title ); + $art->insertNewArticle( $text, '', false, false ); + + $wgCapitalLinks = $oldCapitalLinks; + } + + /** + * Steal a callback function from the primary parser, save it for + * application to our scary parser. If the hook is not installed, + * abort processing of this file. + * + * @param $name String + * @return Bool true if tag hook is present + */ + public function requireHook( $name ) { + global $wgParser; + + $wgParser->firstCallInit( ); // make sure hooks are loaded. + + if ( isset( $wgParser->mTagHooks[$name] ) ) { + $this->hooks[$name] = $wgParser->mTagHooks[$name]; + } else { + echo " This test suite requires the '$name' hook extension, skipping.\n"; + return false; + } + + return true; + } + + /** + * Steal a callback function from the primary parser, save it for + * application to our scary parser. If the hook is not installed, + * abort processing of this file. + * + * @param $name String + * @return Bool true if function hook is present + */ + public function requireFunctionHook( $name ) { + global $wgParser; + + $wgParser->firstCallInit( ); // make sure hooks are loaded. + + if ( isset( $wgParser->mFunctionHooks[$name] ) ) { + $this->functionHooks[$name] = $wgParser->mFunctionHooks[$name]; + } else { + echo " This test suite requires the '$name' function hook extension, skipping.\n"; + return false; + } + + return true; + } + + /* + * Run the "tidy" command on text if the $wgUseTidy + * global is true + * + * @param $text String: the text to tidy + * @return String + * @static + */ + private function tidy( $text ) { + global $wgUseTidy; + + if ( $wgUseTidy ) { + $text = MWTidy::tidy( $text ); + } + + return $text; + } + + private function wellFormed( $text ) { + $html = + Sanitizer::hackDocType() . + '<html>' . + $text . + '</html>'; + + $parser = xml_parser_create( "UTF-8" ); + + # case folding violates XML standard, turn it off + xml_parser_set_option( $parser, XML_OPTION_CASE_FOLDING, false ); + + if ( !xml_parse( $parser, $html, true ) ) { + $err = xml_error_string( xml_get_error_code( $parser ) ); + $position = xml_get_current_byte_index( $parser ); + $fragment = $this->extractFragment( $html, $position ); + $this->mXmlError = "$err at byte $position:\n$fragment"; + xml_parser_free( $parser ); + + return false; + } + + xml_parser_free( $parser ); + + return true; + } + + private function extractFragment( $text, $position ) { + $start = max( 0, $position - 10 ); + $before = $position - $start; + $fragment = '...' . + $this->term->color( 34 ) . + substr( $text, $start, $before ) . + $this->term->color( 0 ) . + $this->term->color( 31 ) . + $this->term->color( 1 ) . + substr( $text, $position, 1 ) . + $this->term->color( 0 ) . + $this->term->color( 34 ) . + substr( $text, $position + 1, 9 ) . + $this->term->color( 0 ) . + '...'; + $display = str_replace( "\n", ' ', $fragment ); + $caret = ' ' . + str_repeat( ' ', $before ) . + $this->term->color( 31 ) . + '^' . + $this->term->color( 0 ); + + return "$display\n$caret"; + } + + static function getFakeTimestamp( &$parser, &$ts ) { + $ts = 123; + return true; + } +} diff --git a/maintenance/tests/parser/parserTests.txt b/maintenance/tests/parser/parserTests.txt new file mode 100644 index 00000000..b3fa560c --- /dev/null +++ b/maintenance/tests/parser/parserTests.txt @@ -0,0 +1,8315 @@ +# MediaWiki Parser test cases +# Some taken from http://meta.wikimedia.org/wiki/Parser_testing +# All (C) their respective authors and released under the GPL +# +# The syntax should be fairly self-explanatory. +# +# Currently supported test options: +# One of the following three: +# +# (default) generate HTML output +# pst apply pre-save transform +# msg apply message transform +# +# Plus any combination of these: +# +# cat add category links +# ill add inter-language links +# subpage enable subpages (disabled by default) +# noxml don't check for XML well formdness +# title=[[XXX]] run test using article title XXX +# language=XXX set content language to XXX for this test +# variant=XXX set the variant of language for this test (eg zh-tw) +# disabled do not run test +# showtitle make the first line the title +# comment run through Linker::formatComment() instead of main parser +# local format section links in edit comment text as local links +# +# For testing purposes, temporary articles can created: +# !!article / NAMESPACE:TITLE / !!text / ARTICLE TEXT / !!endarticle +# where '/' denotes a newline. + +# This is the standard article assumed to exist. +!! article +Main Page +!! text +blah blah +!! endarticle + +!!article +Template:Foo +!!text +FOO +!!endarticle + +!! article +Template:Blank +!! text +!! endarticle + +!! article +Template:! +!! text +| +!! endarticle + +!!article +MediaWiki:bad image list +!!text +* [[File:Bad.jpg]] except [[Nasty page]] +!!endarticle + +### +### Basic tests +### +!! test +Blank input +!! input +!! result +!! end + + +!! test +Simple paragraph +!! input +This is a simple paragraph. +!! result +<p>This is a simple paragraph. +</p> +!! end + +!! test +Simple list +!! input +* Item 1 +* Item 2 +!! result +<ul><li> Item 1 +</li><li> Item 2 +</li></ul> + +!! end + +!! test +Italics and bold +!! input +* plain +* plain''italic''plain +* plain''italic''plain''italic''plain +* plain'''bold'''plain +* plain'''bold'''plain'''bold'''plain +* plain''italic''plain'''bold'''plain +* plain'''bold'''plain''italic''plain +* plain''italic'''bold-italic'''italic''plain +* plain'''bold''bold-italic''bold'''plain +* plain'''''bold-italic'''italic''plain +* plain'''''bold-italic''bold'''plain +* plain''italic'''bold-italic'''''plain +* plain'''bold''bold-italic'''''plain +* plain l'''italic''plain +* plain l''''bold''' plain +!! result +<ul><li> plain +</li><li> plain<i>italic</i>plain +</li><li> plain<i>italic</i>plain<i>italic</i>plain +</li><li> plain<b>bold</b>plain +</li><li> plain<b>bold</b>plain<b>bold</b>plain +</li><li> plain<i>italic</i>plain<b>bold</b>plain +</li><li> plain<b>bold</b>plain<i>italic</i>plain +</li><li> plain<i>italic<b>bold-italic</b>italic</i>plain +</li><li> plain<b>bold<i>bold-italic</i>bold</b>plain +</li><li> plain<i><b>bold-italic</b>italic</i>plain +</li><li> plain<b><i>bold-italic</i>bold</b>plain +</li><li> plain<i>italic<b>bold-italic</b></i>plain +</li><li> plain<b>bold<i>bold-italic</i></b>plain +</li><li> plain l'<i>italic</i>plain +</li><li> plain l'<b>bold</b> plain +</li></ul> + +!! end + +### +### <nowiki> test cases +### + +!! test +<nowiki> unordered list +!! input +<nowiki>* This is not an unordered list item.</nowiki> +!! result +<p>* This is not an unordered list item. +</p> +!! end + +!! test +<nowiki> spacing +!! input +<nowiki>Lorem ipsum dolor + +sed abit. + sed nullum. + +:and a colon +</nowiki> +!! result +<p>Lorem ipsum dolor + +sed abit. + sed nullum. + +:and a colon + +</p> +!! end + +!! test +nowiki 3 +!! input +:There is not nowiki. +:There is <nowiki>nowiki</nowiki>. + +#There is not nowiki. +#There is <nowiki>nowiki</nowiki>. + +*There is not nowiki. +*There is <nowiki>nowiki</nowiki>. +!! result +<dl><dd>There is not nowiki. +</dd><dd>There is nowiki. +</dd></dl> +<ol><li>There is not nowiki. +</li><li>There is nowiki. +</li></ol> +<ul><li>There is not nowiki. +</li><li>There is nowiki. +</li></ul> + +!! end + + +### +### Comments +### +!! test +Comment test 1 +!! input +<!-- comment 1 --> asdf +<!-- comment 2 --> +!! result +<pre>asdf +</pre> + +!! end + +!! test +Comment test 2 +!! input +asdf +<!-- comment 1 --> +jkl +!! result +<p>asdf +jkl +</p> +!! end + +!! test +Comment test 3 +!! input +asdf +<!-- comment 1 --> +<!-- comment 2 --> +jkl +!! result +<p>asdf +jkl +</p> +!! end + +!! test +Comment test 4 +!! input +asdf<!-- comment 1 -->jkl +!! result +<p>asdfjkl +</p> +!! end + +!! test +Comment spacing +!! input +a + <!-- foo --> b <!-- bar --> +c +!! result +<p>a +</p> +<pre> b +</pre> +<p>c +</p> +!! end + +!! test +Comment whitespace +!! input +<!-- returns a single newline, not nothing, since the newline after > is not stripped --> +!! result + +!! end + +!! test +Comment semantics and delimiters +!! input +<!-- --><!----><!-----><!------> +!! result + +!! end + +!! test +Comment semantics and delimiters, redux +!! input +<!-- In SGML every "foo" here would actually show up in the text -- foo -- bar +-- foo -- funky huh? ... --> +!! result + +!! end + +!! test +Comment semantics and delimiters: directors cut +!! input +<!-- ... However we like to keep things simple and somewhat XML-ish so we eat +everything starting with < followed by !-- until the first -- and > we see, +that wouldn't be valid XML however, since in XML -- has to terminate a comment +-->--> +!! result +<p>--> +</p> +!! end + +!! test +Comment semantics: nesting +!! input +<!--<!-- no, we're not going to do anything fancy here -->--> +!! result +<p>--> +</p> +!! end + +!! test +Comment semantics: unclosed comment at end +!! input +<!--This comment will run out to the end of the document +!! result + +!! end + +!! test +Comment in template title +!! input +{{f<!---->oo}} +!! result +<p>FOO +</p> +!! end + +!! test +Comment on its own line post-expand +!! input +a +{{blank}}<!----> +b +!! result +<p>a +</p><p>b +</p> +!! end + +### +### Preformatted text +### +!! test +Preformatted text +!! input + This is some + Preformatted text + With ''italic'' + And '''bold''' + And a [[Main Page|link]] +!! result +<pre>This is some +Preformatted text +With <i>italic</i> +And <b>bold</b> +And a <a href="/wiki/Main_Page" title="Main Page">link</a> +</pre> +!! end + +!! test +<pre> with <nowiki> inside (compatibility with 1.6 and earlier) +!! input +<pre><nowiki> +<b> +<cite> +<em> +</nowiki></pre> +!! result +<pre> +<b> +<cite> +<em> +</pre> + +!! end + +!! test +Regression with preformatted in <center> +!! input +<center> + Blah +</center> +!! result +<center> +<pre>Blah +</pre> +</center> + +!! end + +# Expected output in the following test is not really expected (there should be +# <pre> in the output) -- it's only testing for well-formedness. +!! test +Bug 6200: Preformatted in <blockquote> +!! input +<blockquote> + Blah +</blockquote> +!! result +<blockquote> + Blah +</blockquote> + +!! end + +!! test +<pre> with attributes (bug 3202) +!! input +<pre style="background: blue; color:white">Bluescreen of WikiDeath</pre> +!! result +<pre style="background: blue; color:white">Bluescreen of WikiDeath</pre> + +!! end + +!! test +<pre> with width attribute (bug 3202) +!! input +<pre width="8">Narrow screen goodies</pre> +!! result +<pre width="8">Narrow screen goodies</pre> + +!! end + +!! test +<pre> with forbidden attribute (bug 3202) +!! input +<pre width="8" onmouseover="alert(document.cookie)">Narrow screen goodies</pre> +!! result +<pre width="8">Narrow screen goodies</pre> + +!! end + +!! test +<pre> with forbidden attribute values (bug 3202) +!! input +<pre width="8" style="border-width: expression(alert(document.cookie))">Narrow screen goodies</pre> +!! result +<pre width="8" style="/* insecure input */">Narrow screen goodies</pre> + +!! end + +!! test +<nowiki> inside <pre> (bug 13238) +!! input +<pre> +<nowiki> +</pre> +<pre> +<nowiki></nowiki> +</pre> +<pre><nowiki><nowiki></nowiki>Foo<nowiki></nowiki></nowiki></pre> +!! result +<pre> +<nowiki> +</pre> +<pre> + +</pre> +<pre><nowiki>Foo</nowiki></pre> + +!! end + +!! test +<nowiki> and <pre> preference (first one wins) +!! input +<pre> +<nowiki> +</pre> +</nowiki> +</pre> + +<nowiki> +<pre> +<nowiki> +</pre> +</nowiki> +</pre> + +!! result +<pre> +<nowiki> +</pre> +<p></nowiki> +</pre> +</p><p> +<pre> +<nowiki> +</pre> + +</pre> +</p> +!! end + + +### +### Definition lists +### +!! test +Simple definition +!! input +; name : Definition +!! result +<dl><dt> name </dt><dd> Definition +</dd></dl> + +!! end + +!! test +Definition list for indentation only +!! input +: Indented text +!! result +<dl><dd> Indented text +</dd></dl> + +!! end + +!! test +Definition list with no space +!! input +;name:Definition +!! result +<dl><dt>name</dt><dd>Definition +</dd></dl> + +!!end + +!! test +Definition list with URL link +!! input +; http://example.com/ : definition +!! result +<dl><dt> <a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> </dt><dd> definition +</dd></dl> + +!! end + +!! test +Definition list with bracketed URL link +!! input +;[http://www.example.com/ Example]:Something about it +!! result +<dl><dt><a href="http://www.example.com/" class="external text" rel="nofollow">Example</a></dt><dd>Something about it +</dd></dl> + +!! end + +!! test +Definition list with wikilink containing colon +!! input +; [[Help:FAQ]]: The least-read page on Wikipedia +!! result +<dl><dt> <a href="/index.php?title=Help:FAQ&action=edit&redlink=1" class="new" title="Help:FAQ (page does not exist)">Help:FAQ</a></dt><dd> The least-read page on Wikipedia +</dd></dl> + +!! end + +# At Brion's and JeLuF's insistence... :) +!! test +Definition list with news link containing colon +!! input +; news:alt.wikipedia.rox: This isn't even a real newsgroup! +!! result +<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" rel="nofollow">news:alt.wikipedia.rox</a></dt><dd> This isn't even a real newsgroup! +</dd></dl> + +!! end + +!! test +Malformed definition list with colon +!! input +; news:alt.wikipedia.rox -- don't crash or enter an infinite loop +!! result +<dl><dt> <a href="news:alt.wikipedia.rox" class="external free" rel="nofollow">news:alt.wikipedia.rox</a> -- don't crash or enter an infinite loop +</dt></dl> + +!! end + +!! test +Definition lists: colon in external link text +!! input +; [http://www.wikipedia2.org/ Wikipedia : The Next Generation]: OK, I made that up +!! result +<dl><dt> <a href="http://www.wikipedia2.org/" class="external text" rel="nofollow">Wikipedia : The Next Generation</a></dt><dd> OK, I made that up +</dd></dl> + +!! end + +!! test +Definition lists: colon in HTML attribute +!! input +;<b style="display: inline">bold</b> +!! result +<dl><dt><b style="display: inline">bold</b> +</dt></dl> + +!! end + + +!! test +Definition lists: self-closed tag +!! input +;one<br/>two : two-line fun +!! result +<dl><dt>one<br />two </dt><dd> two-line fun +</dd></dl> + +!! end + + +### +### External links +### +!! test +External links: non-bracketed +!! input +Non-bracketed: http://example.com +!! result +<p>Non-bracketed: <a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> +</p> +!! end + +!! test +External links: numbered +!! input +Numbered: [http://example.com] +Numbered: [http://example.net] +Numbered: [http://example.com] +!! result +<p>Numbered: <a href="http://example.com" class="external autonumber" rel="nofollow">[1]</a> +Numbered: <a href="http://example.net" class="external autonumber" rel="nofollow">[2]</a> +Numbered: <a href="http://example.com" class="external autonumber" rel="nofollow">[3]</a> +</p> +!!end + +!! test +External links: specified text +!! input +Specified text: [http://example.com link] +!! result +<p>Specified text: <a href="http://example.com" class="external text" rel="nofollow">link</a> +</p> +!!end + +!! test +External links: trail +!! input +Linktrails should not work for external links: [http://example.com link]s +!! result +<p>Linktrails should not work for external links: <a href="http://example.com" class="external text" rel="nofollow">link</a>s +</p> +!! end + +!! test +External links: dollar sign in URL +!! input +http://example.com/1$2345 +!! result +<p><a href="http://example.com/1$2345" class="external free" rel="nofollow">http://example.com/1$2345</a> +</p> +!! end + +!! test +External links: dollar sign in URL (named) +!! input +[http://example.com/1$2345] +!! result +<p><a href="http://example.com/1$2345" class="external autonumber" rel="nofollow">[1]</a> +</p> +!!end + +!! test +External links: open square bracket forbidden in URL (bug 4377) +!! input +http://example.com/1[2345 +!! result +<p><a href="http://example.com/1" class="external free" rel="nofollow">http://example.com/1</a>[2345 +</p> +!! end + +!! test +External links: open square bracket forbidden in URL (named) (bug 4377) +!! input +[http://example.com/1[2345] +!! result +<p><a href="http://example.com/1" class="external text" rel="nofollow">[2345</a> +</p> +!!end + +!! test +External links: nowiki in URL link text (bug 6230) +!!input +[http://example.com/ <nowiki>''example site''</nowiki>] +!! result +<p><a href="http://example.com/" class="external text" rel="nofollow">''example site''</a> +</p> +!! end + +!! test +External links: newline forbidden in text (bug 6230 regression check) +!! input +[http://example.com/ first +second] +!! result +<p>[<a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> first +second] +</p> +!!end + +!! test +External image +!! input +External image: http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png +!! result +<p>External image: <img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" /> +</p> +!! end + +!! test +External image from https +!! input +External image from https: https://meta.wikimedia.org/upload/f/f1/Ncwikicol.png +!! result +<p>External image from https: <img src="https://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" /> +</p> +!! end + +!! test +Link to non-http image, no img tag +!! input +Link to non-http image, no img tag: ftp://example.com/test.jpg +!! result +<p>Link to non-http image, no img tag: <a href="ftp://example.com/test.jpg" class="external free" rel="nofollow">ftp://example.com/test.jpg</a> +</p> +!! end + +!! test +External links: terminating separator +!! input +Terminating separator: http://example.com/thing, +!! result +<p>Terminating separator: <a href="http://example.com/thing" class="external free" rel="nofollow">http://example.com/thing</a>, +</p> +!! end + +!! test +External links: intervening separator +!! input +Intervening separator: http://example.com/1,2,3 +!! result +<p>Intervening separator: <a href="http://example.com/1,2,3" class="external free" rel="nofollow">http://example.com/1,2,3</a> +</p> +!! end + +!! test +External links: old bug with URL in query +!! input +Old bug with URL in query: [http://example.com/thing?url=http://example.com link] +!! result +<p>Old bug with URL in query: <a href="http://example.com/thing?url=http://example.com" class="external text" rel="nofollow">link</a> +</p> +!! end + +!! test +External links: old URL-in-URL bug, mixed protocols +!! input +And again with mixed protocols: [ftp://example.com?url=http://example.com link] +!! result +<p>And again with mixed protocols: <a href="ftp://example.com?url=http://example.com" class="external text" rel="nofollow">link</a> +</p> +!!end + +!! test +External links: URL in text +!! input +URL in text: [http://example.com http://example.com] +!! result +<p>URL in text: <a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> +</p> +!! end + +!! test +External links: Clickable images +!! input +ja-style clickable images: [http://example.com http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png] +!! result +<p>ja-style clickable images: <a href="http://example.com" class="external text" rel="nofollow"><img src="http://meta.wikimedia.org/upload/f/f1/Ncwikicol.png" alt="Ncwikicol.png" /></a> +</p> +!!end + +!! test +External links: raw ampersand +!! input +Old & use: http://x&y +!! result +<p>Old & use: <a href="http://x&y" class="external free" rel="nofollow">http://x&y</a> +</p> +!! end + +!! test +External links: encoded ampersand +!! input +Old & use: http://x&y +!! result +<p>Old & use: <a href="http://x&y" class="external free" rel="nofollow">http://x&y</a> +</p> +!! end + +!! test +External links: encoded equals (bug 6102) +!! input +http://example.com/?foo=bar +!! result +<p><a href="http://example.com/?foo=bar" class="external free" rel="nofollow">http://example.com/?foo=bar</a> +</p> +!! end + +!! test +External links: [raw ampersand] +!! input +Old & use: [http://x&y] +!! result +<p>Old & use: <a href="http://x&y" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +External links: [encoded ampersand] +!! input +Old & use: [http://x&y] +!! result +<p>Old & use: <a href="http://x&y" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +External links: [encoded equals] (bug 6102) +!! input +[http://example.com/?foo=bar] +!! result +<p><a href="http://example.com/?foo=bar" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +External links: [IDN ignored character reference in hostname; strip it right off] +!! input +[http://e‌xample.com/] +!! result +<p><a href="http://example.com/" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +External links: IDN ignored character reference in hostname; strip it right off +!! input +http://e‌xample.com/ +!! result +<p><a href="http://example.com/" class="external free" rel="nofollow">http://example.com/</a> +</p> +!! end + +!! test +External links: www.jpeg.org (bug 554) +!! input +http://www.jpeg.org +!!result +<p><a href="http://www.jpeg.org" class="external free" rel="nofollow">http://www.jpeg.org</a> +</p> +!! end + +!! test +External links: URL within URL (original bug 2) +!! input +[http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp] +!! result +<p><a href="http://www.unausa.org/newindex.asp?place=http://www.unausa.org/programs/mun.asp" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +BUG 361: URL inside bracketed URL +!! input +[http://www.example.com/foo http://www.example.com/bar] +!! result +<p><a href="http://www.example.com/foo" class="external text" rel="nofollow">http://www.example.com/bar</a> +</p> +!! end + +!! test +BUG 361: URL within URL, not bracketed +!! input +http://www.example.com/foo?=http://www.example.com/bar +!! result +<p><a href="http://www.example.com/foo?=http://www.example.com/bar" class="external free" rel="nofollow">http://www.example.com/foo?=http://www.example.com/bar</a> +</p> +!! end + +!! test +BUG 289: ">"-token in URL-tail +!! input +http://www.example.com/<hello> +!! result +<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a><hello> +</p> +!!end + +!! test +BUG 289: literal ">"-token in URL-tail +!! input +http://www.example.com/<b>html</b> +!! result +<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a><b>html</b> +</p> +!!end + +!! test +BUG 289: ">"-token in bracketed URL +!! input +[http://www.example.com/<hello> stuff] +!! result +<p><a href="http://www.example.com/" class="external text" rel="nofollow"><hello> stuff</a> +</p> +!!end + +!! test +BUG 289: literal ">"-token in bracketed URL +!! input +[http://www.example.com/<b>html</b> stuff] +!! result +<p><a href="http://www.example.com/" class="external text" rel="nofollow"><b>html</b> stuff</a> +</p> +!!end + +!! test +BUG 289: literal double quote at end of URL +!! input +http://www.example.com/"hello" +!! result +<p><a href="http://www.example.com/" class="external free" rel="nofollow">http://www.example.com/</a>"hello" +</p> +!!end + +!! test +BUG 289: literal double quote in bracketed URL +!! input +[http://www.example.com/"hello" stuff] +!! result +<p><a href="http://www.example.com/" class="external text" rel="nofollow">"hello" stuff</a> +</p> +!!end + +!! test +External links: multiple legal whitespace is fine, Magnus. Don't break it please. (bug 5081) +!! input +[http://www.example.com test] +!! result +<p><a href="http://www.example.com" class="external text" rel="nofollow">test</a> +</p> +!! end + +!! test +External links: wiki links within external link (Bug 3695) +!! input +[http://example.com [[wikilink]] embedded in ext link] +!! result +<p><a href="http://example.com" class="external text" rel="nofollow"></a><a href="/index.php?title=Wikilink&action=edit&redlink=1" class="new" title="Wikilink (page does not exist)">wikilink</a><a href="http://example.com" class="external text" rel="nofollow"> embedded in ext link</a> +</p> +!! end + +!! test +BUG 787: Links with one slash after the url protocol are invalid +!! input +http:/example.com + +[http:/example.com title] +!! result +<p>http:/example.com +</p><p>[http:/example.com title] +</p> +!! end + +!! test +Bug 2702: Mismatched <i>, <b> and <a> tags are invalid +!! input +''[http://example.com text''] +[http://example.com '''text]''' +''Something [http://example.com in italic''] +''Something [http://example.com mixed''''', even bold]''' +'''''Now [http://example.com both'''''] +!! result +<p><a href="http://example.com" class="external text" rel="nofollow"><i>text</i></a> +<a href="http://example.com" class="external text" rel="nofollow"><b>text</b></a> +<i>Something </i><a href="http://example.com" class="external text" rel="nofollow"><i>in italic</i></a> +<i>Something </i><a href="http://example.com" class="external text" rel="nofollow"><i>mixed</i><b>, even bold</b></a> +<i><b>Now </b></i><a href="http://example.com" class="external text" rel="nofollow"><i><b>both</b></i></a> +</p> +!! end + + +!! test +Bug 4781: %26 in URL +!! input +http://www.example.com/?title=AT%26T +!! result +<p><a href="http://www.example.com/?title=AT%26T" class="external free" rel="nofollow">http://www.example.com/?title=AT%26T</a> +</p> +!! end + +!! test +Bug 4781, 5267: %26 in URL +!! input +http://www.example.com/?title=100%25_Bran +!! result +<p><a href="http://www.example.com/?title=100%25_Bran" class="external free" rel="nofollow">http://www.example.com/?title=100%25_Bran</a> +</p> +!! end + +!! test +Bug 4781, 5267: %28, %29 in URL +!! input +http://www.example.com/?title=Ben-Hur_%281959_film%29 +!! result +<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external free" rel="nofollow">http://www.example.com/?title=Ben-Hur_%281959_film%29</a> +</p> +!! end + + +!! test +Bug 4781: %26 in autonumber URL +!! input +[http://www.example.com/?title=AT%26T] +!! result +<p><a href="http://www.example.com/?title=AT%26T" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +Bug 4781, 5267: %26 in autonumber URL +!! input +[http://www.example.com/?title=100%25_Bran] +!! result +<p><a href="http://www.example.com/?title=100%25_Bran" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + +!! test +Bug 4781, 5267: %28, %29 in autonumber URL +!! input +[http://www.example.com/?title=Ben-Hur_%281959_film%29] +!! result +<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external autonumber" rel="nofollow">[1]</a> +</p> +!! end + + +!! test +Bug 4781: %26 in bracketed URL +!! input +[http://www.example.com/?title=AT%26T link] +!! result +<p><a href="http://www.example.com/?title=AT%26T" class="external text" rel="nofollow">link</a> +</p> +!! end + +!! test +Bug 4781, 5267: %26 in bracketed URL +!! input +[http://www.example.com/?title=100%25_Bran link] +!! result +<p><a href="http://www.example.com/?title=100%25_Bran" class="external text" rel="nofollow">link</a> +</p> +!! end + +!! test +Bug 4781, 5267: %28, %29 in bracketed URL +!! input +[http://www.example.com/?title=Ben-Hur_%281959_film%29 link] +!! result +<p><a href="http://www.example.com/?title=Ben-Hur_%281959_film%29" class="external text" rel="nofollow">link</a> +</p> +!! end + +!! test +External link containing double-single-quotes in text '' (bug 4598 sanity check) +!! input +Some [http://example.com/ pretty ''italics'' and stuff]! +!! result +<p>Some <a href="http://example.com/" class="external text" rel="nofollow">pretty <i>italics</i> and stuff</a>! +</p> +!! end + +!! test +External link containing double-single-quotes in text embedded in italics (bug 4598 sanity check) +!! input +''Some [http://example.com/ pretty ''italics'' and stuff]!'' +!! result +<p><i>Some </i><a href="http://example.com/" class="external text" rel="nofollow"><i>pretty </i>italics<i> and stuff</i></a><i>!</i> +</p> +!! end + +!! test +External link containing double-single-quotes with no space separating the url from text in italics +!! input +[http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm''La muerte de Casagemas'' (1901) en el sitio de [[Museo Picasso (París)|Museo Picasso]].] +!! result +<p><a href="http://www.musee-picasso.fr/pages/page_id18528_u1l2.htm" class="external text" rel="nofollow"><i>La muerte de Casagemas</i> (1901) en el sitio de <a href="/index.php?title=Museo_Picasso_(Par%C3%ADs)&action=edit&redlink=1" class="new" title="Museo Picasso (París) (page does not exist)">Museo Picasso</a>.</a> +</p> +!! end + +!! test +URL-encoding in URL functions (single parameter) +!! input +{{localurl:Some page|amp=&}} +!! result +<p>/index.php?title=Some_page&amp=& +</p> +!! end + +!! test +URL-encoding in URL functions (multiple parameters) +!! input +{{localurl:Some page|q=?&=&}} +!! result +<p>/index.php?title=Some_page&q=?&amp=& +</p> +!! end + +### +### Quotes +### + +!! test +Quotes +!! input +Normal text. '''Bold text.''' Normal text. ''Italic text.'' + +Normal text. '''''Bold italic text.''''' Normal text. +!!result +<p>Normal text. <b>Bold text.</b> Normal text. <i>Italic text.</i> +</p><p>Normal text. <i><b>Bold italic text.</b></i> Normal text. +</p> +!! end + + +!! test +Unclosed and unmatched quotes +!! input +'''''Bold italic text '''with bold deactivated''' in between.''''' + +'''''Bold italic text ''with italic deactivated'' in between.''''' + +'''Bold text.. + +..spanning two paragraphs (should not work).''' + +'''Bold tag left open + +''Italic tag left open + +Normal text. + +<!-- Unmatching number of opening, closing tags: --> +'''This year''''s election ''should'' beat '''last year''''s. + +''Tom'''s car is bigger than ''Susan'''s. +!! result +<p><i><b>Bold italic text </b>with bold deactivated<b> in between.</b></i> +</p><p><b><i>Bold italic text </i>with italic deactivated<i> in between.</i></b> +</p><p><b>Bold text..</b> +</p><p>..spanning two paragraphs (should not work). +</p><p><b>Bold tag left open</b> +</p><p><i>Italic tag left open</i> +</p><p>Normal text. +</p><p><b>This year'</b>s election <i>should</i> beat <b>last year'</b>s. +</p><p><i>Tom<b>s car is bigger than </b></i><b>Susan</b>s. +</p> +!! end + +### +### Tables +### +### some content taken from http://meta.wikimedia.org/wiki/MediaWiki_User%27s_Guide:_Using_tables +### + +# This should not produce <table></table> as <table><tr><td></td></tr></table> +# is the bare minimun required by the spec, see: +# http://www.w3.org/TR/xhtml-modularization/dtd_module_defs.html#a_module_Basic_Tables +!! test +A table with no data. +!! input +{||} +!! result +!! end + +# A table with nothing but a caption is invalid XHTML, we might want to render +# this as <p>caption</p> +!! test +A table with nothing but a caption +!! input +{| +|+ caption +|} +!! result +<table> +<caption> caption +</caption><tr><td></td></tr></table> + +!! end + +!! test +Simple table +!! input +{| +| 1 || 2 +|- +| 3 || 4 +|} +!! result +<table> +<tr> +<td> 1 </td> +<td> 2 +</td></tr> +<tr> +<td> 3 </td> +<td> 4 +</td></tr></table> + +!! end + +!! test +Multiplication table +!! input +{| border="1" cellpadding="2" +|+Multiplication table +|- +! × !! 1 !! 2 !! 3 +|- +! 1 +| 1 || 2 || 3 +|- +! 2 +| 2 || 4 || 6 +|- +! 3 +| 3 || 6 || 9 +|- +! 4 +| 4 || 8 || 12 +|- +! 5 +| 5 || 10 || 15 +|} +!! result +<table border="1" cellpadding="2"> +<caption>Multiplication table +</caption> +<tr> +<th> × </th> +<th> 1 </th> +<th> 2 </th> +<th> 3 +</th></tr> +<tr> +<th> 1 +</th> +<td> 1 </td> +<td> 2 </td> +<td> 3 +</td></tr> +<tr> +<th> 2 +</th> +<td> 2 </td> +<td> 4 </td> +<td> 6 +</td></tr> +<tr> +<th> 3 +</th> +<td> 3 </td> +<td> 6 </td> +<td> 9 +</td></tr> +<tr> +<th> 4 +</th> +<td> 4 </td> +<td> 8 </td> +<td> 12 +</td></tr> +<tr> +<th> 5 +</th> +<td> 5 </td> +<td> 10 </td> +<td> 15 +</td></tr></table> + +!! end + +!! test +Table rowspan +!! input +{| align=right border=1 +| Cell 1, row 1 +|rowspan=2| Cell 2, row 1 (and 2) +| Cell 3, row 1 +|- +| Cell 1, row 2 +| Cell 3, row 2 +|} +!! result +<table align="right" border="1"> +<tr> +<td> Cell 1, row 1 +</td> +<td rowspan="2"> Cell 2, row 1 (and 2) +</td> +<td> Cell 3, row 1 +</td></tr> +<tr> +<td> Cell 1, row 2 +</td> +<td> Cell 3, row 2 +</td></tr></table> + +!! end + +!! test +Nested table +!! input +{| border=1 +| α +| +{| bgcolor=#ABCDEF border=2 +|nested +|- +|table +|} +|the original table again +|} +!! result +<table border="1"> +<tr> +<td> α +</td> +<td> +<table bgcolor="#ABCDEF" border="2"> +<tr> +<td>nested +</td></tr> +<tr> +<td>table +</td></tr></table> +</td> +<td>the original table again +</td></tr></table> + +!! end + +!! test +Invalid attributes in table cell (bug 1830) +!! input +{| +|Cell:|broken +|} +!! result +<table> +<tr> +<td>broken +</td></tr></table> + +!! end + + +!! test +Table security: embedded pipes (http://lists.wikimedia.org/mailman/htdig/wikitech-l/2006-April/022293.html) +!! input +{| +| |[ftp://|x||]" onmouseover="alert(document.cookie)">test +!! result +<table> +<tr> +<td>[<a href="ftp://%7Cx" class="external free" rel="nofollow">ftp://%7Cx</a></td> +<td>]" onmouseover="alert(document.cookie)">test +</td> +</tr> +</table> + +!! end + + +### +### Internal links +### +!! test +Plain link, capitalized +!! input +[[Main Page]] +!! result +<p><a href="/wiki/Main_Page">Main Page</a> +</p> +!! end + +!! test +Plain link, uncapitalized +!! input +[[main Page]] +!! result +<p><a href="/wiki/Main_Page">main Page</a> +</p> +!! end + +!! test +Piped link +!! input +[[Main Page|The Main Page]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">The Main Page</a> +</p> +!! end + +!! test +Broken link +!! input +[[Zigzagzogzagzig]] +!! result +<p><a href="/index.php?title=Zigzagzogzagzig&action=edit&redlink=1" class="new" title="Zigzagzogzagzig (page does not exist)">Zigzagzogzagzig</a> +</p> +!! end + +!! test +Broken link with fragment +!! input +[[Zigzagzogzagzig#zug]] +!! result +<p><a href="/index.php?title=Zigzagzogzagzig&action=edit&redlink=1" class="new" title="Zigzagzogzagzig (page does not exist)">Zigzagzogzagzig#zug</a> +</p> +!! end + +!! test +Special page link with fragment +!! input +[[Special:Version#anchor]] +!! result +<p><a href="/wiki/Special:Version#anchor" title="Special:Version">Special:Version#anchor</a> +</p> +!! end + +!! test +Nonexistent special page link with fragment +!! input +[[Special:ThisNameWillHopefullyNeverBeUsed#anchor]] +!! result +<p><a href="/wiki/Special:ThisNameWillHopefullyNeverBeUsed" class="new" title="Special:ThisNameWillHopefullyNeverBeUsed (page does not exist)">Special:ThisNameWillHopefullyNeverBeUsed#anchor</a> +</p> +!! end + +!! test +Link with prefix +!! input +xxx[[main Page]], xxx[[Main Page]], Xxx[[main Page]] XXX[[main Page]], XXX[[Main Page]] +!! result +<p>xxx<a href="/wiki/Main_Page">main Page</a>, xxx<a href="/wiki/Main_Page">Main Page</a>, Xxx<a href="/wiki/Main_Page">main Page</a> XXX<a href="/wiki/Main_Page">main Page</a>, XXX<a href="/wiki/Main_Page">Main Page</a> +</p> +!! end + +!! test +Link with suffix +!! input +[[Main Page]]xxx, [[Main Page]]XXX, [[Main Page]]!!! +!! result +<p><a href="/wiki/Main_Page" title="Main Page">Main Pagexxx</a>, <a href="/wiki/Main_Page">Main Page</a>XXX, <a href="/wiki/Main_Page">Main Page</a>!!! +</p> +!! end + +!! test +Link with 3 brackets +!! input +[[[main page]]] +!! result +<p>[[[main page]]] +</p> +!! end + +!! test +Piped link with 3 brackets +!! input +[[[main page|the main page]]] +!! result +<p>[[[main page|the main page]]] +</p> +!! end + +!! test +Link with multiple pipes +!! input +[[Main Page|The|Main|Page]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">The|Main|Page</a> +</p> +!! end + +!! test +Link to namespaces +!! input +[[Talk:Parser testing]], [[Meta:Disclaimers]] +!! result +<p><a href="/index.php?title=Talk:Parser_testing&action=edit&redlink=1" class="new" title="Talk:Parser testing (page does not exist)">Talk:Parser testing</a>, <a href="/index.php?title=Meta:Disclaimers&action=edit&redlink=1" class="new" title="Meta:Disclaimers (page does not exist)">Meta:Disclaimers</a> +</p> +!! end + +!! test +Piped link to namespace +!! input +[[Meta:Disclaimers|The disclaimers]] +!! result +<p><a href="/index.php?title=Meta:Disclaimers&action=edit&redlink=1" class="new" title="Meta:Disclaimers (page does not exist)">The disclaimers</a> +</p> +!! end + +!! test +Link containing } +!! input +[[Usually caused by a typo (oops}]] +!! result +<p>[[Usually caused by a typo (oops}]] +</p> +!! end + +!! test +Link containing % (not as a hex sequence) +!! input +[[7% Solution]] +!! result +<p><a href="/index.php?title=7%25_Solution&action=edit&redlink=1" class="new" title="7% Solution (page does not exist)">7% Solution</a> +</p> +!! end + +!! test +Link containing % as a single hex sequence interpreted to char +!! input +[[7%25 Solution]] +!! result +<p><a href="/index.php?title=7%25_Solution&action=edit&redlink=1" class="new" title="7% Solution (page does not exist)">7% Solution</a> +</p> +!!end + +!! test +Link containing % as a double hex sequence interpreted to hex sequence +!! input +[[7%2525 Solution]] +!! result +<p>[[7%2525 Solution]] +</p> +!!end + +!! test +Link containing "#<" and "#>" % as a hex sequences- these are valid section anchors +Example for such a section: == < == +!! input +[[%23%3c]][[%23%3e]] +!! result +<p><a href="#.3C">#<</a><a href="#.3E">#></a> +</p> +!! end + +!! test +Link containing "<#" and ">#" as a hex sequences +!! input +[[%3c%23]][[%3e%23]] +!! result +<p>[[%3c%23]][[%3e%23]] +</p> +!! end + +!! test +Link containing double-single-quotes '' (bug 4598) +!! input +[[Lista d''e paise d''o munno]] +!! result +<p><a href="/index.php?title=Lista_d%27%27e_paise_d%27%27o_munno&action=edit&redlink=1" class="new" title="Lista d''e paise d''o munno (page does not exist)">Lista d''e paise d''o munno</a> +</p> +!! end + +!! test +Link containing double-single-quotes '' in text (bug 4598 sanity check) +!! input +Some [[Link|pretty ''italics'' and stuff]]! +!! result +<p>Some <a href="/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">pretty <i>italics</i> and stuff</a>! +</p> +!! end + +!! test +Link containing double-single-quotes '' in text embedded in italics (bug 4598 sanity check) +!! input +''Some [[Link|pretty ''italics'' and stuff]]! +!! result +<p><i>Some <a href="/index.php?title=Link&action=edit&redlink=1" class="new" title="Link (page does not exist)">pretty <i>italics</i> and stuff</a>!</i> +</p> +!! end + +!! test +Link with double quotes in title part (literal) and alternate part (interpreted) +!! input +[[File:Denys Savchenko ''Pentecoste''.jpg]] + +[[''Pentecoste'']] + +[[''Pentecoste''|Pentecoste]] + +[[''Pentecoste''|''Pentecoste'']] +!! result +<p><a href="/index.php?title=Special:Upload&wpDestFile=Denys_Savchenko_%27%27Pentecoste%27%27.jpg" class="new" title="File:Denys Savchenko ''Pentecoste''.jpg">File:Denys Savchenko <i>Pentecoste</i>.jpg</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)">''Pentecoste''</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)">Pentecoste</a> +</p><p><a href="/index.php?title=%27%27Pentecoste%27%27&action=edit&redlink=1" class="new" title="''Pentecoste'' (page does not exist)"><i>Pentecoste</i></a> +</p> +!! end + +!! test +Plain link to URL +!! input +[[http://www.example.com]] +!! result +<p>[<a href="http://www.example.com" class="external autonumber" rel="nofollow">[1]</a>] +</p> +!! end + +# I'm fairly sure the expected result here is wrong. +# We want these to be URL links, not pseudo-pages with URLs for titles.... +# However the current output is also pretty screwy. +# +# ---- +# I'm changing it to match the current output--it arguably makes more +# sense in the light of the test above. Old expected result was: +#<p>Piped link to URL: <a href="/index.php?title=Http://www.example.com&action=edit" class="new">an example URL</a> +#</p> +# But I think this test is bordering on "garbage in, garbage out" anyway. +# -- wtm +!! test +Piped link to URL +!! input +Piped link to URL: [[http://www.example.com|an example URL]] +!! result +<p>Piped link to URL: [<a href="http://www.example.com%7Can" class="external text" rel="nofollow">example URL</a>] +</p> +!! end + +!! test +BUG 2: [[page|http://url/]] should link to page, not http://url/ +!! input +[[Main Page|http://url/]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">http://url/</a> +</p> +!! end + +!! test +BUG 337: Escaped self-links should be bold +!! options +title=[[Bug462]] +!! input +[[Bug462]] [[Bug462]] +!! result +<p><strong class="selflink">Bug462</strong> <strong class="selflink">Bug462</strong> +</p> +!! end + +!! test +Self-link to section should not be bold +!! options +title=[[Main Page]] +!! input +[[Main Page#section]] +!! result +<p><a href="/wiki/Main_Page#section" title="Main Page">Main Page#section</a> +</p> +!! end + +!! article +00 +!! text +This is 00. +!! endarticle + +!!test +Self-link to numeric title +!!options +title=[[0]] +!!input +[[0]] +!!result +<p><strong class="selflink">0</strong> +</p> +!!end + +!!test +Link to numeric-equivalent title +!!options +title=[[0]] +!!input +[[00]] +!!result +<p><a href="/wiki/00">00</a> +</p> +!!end + +!! test +<nowiki> inside a link +!! input +[[Main<nowiki> Page</nowiki>]] [[Main Page|the main page <nowiki>[it's not very good]</nowiki>]] +!! result +<p>[[Main Page]] <a href="/wiki/Main_Page" title="Main Page">the main page [it's not very good]</a> +</p> +!! end + +!! test +Non-breaking spaces in title +!! input +[[ Main Page ]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page"> Main Page </a> +</p> +!!end + + +### +### Interwiki links (see maintenance/interwiki.sql) +### + +!! test +Inline interwiki link +!! input +[[MeatBall:SoftSecurity]] +!! result +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity" class="extiw">MeatBall:SoftSecurity</a> +</p> +!! end + +!! test +Inline interwiki link with empty title (bug 2372) +!! input +[[MeatBall:]] +!! result +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?" class="extiw">MeatBall:</a> +</p> +!! end + +!! test +Interwiki link encoding conversion (bug 1636) +!! input +*[[Wikipedia:ro:Olteniţa]] +*[[Wikipedia:ro:Olteniţa]] +!! result +<ul><li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> +</li><li><a href="http://en.wikipedia.org/wiki/ro:Olteni%C5%A3a" class="extiw" title="wikipedia:ro:Olteniţa">Wikipedia:ro:Olteniţa</a> +</li></ul> + +!! end + +!! test +Interwiki link with fragment (bug 2130) +!! input +[[MeatBall:SoftSecurity#foo]] +!! result +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?SoftSecurity#foo" class="extiw" title="meatball:SoftSecurity">MeatBall:SoftSecurity#foo</a> +</p> +!! end + +!! test +Interlanguage link +!! input +Blah blah blah +[[zh:Chinese]] +!!result +<p>Blah blah blah +</p> +!! end + +!! test +Double interlanguage link +!! input +Blah blah blah +[[es:Spanish]] +[[zh:Chinese]] +!!result +<p>Blah blah blah +</p> +!! end + +!! test +Interlanguage link, with prefix links +!! options +language=ln +!! input +Blah blah blah +[[zh:Chinese]] +!!result +<p>Blah blah blah +</p> +!! end + +!! test +Double interlanguage link, with prefix links (bug 8897) +!! options +language=ln +!! input +Blah blah blah +[[es:Spanish]] +[[zh:Chinese]] +!!result +<p>Blah blah blah +</p> +!! end + + +## +## XHTML tidiness +### + +!! test +<br> to <br /> +!! input +1<br>2<br />3 +!! result +<p>1<br />2<br />3 +</p> +!! end + +!! test +Incorrecly removing closing slashes from correctly formed XHTML +!! input +<br style="clear:both;" /> +!! result +<p><br style="clear:both;" /> +</p> +!! end + +!! test +Failing to transform badly formed HTML into correct XHTML +!! input +<br clear=left> +<br clear=right> +<br clear=all> +!! result +<p><br clear="left" /> +<br clear="right" /> +<br clear="all" /> +</p> +!!end + +!! test +Horizontal ruler (should it add that extra space?) +!! input +<hr> +<hr > +foo <hr +> bar +!! result +<hr /> +<hr /> +foo <hr /> bar + +!! end + +### +### Block-level elements +### +!! test +Common list +!! input +*Common list +* item 2 +*item 3 +!! result +<ul><li>Common list +</li><li> item 2 +</li><li>item 3 +</li></ul> + +!! end + +!! test +Numbered list +!! input +#Numbered list +#item 2 +# item 3 +!! result +<ol><li>Numbered list +</li><li>item 2 +</li><li> item 3 +</li></ol> + +!! end + +!! test +Mixed list +!! input +*Mixed list +*# with numbers +** and bullets +*# and numbers +*bullets again +**bullet level 2 +***bullet level 3 +***#Number on level 4 +**bullet level 2 +**#Number on level 3 +**#Number on level 3 +*#number level 2 +*Level 1 +!! result +<ul><li>Mixed list +<ol><li> with numbers +</li></ol> +<ul><li> and bullets +</li></ul> +<ol><li> and numbers +</li></ol> +</li><li>bullets again +<ul><li>bullet level 2 +<ul><li>bullet level 3 +<ol><li>Number on level 4 +</li></ol> +</li></ul> +</li><li>bullet level 2 +<ol><li>Number on level 3 +</li><li>Number on level 3 +</li></ol> +</li></ul> +<ol><li>number level 2 +</li></ol> +</li><li>Level 1 +</li></ul> + +!! end + +!! test +List items are not parsed correctly following a <pre> block (bug 785) +!! input +* <pre>foo</pre> +* <pre>bar</pre> +* zar +!! result +<ul><li> <pre>foo</pre> +</li><li> <pre>bar</pre> +</li><li> zar +</li></ul> + +!! end + +### +### Magic Words +### + +!! test +Magic Word: {{CURRENTDAY}} +!! input +{{CURRENTDAY}} +!! result +<p>1 +</p> +!! end + +!! test +Magic Word: {{CURRENTDAY2}} +!! input +{{CURRENTDAY2}} +!! result +<p>01 +</p> +!! end + +!! test +Magic Word: {{CURRENTDAYNAME}} +!! input +{{CURRENTDAYNAME}} +!! result +<p>Thursday +</p> +!! end + +!! test +Magic Word: {{CURRENTDOW}} +!! input +{{CURRENTDOW}} +!! result +<p>4 +</p> +!! end + +!! test +Magic Word: {{CURRENTMONTH}} +!! input +{{CURRENTMONTH}} +!! result +<p>01 +</p> +!! end + +!! test +Magic Word: {{CURRENTMONTHABBREV}} +!! input +{{CURRENTMONTHABBREV}} +!! result +<p>Jan +</p> +!! end + +!! test +Magic Word: {{CURRENTMONTHNAME}} +!! input +{{CURRENTMONTHNAME}} +!! result +<p>January +</p> +!! end + +!! test +Magic Word: {{CURRENTMONTHNAMEGEN}} +!! input +{{CURRENTMONTHNAMEGEN}} +!! result +<p>January +</p> +!! end + +!! test +Magic Word: {{CURRENTTIME}} +!! input +{{CURRENTTIME}} +!! result +<p>00:02 +</p> +!! end + +!! test +Magic Word: {{CURRENTWEEK}} (@bug 4594) +!! input +{{CURRENTWEEK}} +!! result +<p>1 +</p> +!! end + +!! test +Magic Word: {{CURRENTYEAR}} +!! input +{{CURRENTYEAR}} +!! result +<p>1970 +</p> +!! end + +!! test +Magic Word: {{FULLPAGENAME}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{FULLPAGENAME}} +!! result +<p>User:Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{FULLPAGENAMEE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{FULLPAGENAMEE}} +!! result +<p>User:%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test +Magic Word: {{NAMESPACE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{NAMESPACE}} +!! result +<p>User +</p> +!! end + +!! test +Magic Word: {{NAMESPACEE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{NAMESPACEE}} +!! result +<p>User +</p> +!! end + +!! test +Magic Word: {{NUMBEROFFILES}} +!! input +{{NUMBEROFFILES}} +!! result +<p>2 +</p> +!! end + +!! test +Magic Word: {{PAGENAME}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{PAGENAME}} +!! result +<p>Ævar Arnfjörð Bjarmason +</p> +!! end + +!! test +Magic Word: {{PAGENAMEE}} +!! options +title=[[User:Ævar Arnfjörð Bjarmason]] +!! input +{{PAGENAMEE}} +!! result +<p>%C3%86var_Arnfj%C3%B6r%C3%B0_Bjarmason +</p> +!! end + +!! test +Magic Word: {{REVISIONID}} +!! input +{{REVISIONID}} +!! result +<p>1337 +</p> +!! end + +!! test +Magic Word: {{SCRIPTPATH}} +!! input +{{SCRIPTPATH}} +!! result +<p>/ +</p> +!! end + +!! test +Magic Word: {{SERVER}} +!! input +{{SERVER}} +!! result +<p><a href="http://Britney-Spears" class="external free" rel="nofollow">http://Britney-Spears</a> +</p> +!! end + +!! test +Magic Word: {{SERVERNAME}} +!! input +{{SERVERNAME}} +!! result +<p>Britney-Spears +</p> +!! end + +!! test +Magic Word: {{SITENAME}} +!! input +{{SITENAME}} +!! result +<p>MediaWiki +</p> +!! end + +!! test +Namespace 1 {{ns:1}} +!! input +{{ns:1}} +!! result +<p>Talk +</p> +!! end + +!! test +Namespace 1 {{ns:01}} +!! input +{{ns:01}} +!! result +<p>Talk +</p> +!! end + +!! test +Namespace 0 {{ns:0}} (bug 4783) +!! input +{{ns:0}} +!! result + +!! end + +!! test +Namespace 0 {{ns:00}} (bug 4783) +!! input +{{ns:00}} +!! result + +!! end + +!! test +Namespace -1 {{ns:-1}} +!! input +{{ns:-1}} +!! result +<p>Special +</p> +!! end + +!! test +Namespace User {{ns:User}} +!! input +{{ns:User}} +!! result +<p>User +</p> +!! end + +!! test +Namespace User talk {{ns:User_talk}} +!! input +{{ns:User_talk}} +!! result +<p>User talk +</p> +!! end + +!! test +Namespace User talk {{ns:uSeR tAlK}} +!! input +{{ns:uSeR tAlK}} +!! result +<p>User talk +</p> +!! end + +!! test +Namespace File {{ns:File}} +!! input +{{ns:File}} +!! result +<p>File +</p> +!! end + +!! test +Namespace File {{ns:Image}} +!! input +{{ns:Image}} +!! result +<p>File +</p> +!! end + +!! test +Namespace (lang=de) Benutzer {{ns:User}} +!! options +language=de +!! input +{{ns:User}} +!! result +<p>Benutzer +</p> +!! end + +!! test +Namespace (lang=de) Benutzer Diskussion {{ns:3}} +!! options +language=de +!! input +{{ns:3}} +!! result +<p>Benutzer Diskussion +</p> +!! end + + +!! test +Urlencode +!! input +{{urlencode:hi world?!}} +{{urlencode:hi world?!|WIKI}} +{{urlencode:hi world?!|PATH}} +{{urlencode:hi world?!|QUERY}} +!! result +<p>hi+world%3F%21 +hi_world%3F! +hi%20world%3F%21 +hi+world%3F%21 +</p> +!! end + +### +### Magic links +### +!! test +Magic links: internal link to RFC (bug 479) +!! input +[[RFC 123]] +!! result +<p><a href="/index.php?title=RFC_123&action=edit&redlink=1" class="new" title="RFC 123 (page does not exist)">RFC 123</a> +</p> +!! end + +!! test +Magic links: RFC (bug 479) +!! input +RFC 822 +!! result +<p><a href="http://tools.ietf.org/html/rfc822" class="external mw-magiclink-rfc">RFC 822</a> +</p> +!! end + +!! test +Magic links: ISBN (bug 1937) +!! input +ISBN 0-306-40615-2 +!! result +<p><a href="/wiki/Special:BookSources/0306406152" class="internal mw-magiclink-isbn">ISBN 0-306-40615-2</a> +</p> +!! end + +!! test +Magic links: PMID incorrectly converts space to underscore +!! input +PMID 1234 +!! result +<p><a href="http://www.ncbi.nlm.nih.gov/pubmed/1234?dopt=Abstract" class="external mw-magiclink-pmid">PMID 1234</a> +</p> +!! end + +### +### Templates +#### + +!! test +Nonexistent template +!! input +{{thistemplatedoesnotexist}} +!! result +<p><a href="/index.php?title=Template:Thistemplatedoesnotexist&action=edit&redlink=1" class="new" title="Template:Thistemplatedoesnotexist (page does not exist)">Template:Thistemplatedoesnotexist</a> +</p> +!! end + +!! article +Template:test +!! text +This is a test template +!! endarticle + +!! test +Simple template +!! input +{{test}} +!! result +<p>This is a test template +</p> +!! end + +!! test +Template with explicit namespace +!! input +{{Template:test}} +!! result +<p>This is a test template +</p> +!! end + + +!! article +Template:paramtest +!! text +This is a test template with parameter {{{param}}} +!! endarticle + +!! test +Template parameter +!! input +{{paramtest|param=foo}} +!! result +<p>This is a test template with parameter foo +</p> +!! end + +!! article +Template:paramtestnum +!! text +[[{{{1}}}|{{{2}}}]] +!! endarticle + +!! test +Template unnamed parameter +!! input +{{paramtestnum|Main Page|the main page}} +!! result +<p><a href="/wiki/Main_Page" title="Main Page">the main page</a> +</p> +!! end + +!! article +Template:templatesimple +!! text +(test) +!! endarticle + +!! article +Template:templateredirect +!! text +#redirect [[Template:templatesimple]] +!! endarticle + +!! article +Template:templateasargtestnum +!! text +{{{{{1}}}}} +!! endarticle + +!! article +Template:templateasargtest +!! text +{{template{{{templ}}}}} +!! endarticle + +!! article +Template:templateasargtest2 +!! text +{{{{{templ}}}}} +!! endarticle + +!! test +Template with template name as unnamed argument +!! input +{{templateasargtestnum|templatesimple}} +!! result +<p>(test) +</p> +!! end + +!! test +Template with template name as argument +!! input +{{templateasargtest|templ=simple}} +!! result +<p>(test) +</p> +!! end + +!! test +Template with template name as argument (2) +!! input +{{templateasargtest2|templ=templatesimple}} +!! result +<p>(test) +</p> +!! end + +!! article +Template:templateasargtestdefault +!! text +{{{{{templ|templatesimple}}}}} +!! endarticle + +!! article +Template:templa +!! text +'''templ''' +!! endarticle + +!! test +Template with default value +!! input +{{templateasargtestdefault}} +!! result +<p>(test) +</p> +!! end + +!! test +Template with default value (value set) +!! input +{{templateasargtestdefault|templ=templa}} +!! result +<p><b>templ</b> +</p> +!! end + +!! test +Template redirect +!! input +{{templateredirect}} +!! result +<p>(test) +</p> +!! end + +!! test +Template with argument in separate line +!! input +{{ templateasargtest | + templ = simple }} +!! result +<p>(test) +</p> +!! end + +!! test +Template with complex template as argument +!! input +{{paramtest| + param ={{ templateasargtest | + templ = simple }}}} +!! result +<p>This is a test template with parameter (test) +</p> +!! end + +!! test +Template with thumb image (with link in description) +!! input +{{paramtest| + param =[[Image:noimage.png|thumb|[[no link|link]] [[no link|caption]]]]}} +!! result +This is a test template with parameter <div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/index.php?title=Special:Upload&wpDestFile=Noimage.png" class="new" title="File:Noimage.png">File:Noimage.png</a> <div class="thumbcaption"><a href="/index.php?title=No_link&action=edit&redlink=1" class="new" title="No link (page does not exist)">link</a> <a href="/index.php?title=No_link&action=edit&redlink=1" class="new" title="No link (page does not exist)">caption</a></div></div></div> + +!! end + +!! article +Template:complextemplate +!! text +{{{1}}} {{paramtest| + param ={{{param}}}}} +!! endarticle + +!! test +Template with complex arguments +!! input +{{complextemplate| + param ={{ templateasargtest | + templ = simple }}|[[Template:complextemplate|link]]}} +!! result +<p><a href="/wiki/Template:Complextemplate" title="Template:Complextemplate">link</a> This is a test template with parameter (test) +</p> +!! end + +!! test +BUG 553: link with two variables in a piped link +!! input +{| +|[[{{{1}}}|{{{2}}}]] +|} +!! result +<table> +<tr> +<td>[[{{{1}}}|{{{2}}}]] +</td></tr></table> + +!! end + +!! test +Magic variable as template parameter +!! input +{{paramtest|param={{SITENAME}}}} +!! result +<p>This is a test template with parameter MediaWiki +</p> +!! end + +!! article +Template:linktest +!! text +[[{{{param}}}|link]] +!! endarticle + +!! test +Template parameter as link source +!! input +{{linktest|param=Main Page}} +!! result +<p><a href="/wiki/Main_Page" title="Main Page">link</a> +</p> +!! end + + +!!article +Template:paramtest2 +!! text +including another template, {{paramtest|param={{{arg}}}}} +!! endarticle + +!! test +Template passing argument to another template +!! input +{{paramtest2|arg='hmm'}} +!! result +<p>including another template, This is a test template with parameter 'hmm' +</p> +!! end + +!! article +Template:Linktest2 +!! text +Main Page +!! endarticle + +!! test +Template as link source +!! input +[[{{linktest2}}]] +!! result +<p><a href="/wiki/Main_Page">Main Page</a> +</p> +!! end + + +!! article +Template:loop1 +!! text +{{loop2}} +!! endarticle + +!! article +Template:loop2 +!! text +{{loop1}} +!! endarticle + +!! test +Template infinite loop +!! input +{{loop1}} +!! result +<p><span class="error">Template loop detected: <a href="/wiki/Template:Loop1">Template:Loop1</a></span> +</p> +!! end + +!! test +Template from main namespace +!! input +{{:Main Page}} +!! result +<p>blah blah +</p> +!! end + +!! article +Template:table +!! text +{| +| 1 || 2 +|- +| 3 || 4 +|} +!! endarticle + +!! test +BUG 529: Template with table, not included at beginning of line +!! input +foo {{table}} +!! result +<p>foo +</p> +<table> +<tr> +<td> 1 </td> +<td> 2 +</td></tr> +<tr> +<td> 3 </td> +<td> 4 +</td></tr></table> + +!! end + +!! test +BUG 523: Template shouldn't eat newline (or add an extra one before table) +!! input +foo +{{table}} +!! result +<p>foo +</p> +<table> +<tr> +<td> 1 </td> +<td> 2 +</td></tr> +<tr> +<td> 3 </td> +<td> 4 +</td></tr></table> + +!! end + +!! test +BUG 41: Template parameters shown as broken links +!! input +{{{parameter}}} +!! result +<p>{{{parameter}}} +</p> +!! end + + +!! article +Template:MSGNW test +!! text +''None'' of '''this''' should be +* interpreted + but rather passed unmodified +{{test}} +!! endarticle + +# hmm, fix this or just deprecate msgnw and document its behavior? +!! test +msgnw keyword +!! options +disabled +!! input +{{msgnw:MSGNW test}} +!! result +<p>''None'' of '''this''' should be +* interpreted + but rather passed unmodified +{{test}} +</p> +!! end + +!! test +int keyword +!! input +{{int:youhavenewmessages|lots of money|not!}} +!! result +<p>You have lots of money (not!). +</p> +!! end + +!! article +Template:Includes +!! text +Foo<noinclude>zar</noinclude><includeonly>bar</includeonly> +!! endarticle + +!! test +<includeonly> and <noinclude> being included +!! input +{{Includes}} +!! result +<p>Foobar +</p> +!! end + +!! article +Template:Includes2 +!! text +<onlyinclude>Foo</onlyinclude>bar +!! endarticle + +!! test +<onlyinclude> being included +!! input +{{Includes2}} +!! result +<p>Foo +</p> +!! end + + +!! article +Template:Includes3 +!! text +<onlyinclude>Foo</onlyinclude>bar<includeonly>zar</includeonly> +!! endarticle + +!! test +<onlyinclude> and <includeonly> being included +!! input +{{Includes3}} +!! result +<p>Foo +</p> +!! end + +!! test +<includeonly> and <noinclude> on a page +!! input +Foo<noinclude>zar</noinclude><includeonly>bar</includeonly> +!! result +<p>Foozar +</p> +!! end + +!! test +<onlyinclude> on a page +!! input +<onlyinclude>Foo</onlyinclude>bar +!! result +<p>Foobar +</p> +!! end + +!! article +Template:Includeonly section +!! text +<includeonly> +==Includeonly section== +</includeonly> +==Section T-1== +!!endarticle + +!! test +Bug 6563: Edit link generation for section shown by <includeonly> +!! input +{{includeonly section}} +!! result +<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-1" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Includeonly_section">Includeonly section</span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Template:Includeonly_section&action=edit&section=T-2" title="Template:Includeonly section">edit</a>]</span> <span class="mw-headline" id="Section_T-1">Section T-1</span></h2> + +!! end + +# Uses same input as the contents of [[Template:Includeonly section]] +!! test +Bug 6563: Section extraction for section shown by <includeonly> +!! options +section=T-2 +!! input +<includeonly> +==Includeonly section== +</includeonly> +==Section T-2== +!! result +==Section T-2== +!! end + +!! test +Bug 6563: Edit link generation for section suppressed by <includeonly> +!! input +<includeonly> +==Includeonly section== +</includeonly> +==Section 1== +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2> + +!! end + +!! test +Bug 6563: Section extraction for section suppressed by <includeonly> +!! options +section=1 +!! input +<includeonly> +==Includeonly section== +</includeonly> +==Section 1== +!! result +==Section 1== +!! end + +### +### Pre-save transform tests +### +!! test +pre-save transform: subst: +!! options +PST +!! input +{{subst:test}} +!! result +This is a test template +!! end + +!! test +pre-save transform: normal template +!! options +PST +!! input +{{test}} +!! result +{{test}} +!! end + +!! test +pre-save transform: nonexistent template +!! options +PST +!! input +{{thistemplatedoesnotexist}} +!! result +{{thistemplatedoesnotexist}} +!! end + + +!! test +pre-save transform: subst magic variables +!! options +PST +!! input +{{subst:SITENAME}} +!! result +MediaWiki +!! end + +# This is bug 89, which I fixed. -- wtm +!! test +pre-save transform: subst: templates with parameters +!! options +pst +!! input +{{subst:paramtest|param="something else"}} +!! result +This is a test template with parameter "something else" +!! end + +!! article +Template:nowikitest +!! text +<nowiki>'''not wiki'''</nowiki> +!! endarticle + +!! test +pre-save transform: nowiki in subst (bug 1188) +!! options +pst +!! input +{{subst:nowikitest}} +!! result +<nowiki>'''not wiki'''</nowiki> +!! end + + +!! article +Template:commenttest +!! text +This template has <!-- a comment --> in it. +!! endarticle + +!! test +pre-save transform: comment in subst (bug 1936) +!! options +pst +!! input +{{subst:commenttest}} +!! result +This template has <!-- a comment --> in it. +!! end + +!! test +pre-save transform: unclosed tag +!! options +pst noxml +!! input +<nowiki>'''not wiki''' +!! result +<nowiki>'''not wiki''' +!! end + +!! test +pre-save transform: mixed tag case +!! options +pst noxml +!! input +<NOwiki>'''not wiki'''</noWIKI> +!! result +<NOwiki>'''not wiki'''</noWIKI> +!! end + +!! test +pre-save transform: unclosed comment in <nowiki> +!! options +pst noxml +!! input +wiki<nowiki>nowiki<!--nowiki</nowiki>wiki +!! result +wiki<nowiki>nowiki<!--nowiki</nowiki>wiki +!!end + +!! article +Template:dangerous +!!text +<span onmouseover="alert('crap')">Oh no</span> +!!endarticle + +!!test +(confirming safety of fix for subst bug 1936) +!! input +{{Template:dangerous}} +!! result +<p><span>Oh no</span> +</p> +!! end + +!! test +pre-save transform: comment containing gallery (bug 5024) +!! options +pst +!! input +<!-- <gallery>data</gallery> --> +!!result +<!-- <gallery>data</gallery> --> +!!end + +!! test +pre-save transform: comment containing extension +!! options +pst +!! input +<!-- <tag>data</tag> --> +!!result +<!-- <tag>data</tag> --> +!!end + +!! test +pre-save transform: comment containing nowiki +!! options +pst +!! input +<!-- <nowiki>data</nowiki> --> +!!result +<!-- <nowiki>data</nowiki> --> +!!end + +!! test +pre-save transform: comment containing math +!! options +pst +!! input +<!-- <math>data</math> --> +!!result +<!-- <math>data</math> --> +!!end + +!! test +pre-save transform: <noinclude> in subst (bug 3298) +!! options +pst +!! input +{{subst:Includes}} +!! result +Foobar +!! end + +!! test +pre-save transform: <onlyinclude> in subst (bug 3298) +!! options +pst +!! input +{{subst:Includes2}} +!! result +Foo +!! end + +!! article +Template:SubstTest +!!text +{{<includeonly>subst:</includeonly>Includes}} +!! endarticle + +!! article +Template:SafeSubstTest +!! text +{{<includeonly>safesubst:</includeonly>Includes}} +!! endarticle + +!! test +bug 22297: safesubst: works during PST +!! options +pst +!! input +{{subst:SafeSubstTest}}{{safesubst:SubstTest}} +!! result +FoobarFoobar +!! end + +!! test +bug 22297: safesubst: works during normal parse +!! input +{{SafeSubstTest}} +!! result +<p>Foobar +</p> +!! end + +!! test: +subst: does not work during normal parse +!! input +{{SubstTest}} +!! result +<p>{{subst:Includes}} +</p> +!! end + +!! test +pre-save transform: context links ("pipe trick") +!! options +pst +!! input +[[Article (context)|]] +[[Bar:Article|]] +[[:Bar:Article|]] +[[Bar:Article (context)|]] +[[:Bar:Article (context)|]] +[[|Article]] +[[|Article (context)]] +[[Bar:X (Y) Z|]] +[[:Bar:X (Y) Z|]] +!! result +[[Article (context)|Article]] +[[Bar:Article|Article]] +[[:Bar:Article|Article]] +[[Bar:Article (context)|Article]] +[[:Bar:Article (context)|Article]] +[[Article]] +[[Article (context)]] +[[Bar:X (Y) Z|X (Y) Z]] +[[:Bar:X (Y) Z|X (Y) Z]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with interwiki prefix +!! options +pst +!! input +[[interwiki:Article|]] +[[:interwiki:Article|]] +[[interwiki:Bar:Article|]] +[[:interwiki:Bar:Article|]] +!! result +[[interwiki:Article|Article]] +[[:interwiki:Article|Article]] +[[interwiki:Bar:Article|Bar:Article]] +[[:interwiki:Bar:Article|Bar:Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with parens in title +!! options +pst title=[[Somearticle (context)]] +!! input +[[|Article]] +!! result +[[Article (context)|Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with comma in title +!! options +pst title=[[Someplace, Somewhere]] +!! input +[[|Otherplace]] +[[Otherplace, Elsewhere|]] +[[Otherplace, Elsewhere, Anywhere|]] +!! result +[[Otherplace, Somewhere|Otherplace]] +[[Otherplace, Elsewhere|Otherplace]] +[[Otherplace, Elsewhere, Anywhere|Otherplace]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with parens and comma +!! options +pst title=[[Someplace (IGNORED), Somewhere]] +!! input +[[|Otherplace]] +[[Otherplace (place), Elsewhere|]] +!! result +[[Otherplace, Somewhere|Otherplace]] +[[Otherplace (place), Elsewhere|Otherplace]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with comma and parens +!! options +pst title=[[Who, me? (context)]] +!! input +[[|Yes, you.]] +[[Me, Myself, and I (1937 song)|]] +!! result +[[Yes, you. (context)|Yes, you.]] +[[Me, Myself, and I (1937 song)|Me, Myself, and I]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with namespace +!! options +pst title=[[Ns:Somearticle]] +!! input +[[|Article]] +!! result +[[Ns:Article|Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with namespace and parens +!! options +pst title=[[Ns:Somearticle (context)]] +!! input +[[|Article]] +!! result +[[Ns:Article (context)|Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with namespace and comma +!! options +pst title=[[Ns:Somearticle, Context, Whatever]] +!! input +[[|Article]] +!! result +[[Ns:Article, Context, Whatever|Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with namespace, comma and parens +!! options +pst title=[[Ns:Somearticle, Context (context)]] +!! input +[[|Article]] +!! result +[[Ns:Article (context)|Article]] +!! end + +!! test +pre-save transform: context links ("pipe trick") with namespace, parens and comma +!! options +pst title=[[Ns:Somearticle (IGNORED), Context]] +!! input +[[|Article]] +!! result +[[Ns:Article, Context|Article]] +!! end + + +### +### Message transform tests +### +!! test +message transform: magic variables +!! options +msg +!! input +{{SITENAME}} +!! result +MediaWiki +!! end + +!! test +message transform: should not transform wiki markup +!! options +msg +!! input +''test'' +!! result +''test'' +!! end + +!! test +message transform: <noinclude> in transcluded template (bug 4926) +!! options +msg +!! input +{{Includes}} +!! result +Foobar +!! end + +!! test +message transform: <onlyinclude> in transcluded template (bug 4926) +!! options +msg +!! input +{{Includes2}} +!! result +Foo +!! end + +!! test +{{#special:}} page name, known +!! options +msg +!! input +{{#special:Recentchanges}} +!! result +Special:RecentChanges +!! end + +!! test +{{#special:}} page name with subpage, known +!! options +msg +!! input +{{#special:Recentchanges/param}} +!! result +Special:RecentChanges/param +!! end + +!! test +{{#special:}} page name, unknown +!! options +msg +!! input +{{#special:foobarnonexistent}} +!! result +No such special page +!! end + +### +### Images +### +!! test +Simple image +!! input +[[Image:foobar.jpg]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Right-aligned image +!! input +[[Image:foobar.jpg|right]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Simple image (using File: namespace, now canonical) +!! input +[[File:foobar.jpg]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with caption +!! input +[[Image:foobar.jpg|right|Caption text]] +!! result +<div class="floatright"><a href="/wiki/File:Foobar.jpg" class="image" title="Caption text"><img alt="Caption text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!! end + +!! test +Image with link parameter, wiki target +!! input +[[Image:foobar.jpg|link=Target page]] +!! result +<p><a href="/wiki/Target_page" title="Target page"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with link parameter, URL target +!! input +[[Image:foobar.jpg|link=http://example.com/]] +!! result +<p><a href="http://example.com/"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with empty link parameter +!! input +[[Image:foobar.jpg|link=]] +!! result +<p><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /> +</p> +!! end + +!! test +Image with link parameter (wiki target) and unnamed parameter +!! input +[[Image:foobar.jpg|link=Target page|Title]] +!! result +<p><a href="/wiki/Target_page" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with link parameter (URL target) and unnamed parameter +!! input +[[Image:foobar.jpg|link=http://example.com/|Title]] +!! result +<p><a href="http://example.com/" title="Title"><img alt="Title" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Thumbnail image with link parameter +!! input +[[Image:foobar.jpg|thumb|link=http://example.com/|Title]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="http://example.com/"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Title</div></div></div> + +!! end + +!! test +Image with frame and link +!! input +[[Image:Foobar.jpg|frame|left|This is a test image [[Main Page]]]] +!! result +<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page">Main Page</a></div></div></div> + +!! end + +!! test +Image with frame and link and explicit alt +!! input +[[Image:Foobar.jpg|frame|left|This is a test image [[Main Page]]|alt=Altitude]] +!! result +<div class="thumb tleft"><div class="thumbinner" style="width:1943px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Altitude" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" class="thumbimage" /></a> <div class="thumbcaption">This is a test image <a href="/wiki/Main_Page">Main Page</a></div></div></div> + +!! end + +!! test +Image with wiki markup in implicit alt +!! input +[[Image:Foobar.jpg|testing '''bold''' in alt]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="testing bold in alt"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Image with wiki markup in explicit alt +!! input +[[Image:Foobar.jpg|alt=testing '''bold''' in alt]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="testing bold in alt" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Link to image page- image page normally doesn't exists, hence edit link +Add test with existing image page +#<p><a href="/wiki/File:Test" title="Image:Test">Image:test</a> +!! input +[[:Image:test]] +!! result +<p><a href="/index.php?title=File:Test&action=edit&redlink=1" class="new" title="File:Test (page does not exist)">Image:test</a> +</p> +!! end + +!! test +bug 18784 Link to non-existent image page with caption should use caption as link text +!! input +[[:Image:test|caption]] +!! result +<p><a href="/index.php?title=File:Test&action=edit&redlink=1" class="new" title="File:Test (page does not exist)">caption</a> +</p> +!! end + +!! test +Frameless image caption with a free URL +!! input +[[Image:foobar.jpg|http://example.com]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="http://example.com"><img alt="http://example.com" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Thumbnail image caption with a free URL +!! input +[[Image:foobar.jpg|thumb|http://example.com]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a></div></div></div> + +!! end + +!! test +Thumbnail image caption with a free URL and explicit alt +!! input +[[Image:foobar.jpg|thumb|http://example.com|alt=Alteration]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Alteration" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a></div></div></div> + +!! end + +!! test +BUG 1887: A ISBN with a thumbnail +!! input +[[Image:foobar.jpg|thumb|ISBN 1235467890]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><a href="/wiki/Special:BookSources/1235467890" class="internal mw-magiclink-isbn">ISBN 1235467890</a></div></div></div> + +!! end + +!! test +BUG 1887: A RFC with a thumbnail +!! input +[[Image:foobar.jpg|thumb|This is RFC 12354]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is <a href="http://tools.ietf.org/html/rfc12354" class="external mw-magiclink-rfc">RFC 12354</a></div></div></div> + +!! end + +!! test +BUG 1887: A mailto link with a thumbnail +!! input +[[Image:foobar.jpg|thumb|Please mailto:nobody@example.com]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>Please <a href="mailto:nobody@example.com" class="external free" rel="nofollow">mailto:nobody@example.com</a></div></div></div> + +!! end + +!! test +BUG 1887: A <math> with a thumbnail- we don't render math in the parsertests by default, +so math is not stripped and turns up as escaped <math> tags. +!! input +[[Image:foobar.jpg|thumb|<math>2+2</math>]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><math>2+2</math></div></div></div> + +!! end + +!! test +BUG 1887, part 2: A <math> with a thumbnail- math enabled +!! options +math +!! input +[[Image:foobar.jpg|thumb|<math>2+2</math>]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div><span class="texhtml">2 + 2</span></div></div></div> + +!! end + +# Pending resolution to bug 368 +!! test +BUG 648: Frameless image caption with a link +!! input +[[Image:foobar.jpg|text with a [[link]] in it]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +BUG 648: Frameless image caption with a link (suffix) +!! input +[[Image:foobar.jpg|text with a [[link]]foo in it]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a linkfoo in it"><img alt="text with a linkfoo in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +BUG 648: Frameless image caption with an interwiki link +!! input +[[Image:foobar.jpg|text with a [[MeatBall:Link]] in it]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a MeatBall:Link in it"><img alt="text with a MeatBall:Link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +BUG 648: Frameless image caption with a piped interwiki link +!! input +[[Image:foobar.jpg|text with a [[MeatBall:Link|link]] in it]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="text with a link in it"><img alt="text with a link in it" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Escape HTML special chars in image alt text +!! input +[[Image:foobar.jpg|& < > "]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="& < > ""><img alt="& < > "" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +BUG 499: Alt text should have Ӓ, not &1234; +!! input +[[Image:foobar.jpg|♀]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="♀"><img alt="♀" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!! end + +!! test +Broken image caption with link +!! input +[[Image:Foobar.jpg|thumb|This is a broken caption. But [[Main Page|this]] is just an ordinary link. +!! result +<p>[[Image:Foobar.jpg|thumb|This is a broken caption. But <a href="/wiki/Main_Page" title="Main Page">this</a> is just an ordinary link. +</p> +!! end + +!! test +Image caption containing another image +!! input +[[Image:Foobar.jpg|thumb|This is a caption with another [[Image:icon.png|image]] inside it!]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This is a caption with another <a href="/index.php?title=Special:Upload&wpDestFile=Icon.png" class="new" title="File:Icon.png">image</a> inside it!</div></div></div> + +!! end + +!! test +Image caption containing a newline +!! input +[[Image:Foobar.jpg|This +*is some text]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image" title="This *is some text"><img alt="This *is some text" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!!end + + +!! test +Bug 3090: External links other than http: in image captions +!! input +[[Image:Foobar.jpg|thumb|200px|This caption has [irc://example.net irc] and [https://example.com Secure] ext links in it.]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:202px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="200" height="23" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>This caption has <a href="irc://example.net" class="external text" rel="nofollow">irc</a> and <a href="https://example.com" class="external text" rel="nofollow">Secure</a> ext links in it.</div></div></div> + +!! end + +!! article +File:Barfoo.jpg +!! text +#REDIRECT [[File:Barfoo.jpg]] +!! endarticle + +!! test +Redirected image +!! input +[[Image:Barfoo.jpg]] +!! result +<p><a href="/wiki/File:Barfoo.jpg">File:Barfoo.jpg</a> +</p> +!! end + +!! test +Missing image with uploads disabled +!! options +wgEnableUploads=0 +!! input +[[Image:Foobaz.jpg]] +!! result +<p><a href="/wiki/File:Foobaz.jpg">File:Foobaz.jpg</a> +</p> +!! end + + +### +### Subpages +### +!! article +Subpage test/subpage +!! text +foo +!! endarticle + +!! test +Subpage link +!! options +subpage title=[[Subpage test]] +!! input +[[/subpage]] +!! result +<p><a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">/subpage</a> +</p> +!! end + +!! test +Subpage noslash link +!! options +subpage title=[[Subpage test]] +!!input +[[/subpage/]] +!! result +<p><a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">subpage</a> +</p> +!! end + +!! test +Disabled subpages +!! input +[[/subpage]] +!! result +<p><a href="/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> +</p> +!! end + +!! test +BUG 561: {{/Subpage}} +!! options +subpage title=[[Page]] +!! input +{{/Subpage}} +!! result +<p><a href="/index.php?title=Page/Subpage&action=edit&redlink=1" class="new" title="Page/Subpage (page does not exist)">Page/Subpage</a> +</p> +!! end + +### +### Categories +### +!! article +Category:MediaWiki User's Guide +!! text +blah +!! endarticle + +!! test +Link to category +!! input +[[:Category:MediaWiki User's Guide]] +!! result +<p><a href="/wiki/Category:MediaWiki_User%27s_Guide">Category:MediaWiki User's Guide</a> +</p> +!! end + +!! test +Simple category +!! options +cat +!! input +[[Category:MediaWiki User's Guide]] +!! result +<a href="/wiki/Category:MediaWiki_User%27s_Guide" title="Category:MediaWiki User's Guide">MediaWiki User's Guide</a> +!! end + +!! test +PAGESINCATEGORY invalid title fatal (r33546 fix) +!! input +{{PAGESINCATEGORY:<bogus>}} +!! result +<p>0 +</p> +!! end + +### +### Inter-language links +### +!! test +Inter-language links +!! options +ill +!! input +[[es:Alimento]] +[[fr:Nourriture]] +[[zh:食品]] +!! result +es:Alimento fr:Nourriture zh:食品 +!! end + +### +### Sections +### +!! test +Basic section headings +!! input +== Headline 1 == +Some text + +==Headline 2== +More +===Smaller headline=== +Blah blah +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2> +<p>Some text +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2">Headline 2</span></h2> +<p>More +</p> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Smaller headline">edit</a>]</span> <span class="mw-headline" id="Smaller_headline">Smaller headline</span></h3> +<p>Blah blah +</p> +!! end + +!! test +Section headings with TOC +!! input +== Headline 1 == +=== Subheadline 1 === +===== Skipping a level ===== +====== Skipping a level ====== + +== Headline 2 == +Some text +===Another headline=== +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Headline_1"><span class="tocnumber">1</span> <span class="toctext">Headline 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#Subheadline_1"><span class="tocnumber">1.1</span> <span class="toctext">Subheadline 1</span></a> +<ul> +<li class="toclevel-3 tocsection-3"><a href="#Skipping_a_level"><span class="tocnumber">1.1.1</span> <span class="toctext">Skipping a level</span></a> +<ul> +<li class="toclevel-4 tocsection-4"><a href="#Skipping_a_level_2"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Skipping a level</span></a></li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +<li class="toclevel-1 tocsection-5"><a href="#Headline_2"><span class="tocnumber">2</span> <span class="toctext">Headline 2</span></a> +<ul> +<li class="toclevel-2 tocsection-6"><a href="#Another_headline"><span class="tocnumber">2.1</span> <span class="toctext">Another headline</span></a></li> +</ul> +</li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Headline 1">edit</a>]</span> <span class="mw-headline" id="Headline_1"> Headline 1 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Subheadline 1">edit</a>]</span> <span class="mw-headline" id="Subheadline_1"> Subheadline 1 </span></h3> +<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level"> Skipping a level </span></h5> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Skipping a level">edit</a>]</span> <span class="mw-headline" id="Skipping_a_level_2"> Skipping a level </span></h6> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Headline 2">edit</a>]</span> <span class="mw-headline" id="Headline_2"> Headline 2 </span></h2> +<p>Some text +</p> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Another headline">edit</a>]</span> <span class="mw-headline" id="Another_headline">Another headline</span></h3> + +!! end + +# perl -e 'print "="x$_," Level $_ heading","="x$_,"\n" for 1..10' +!! test +Handling of sections up to level 6 and beyond +!! input += Level 1 Heading= +== Level 2 Heading== +=== Level 3 Heading=== +==== Level 4 Heading==== +===== Level 5 Heading===== +====== Level 6 Heading====== +======= Level 7 Heading======= +======== Level 8 Heading======== +========= Level 9 Heading========= +========== Level 10 Heading========== +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Level_1_Heading"><span class="tocnumber">1</span> <span class="toctext">Level 1 Heading</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#Level_2_Heading"><span class="tocnumber">1.1</span> <span class="toctext">Level 2 Heading</span></a> +<ul> +<li class="toclevel-3 tocsection-3"><a href="#Level_3_Heading"><span class="tocnumber">1.1.1</span> <span class="toctext">Level 3 Heading</span></a> +<ul> +<li class="toclevel-4 tocsection-4"><a href="#Level_4_Heading"><span class="tocnumber">1.1.1.1</span> <span class="toctext">Level 4 Heading</span></a> +<ul> +<li class="toclevel-5 tocsection-5"><a href="#Level_5_Heading"><span class="tocnumber">1.1.1.1.1</span> <span class="toctext">Level 5 Heading</span></a> +<ul> +<li class="toclevel-6 tocsection-6"><a href="#Level_6_Heading"><span class="tocnumber">1.1.1.1.1.1</span> <span class="toctext">Level 6 Heading</span></a></li> +<li class="toclevel-6 tocsection-7"><a href="#.3D_Level_7_Heading.3D"><span class="tocnumber">1.1.1.1.1.2</span> <span class="toctext">= Level 7 Heading=</span></a></li> +<li class="toclevel-6 tocsection-8"><a href="#.3D.3D_Level_8_Heading.3D.3D"><span class="tocnumber">1.1.1.1.1.3</span> <span class="toctext">== Level 8 Heading==</span></a></li> +<li class="toclevel-6 tocsection-9"><a href="#.3D.3D.3D_Level_9_Heading.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.4</span> <span class="toctext">=== Level 9 Heading===</span></a></li> +<li class="toclevel-6 tocsection-10"><a href="#.3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D"><span class="tocnumber">1.1.1.1.1.5</span> <span class="toctext">==== Level 10 Heading====</span></a></li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</li> +</ul> +</td></tr></table> +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Level 1 Heading">edit</a>]</span> <span class="mw-headline" id="Level_1_Heading"> Level 1 Heading</span></h1> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Level 2 Heading">edit</a>]</span> <span class="mw-headline" id="Level_2_Heading"> Level 2 Heading</span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Level 3 Heading">edit</a>]</span> <span class="mw-headline" id="Level_3_Heading"> Level 3 Heading</span></h3> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Level 4 Heading">edit</a>]</span> <span class="mw-headline" id="Level_4_Heading"> Level 4 Heading</span></h4> +<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Level 5 Heading">edit</a>]</span> <span class="mw-headline" id="Level_5_Heading"> Level 5 Heading</span></h5> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: Level 6 Heading">edit</a>]</span> <span class="mw-headline" id="Level_6_Heading"> Level 6 Heading</span></h6> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=7" title="Edit section: = Level 7 Heading=">edit</a>]</span> <span class="mw-headline" id=".3D_Level_7_Heading.3D">= Level 7 Heading=</span></h6> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=8" title="Edit section: == Level 8 Heading==">edit</a>]</span> <span class="mw-headline" id=".3D.3D_Level_8_Heading.3D.3D">== Level 8 Heading==</span></h6> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=9" title="Edit section: === Level 9 Heading===">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D_Level_9_Heading.3D.3D.3D">=== Level 9 Heading===</span></h6> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=10" title="Edit section: ==== Level 10 Heading====">edit</a>]</span> <span class="mw-headline" id=".3D.3D.3D.3D_Level_10_Heading.3D.3D.3D.3D">==== Level 10 Heading====</span></h6> + +!! end + +!! test +TOC regression (bug 9764) +!! input +== title 1 == +=== title 1.1 === +==== title 1.1.1 ==== +=== title 1.2 === +== title 2 == +=== title 2.1 === +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a> +<ul> +<li class="toclevel-3 tocsection-3"><a href="#title_1.1.1"><span class="tocnumber">1.1.1</span> <span class="toctext">title 1.1.1</span></a></li> +</ul> +</li> +<li class="toclevel-2 tocsection-4"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-5"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a> +<ul> +<li class="toclevel-2 tocsection-6"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li> +</ul> +</li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3> + +!! end + +!! test +TOC with wgMaxTocLevel=3 (bug 6204) +!! options +wgMaxTocLevel=3 +!! input +== title 1 == +=== title 1.1 === +==== title 1.1.1 ==== +=== title 1.2 === +== title 2 == +=== title 2.1 === +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li> +<li class="toclevel-2 tocsection-4"><a href="#title_1.2"><span class="tocnumber">1.2</span> <span class="toctext">title 1.2</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-5"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a> +<ul> +<li class="toclevel-2 tocsection-6"><a href="#title_2.1"><span class="tocnumber">2.1</span> <span class="toctext">title 2.1</span></a></li> +</ul> +</li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 1.1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1.1"> title 1.1.1 </span></h4> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: title 1.2">edit</a>]</span> <span class="mw-headline" id="title_1.2"> title 1.2 </span></h3> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: title 2.1">edit</a>]</span> <span class="mw-headline" id="title_2.1"> title 2.1 </span></h3> + +!! end + +!! test +TOC with wgMaxTocLevel=3 and two level four headings (bug 6204) +!! options +wgMaxTocLevel=3 +!! input +==Section 1== +===Section 1.1=== +====Section 1.1.1==== +====Section 1.1.1.1==== +==Section 2== +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#Section_1"><span class="tocnumber">1</span> <span class="toctext">Section 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#Section_1.1"><span class="tocnumber">1.1</span> <span class="toctext">Section 1.1</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-5"><a href="#Section_2"><span class="tocnumber">2</span> <span class="toctext">Section 2</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 1">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1">Section 1.1</span></h3> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: Section 1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1">Section 1.1.1</span></h4> +<h4><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: Section 1.1.1.1">edit</a>]</span> <span class="mw-headline" id="Section_1.1.1.1">Section 1.1.1.1</span></h4> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: Section 2">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2> + +!! end + + +!! test +Resolving duplicate section names +!! input +== Foo bar == +== Foo bar == +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar_2"> Foo bar </span></h2> + +!! end + +!! test +Resolving duplicate section names with differing case (bug 10721) +!! input +== Foo bar == +== Foo Bar == +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Foo bar">edit</a>]</span> <span class="mw-headline" id="Foo_bar"> Foo bar </span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Foo Bar">edit</a>]</span> <span class="mw-headline" id="Foo_Bar_2"> Foo Bar </span></h2> + +!! end + +!! article +Template:sections +!! text +===Section 1=== +==Section 2== +!! endarticle + +!! test +Template with sections, __NOTOC__ +!! input +__NOTOC__ +==Section 0== +{{sections}} +==Section 4== +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section 0">edit</a>]</span> <span class="mw-headline" id="Section_0">Section 0</span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Template:Sections&action=edit&section=T-1" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_1">Section 1</span></h3> +<h2><span class="editsection">[<a href="/index.php?title=Template:Sections&action=edit&section=T-2" title="Template:Sections">edit</a>]</span> <span class="mw-headline" id="Section_2">Section 2</span></h2> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: Section 4">edit</a>]</span> <span class="mw-headline" id="Section_4">Section 4</span></h2> + +!! end + +!! test +__NOEDITSECTION__ keyword +!! input +__NOEDITSECTION__ +==Section 1== +==Section 2== +!! result +<h2> <span class="mw-headline" id="Section_1">Section 1</span></h2> +<h2> <span class="mw-headline" id="Section_2">Section 2</span></h2> + +!! end + +!! test +Link inside a section heading +!! input +==Section with a [[Main Page|link]] in it== +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: Section with a link in it">edit</a>]</span> <span class="mw-headline" id="Section_with_a_link_in_it">Section with a <a href="/wiki/Main_Page" title="Main Page">link</a> in it</span></h2> + +!! end + +!! test +TOC regression (bug 12077) +!! input +__TOC__ +== title 1 == +=== title 1.1 === +== title 2 == +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#title_1"><span class="tocnumber">1</span> <span class="toctext">title 1</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#title_1.1"><span class="tocnumber">1.1</span> <span class="toctext">title 1.1</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-3"><a href="#title_2"><span class="tocnumber">2</span> <span class="toctext">title 2</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: title 1">edit</a>]</span> <span class="mw-headline" id="title_1"> title 1 </span></h2> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: title 1.1">edit</a>]</span> <span class="mw-headline" id="title_1.1"> title 1.1 </span></h3> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: title 2">edit</a>]</span> <span class="mw-headline" id="title_2"> title 2 </span></h2> + +!! end + +!! test +BUG 1219 URL next to image (good) +!! input +http://example.com [[Image:foobar.jpg]] +!! result +<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> <a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!!end + +!! test +Short headings with trailing space should match behaviour of Parser::doHeadings (bug 19910) +!! input +=== +The line above must have a trailing space! +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! result +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D">=</span></h1> +<p>The line above must have a trailing space! +</p> +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: =">edit</a>]</span> <span class="mw-headline" id=".3D_2">=</span></h1> +<p>But just in case it doesn't... +</p> +!! end + +!! test +Header with special characters (bug 25462) +!! input +The tooltips shall not show entities to the user (ie. be double escaped) + +== text > text == +section 1 + +== text < text == +section 2 + +== text & text == +section 3 + +== text ' text == +section 4 + +== text " text == +section 5 +!! result +<p>The tooltips shall not show entities to the user (ie. be double escaped) +</p> +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#text_.3E_text"><span class="tocnumber">1</span> <span class="toctext">text > text</span></a></li> +<li class="toclevel-1 tocsection-2"><a href="#text_.3C_text"><span class="tocnumber">2</span> <span class="toctext">text < text</span></a></li> +<li class="toclevel-1 tocsection-3"><a href="#text_.26_text"><span class="tocnumber">3</span> <span class="toctext">text & text</span></a></li> +<li class="toclevel-1 tocsection-4"><a href="#text_.27_text"><span class="tocnumber">4</span> <span class="toctext">text ' text</span></a></li> +<li class="toclevel-1 tocsection-5"><a href="#text_.22_text"><span class="tocnumber">5</span> <span class="toctext">text " text</span></a></li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: text > text">edit</a>]</span> <span class="mw-headline" id="text_.3E_text"> text > text </span></h2> +<p>section 1 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: text < text">edit</a>]</span> <span class="mw-headline" id="text_.3C_text"> text < text </span></h2> +<p>section 2 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: text & text">edit</a>]</span> <span class="mw-headline" id="text_.26_text"> text & text </span></h2> +<p>section 3 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: text ' text">edit</a>]</span> <span class="mw-headline" id="text_.27_text"> text ' text </span></h2> +<p>section 4 +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: text " text">edit</a>]</span> <span class="mw-headline" id="text_.22_text"> text " text </span></h2> +<p>section 5 +</p> +!! end + +!! test +BUG 1219 URL next to image (broken) +!! input +http://example.com[[Image:foobar.jpg]] +!! result +<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a> +</p> +!!end + +!! test +Bug 1186 news: in the middle of text +!! input +http://en.wikinews.org/wiki/Wikinews:Workplace +!! result +<p><a href="http://en.wikinews.org/wiki/Wikinews:Workplace" class="external free" rel="nofollow">http://en.wikinews.org/wiki/Wikinews:Workplace</a> +</p> +!!end + + +!! test +Namespaced link must have a title +!! input +[[Project:]] +!! result +<p>[[Project:]] +</p> +!!end + +!! test +Namespaced link must have a title (bad fragment version) +!! input +[[Project:#fragment]] +!! result +<p>[[Project:#fragment]] +</p> +!!end + + +!! test +div with no attributes +!! input +<div>HTML rocks</div> +!! result +<div>HTML rocks</div> + +!! end + +!! test +div with double-quoted attribute +!! input +<div id="rock">HTML rocks</div> +!! result +<div id="rock">HTML rocks</div> + +!! end + +!! test +div with single-quoted attribute +!! input +<div id='rock'>HTML rocks</div> +!! result +<div id="rock">HTML rocks</div> + +!! end + +!! test +div with unquoted attribute +!! input +<div id=rock>HTML rocks</div> +!! result +<div id="rock">HTML rocks</div> + +!! end + +!! test +div with illegal double attributes +!! input +<div align="center" align="right">HTML rocks</div> +!! result +<div align="right">HTML rocks</div> + +!!end + +!! test +HTML multiple attributes correction +!! input +<p class="error" class="awesome">Awesome!</p> +!! result +<p class="awesome">Awesome!</p> + +!!end + +!! test +Table multiple attributes correction +!! input +{| +!+ class="error" class="awesome"| status +|} +!! result +<table> +<tr> +<th class="awesome"> status +</th></tr></table> + +!!end + +!! test +DIV IN UPPERCASE +!! input +<DIV ALIGN="center">HTML ROCKS</DIV> +!! result +<div align="center">HTML ROCKS</div> + +!!end + + +!! test +text with amp in the middle of nowhere +!! input +Remember AT&T? +!!result +<p>Remember AT&T? +</p> +!! end + +!! test +text with character entity: eacute +!! input +I always thought é was a cute letter. +!! result +<p>I always thought é was a cute letter. +</p> +!! end + +!! test +text with undefined character entity: xacute +!! input +I always thought &xacute; was a cute letter. +!! result +<p>I always thought &xacute; was a cute letter. +</p> +!! end + + +### +### Media links +### + +!! test +Media link +!! input +[[Media:Foobar.jpg]] +!! result +<p><a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">Media:Foobar.jpg</a> +</p> +!! end + +!! test +Media link with text +!! input +[[Media:Foobar.jpg|A neat file to look at]] +!! result +<p><a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">A neat file to look at</a> +</p> +!! end + +# FIXME: this is still bad HTML tag nesting +!! test +Media link with nasty text +fixme: doBlockLevels won't wrap this in a paragraph because it contains a div +!! input +[[Media:Foobar.jpg|Safe Link<div style=display:none>" onmouseover="alert(document.cookie)" onfoo="</div>]] +!! result +<a href="http://example.com/images/3/3a/Foobar.jpg" class="internal" title="Foobar.jpg">Safe Link<div style="display:none">" onmouseover="alert(document.cookie)" onfoo="</div></a> + +!! end + +!! test +Media link to nonexistent file (bug 1702) +!! input +[[Media:No such.jpg]] +!! result +<p><a href="/index.php?title=Special:Upload&wpDestFile=No_such.jpg" class="new" title="No such.jpg">Media:No such.jpg</a> +</p> +!! end + +!! test +Image link to nonexistent file (bug 1850 - good) +!! input +[[Image:No such.jpg]] +!! result +<p><a href="/index.php?title=Special:Upload&wpDestFile=No_such.jpg" class="new" title="File:No such.jpg">File:No such.jpg</a> +</p> +!! end + +!! test +:Image link to nonexistent file (bug 1850 - bad) +!! input +[[:Image:No such.jpg]] +!! result +<p><a href="/index.php?title=File:No_such.jpg&action=edit&redlink=1" class="new" title="File:No such.jpg (page does not exist)">Image:No such.jpg</a> +</p> +!! end + + + +!! test +Character reference normalization in link text (bug 1938) +!! input +[[Main Page|this&that]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">this&that</a> +</p> +!!end + +!! article +אַ +!! text +Test for unicode normalization + +The page's name is U+05d0 U+05b7, with non-canonical form U+FB2E +!! endarticle + +!! test +(bug 19451) Links should refer to the normalized form. +!! input +[[אַ]] +[[אַ]] +[[אַ]] +[[אַ]] +[[אַ]] +!! result +<p><a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7" title="אַ">אַ</a> +<a href="/wiki/%D7%90%D6%B7">אַ</a> +</p> +!! end + +!! test +Empty attribute crash test (bug 2067) +!! input +<font color="">foo</font> +!! result +<p><font color="">foo</font> +</p> +!! end + +!! test +Empty attribute crash test single-quotes (bug 2067) +!! input +<font color=''>foo</font> +!! result +<p><font color="">foo</font> +</p> +!! end + +!! test +Attribute test: equals, then nothing +!! input +<font color=>foo</font> +!! result +<p><font>foo</font> +</p> +!! end + +!! test +Attribute test: unquoted value +!! input +<font color=x>foo</font> +!! result +<p><font color="x">foo</font> +</p> +!! end + +!! test +Attribute test: unquoted but illegal value (hash) +!! input +<font color=#x>foo</font> +!! result +<p><font color="#x">foo</font> +</p> +!! end + +!! test +Attribute test: no value +!! input +<font color>foo</font> +!! result +<p><font color="color">foo</font> +</p> +!! end + +!! test +Bug 2095: link with three closing brackets +!! input +[[Main Page]]] +!! result +<p><a href="/wiki/Main_Page">Main Page</a>] +</p> +!! end + +!! test +Bug 2095: link with pipe and three closing brackets +!! input +[[Main Page|link]]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">link</a>] +</p> +!! end + +!! test +Bug 2095: link with pipe and three closing brackets, version 2 +!! input +[[Main Page|[http://example.com/]]] +!! result +<p><a href="/wiki/Main_Page" title="Main Page">[http://example.com/]</a> +</p> +!! end + + +### +### Safety +### + +!! article +Template:Dangerous attribute +!! text +" onmouseover="alert(document.cookie) +!! endarticle + +!! article +Template:Dangerous style attribute +!! text +border-size: expression(alert(document.cookie)) +!! endarticle + +!! article +Template:Div style +!! text +<div style="float: right; {{{1}}}">Magic div</div> +!! endarticle + +!! test +Bug 2304: HTML attribute safety (safe template; regression bug 2309) +!! input +<div title="{{test}}"></div> +!! result +<div title="This is a test template"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (dangerous template; 2309) +!! input +<div title="{{dangerous attribute}}"></div> +!! result +<div title=""></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (dangerous style template; 2309) +!! input +<div style="{{dangerous style attribute}}"></div> +!! result +<div style="/* insecure input */"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (safe parameter; 2309) +!! input +{{div style|width: 200px}} +!! result +<div style="float: right; width: 200px">Magic div</div> + +!! end + +!! test +Bug 2304: HTML attribute safety (unsafe parameter; 2309) +!! input +{{div style|width: expression(alert(document.cookie))}} +!! result +<div style="/* insecure input */">Magic div</div> + +!! end + +!! test +Bug 2304: HTML attribute safety (unsafe breakout parameter; 2309) +!! input +{{div style|"><script>alert(document.cookie)</script>}} +!! result +<div style="float: right;"><script>alert(document.cookie)</script>">Magic div</div> + +!! end + +!! test +Bug 2304: HTML attribute safety (unsafe breakout parameter 2; 2309) +!! input +{{div style|" ><script>alert(document.cookie)</script>}} +!! result +<div style="float: right;"><script>alert(document.cookie)</script>">Magic div</div> + +!! end + +!! test +Bug 2304: HTML attribute safety (link) +!! input +<div title="[[Main Page]]"></div> +!! result +<div title="[[Main Page]]"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (italics) +!! input +<div title="''foobar''"></div> +!! result +<div title="''foobar''"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (bold) +!! input +<div title="'''foobar'''"></div> +!! result +<div title="'''foobar'''"></div> + +!! end + + +!! test +Bug 2304: HTML attribute safety (ISBN) +!! input +<div title="ISBN 1234567890"></div> +!! result +<div title="ISBN 1234567890"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (RFC) +!! input +<div title="RFC 1234"></div> +!! result +<div title="RFC 1234"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (PMID) +!! input +<div title="PMID 1234567890"></div> +!! result +<div title="PMID 1234567890"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (web link) +!! input +<div title="http://example.com/"></div> +!! result +<div title="http://example.com/"></div> + +!! end + +!! test +Bug 2304: HTML attribute safety (named web link) +!! input +<div title="[http://example.com/ link]"></div> +!! result +<div title="[http://example.com/ link]"></div> + +!! end + +!! test +Bug 3244: HTML attribute safety (extension; safe) +!! input +<div style="<nowiki>background:blue</nowiki>"></div> +!! result +<div style="background:blue"></div> + +!! end + +!! test +Bug 3244: HTML attribute safety (extension; unsafe) +!! input +<div style="<nowiki>border-left:expression(alert(document.cookie))</nowiki>"></div> +!! result +<div style="/* insecure input */"></div> + +!! end + +!! test +Math section safety when disabled +!! input +<math><script>alert(document.cookies);</script></math> +!! result +<p><math><script>alert(document.cookies);</script></math> +</p> +!! end + +# More MSIE fun discovered by Tom Gilder + +!! test +MSIE CSS safety test: spurious slash +!! input +<div style="background-image:u\rl(javascript:alert('boo'))">evil</div> +!! result +<div style="/* insecure input */">evil</div> + +!! end + +!! test +MSIE CSS safety test: hex code +!! input +<div style="background-image:u\72l(javascript:alert('boo'))">evil</div> +!! result +<div style="/* insecure input */">evil</div> + +!! end + +!! test +MSIE CSS safety test: comment in url +!! input +<div style="background-image:u/**/rl(javascript:alert('boo'))">evil</div> +!! result +<div style="background-image:u rl(javascript:alert('boo'))">evil</div> + +!! end + +!! test +MSIE CSS safety test: comment in expression +!! input +<div style="background-image:expres/**/sion(alert('boo4'))">evil4</div> +!! result +<div style="background-image:expres sion(alert('boo4'))">evil4</div> + +!! end + + +!! test +Table attribute legitimate extension +!! input +{| +!+ style="<nowiki>color:blue</nowiki>"| status +|} +!! result +<table> +<tr> +<th style="color:blue"> status +</th></tr></table> + +!!end + +!! test +Table attribute safety +!! input +{| +!+ style="<nowiki>border-width:expression(0+alert(document.cookie))</nowiki>"| status +|} +!! result +<table> +<tr> +<th style="/* insecure input */"> status +</th></tr></table> + +!! end + +!! test +CSS line continuation 1 +!! input +<div style="background-image: u\ rl(test.jpg);"></div> +!! result +<div style="/* insecure input */"></div> + +!! end + +!! test +CSS line continuation 2 +!! input +<div style="background-image: u\ rl(test.jpg); "></div> +!! result +<div style="/* insecure input */"></div> + +!! end + +!! article +Template:Identity +!! text +{{{1}}} +!! endarticle + +!! test +Expansion of multi-line templates in attribute values (bug 6255) +!! input +<div style="background: {{identity|#00FF00}}">-</div> +!! result +<div style="background: #00FF00">-</div> + +!! end + + +!! test +Expansion of multi-line templates in attribute values (bug 6255 sanity check) +!! input +<div style="background: +#00FF00">-</div> +!! result +<div style="background: #00FF00">-</div> + +!! end + +!! test +Expansion of multi-line templates in attribute values (bug 6255 sanity check 2) +!! input +<div style="background: #00FF00">-</div> +!! result +<div style="background: #00FF00">-</div> + +!! end + +### +### Parser hooks (see maintenance/parserTestsParserHook.php for the <tag> extension) +### +!! test +Parser hook: empty input +!! input +<tag></tag> +!! result +<pre> +string(0) "" +array(0) { +} +</pre> + +!! end + +!! test +Parser hook: empty input using terminated empty elements +!! input +<tag/> +!! result +<pre> +NULL +array(0) { +} +</pre> + +!! end + +!! test +Parser hook: empty input using terminated empty elements (space before) +!! input +<tag /> +!! result +<pre> +NULL +array(0) { +} +</pre> + +!! end + +!! test +Parser hook: basic input +!! input +<tag>input</tag> +!! result +<pre> +string(5) "input" +array(0) { +} +</pre> + +!! end + + +!! test +Parser hook: case insensitive +!! input +<TAG>input</TAG> +!! result +<pre> +string(5) "input" +array(0) { +} +</pre> + +!! end + + +!! test +Parser hook: case insensitive, redux +!! input +<TaG>input</TAg> +!! result +<pre> +string(5) "input" +array(0) { +} +</pre> + +!! end + +!! test +Parser hook: nested tags +!! options +noxml +!! input +<tag><tag></tag></tag> +!! result +<pre> +string(5) "<tag>" +array(0) { +} +</pre></tag> + +!! end + +!! test +Parser hook: basic arguments +!! input +<tag width=200 height = "100" depth = '50' square></tag> +!! result +<pre> +string(0) "" +array(4) { + ["width"]=> + string(3) "200" + ["height"]=> + string(3) "100" + ["depth"]=> + string(2) "50" + ["square"]=> + string(6) "square" +} +</pre> + +!! end + +!! test +Parser hook: argument containing a forward slash (bug 5344) +!! input +<tag filename='/tmp/bla'></tag> +!! result +<pre> +string(0) "" +array(1) { + ["filename"]=> + string(8) "/tmp/bla" +} +</pre> + +!! end + +!! test +Parser hook: empty input using terminated empty elements (bug 2374) +!! input +<tag foo=bar/>text +!! result +<pre> +NULL +array(1) { + ["foo"]=> + string(3) "bar" +} +</pre>text + +!! end + +# </tag> should be output literally since there is no matching tag that begins it +!! test +Parser hook: basic arguments using terminated empty elements (bug 2374) +!! input +<tag width=200 height = "100" depth = '50' square/> +other stuff +</tag> +!! result +<pre> +NULL +array(4) { + ["width"]=> + string(3) "200" + ["height"]=> + string(3) "100" + ["depth"]=> + string(2) "50" + ["square"]=> + string(6) "square" +} +</pre> +<p>other stuff +</tag> +</p> +!! end + +### +### (see maintenance/parserTestsStaticParserHook.php for the <statictag> extension) +### + +!! test +Parser hook: static parser hook not inside a comment +!! input +<statictag>hello, world</statictag> +<statictag action=flush/> +!! result +<p>hello, world +</p> +!! end + + +!! test +Parser hook: static parser hook inside a comment +!! input +<!-- <statictag>hello, world</statictag> --> +<statictag action=flush/> +!! result +<p><br /> +</p> +!! end + +# Nested template calls; this case was broken by Parser.php rev 1.506, +# since reverted. + +!! article +Template:One-parameter +!! text +(My parameter is: {{{1}}}) +!! endarticle + +!! article +Template:Map-one-parameter +!! text +{{{{{1}}}|{{{2}}}}} +!! endarticle + +!! test +Nested template calls +!! input +{{Map-one-parameter|One-parameter|param}} +!! result +<p>(My parameter is: param) +</p> +!! end + + +### +### Sanitizer +### +!! test +Sanitizer: Closing of open tags +!! input +<s></s><table></table> +!! result +<s></s><table></table> + +!! end + +!! test +Sanitizer: Closing of open but not closed tags +!! input +<s>foo +!! result +<p><s>foo</s> +</p> +!! end + +!! test +Sanitizer: Closing of closed but not open tags +!! input +</s> +!! result +<p></s> +</p> +!! end + +!! test +Sanitizer: Closing of closed but not open table tags +!! input +Table not started</td></tr></table> +!! result +<p>Table not started</td></tr></table> +</p> +!! end + +!! test +Sanitizer: Escaping of spaces, multibyte characters, colons & other stuff in id="" +!! input +<span id="æ: v">byte</span>[[#æ: v|backlink]] +!! result +<p><span id=".C3.A6:_v">byte</span><a href="#.C3.A6:_v">backlink</a> +</p> +!! end + +!! test +Sanitizer: Validating the contents of the id attribute (bug 4515) +!! options +disabled +!! input +<br id=9 /> +!! result +Something, but definitely not <br id="9" />... +!! end + +!! test +Sanitizer: Validating id attribute uniqueness (bug 4515, bug 6301) +!! options +disabled +!! input +<br id="foo" /><br id="foo" /> +!! result +Something need to be done. foo-2 ? +!! end + +!! test +Language converter: output gets cut off unexpectedly (bug 5757) +!! options +language=zh +!! input +this bit is safe: }- + +but if we add a conversion instance: -{zh-cn:xxx;zh-tw:yyy}- + +then we get cut off here: }- + +all additional text is vanished +!! result +<p>this bit is safe: }- +</p><p>but if we add a conversion instance: xxx +</p><p>then we get cut off here: }- +</p><p>all additional text is vanished +</p> +!! end + +!! test +Self closed html pairs (bug 5487) +!! options +!! input +<center><font id="bug" />Centered text</center> +<div><font id="bug2" />In div text</div> +!! result +<center><font id="bug" />Centered text</center> +<div><font id="bug2" />In div text</div> + +!! end + +# +# +# + +!! test +Punctuation: nbsp before exclamation +!! input +C'est grave ! +!! result +<p>C'est grave ! +</p> +!! end + +!! test +Punctuation: CSS !important (bug 11874) +!! input +<div style="width:50% !important">important</div> +!! result +<div style="width:50% !important">important</div> + +!!end + +!! test +Punctuation: CSS ! important (bug 11874; with space after) +!! input +<div style="width:50% ! important">important</div> +!! result +<div style="width:50% ! important">important</div> + +!!end + + +!! test +HTML bullet list, closed tags (bug 5497) +!! input +<ul> +<li>One</li> +<li>Two</li> +</ul> +!! result +<ul> +<li>One</li> +<li>Two</li> +</ul> + +!! end + +!! test +HTML bullet list, unclosed tags (bug 5497) +!! options +disabled +!! input +<ul> +<li>One +<li>Two +</ul> +!! result +<ul> +<li>One +</li><li>Two +</li></ul> + +!! end + +!! test +HTML ordered list, closed tags (bug 5497) +!! input +<ol> +<li>One</li> +<li>Two</li> +</ol> +!! result +<ol> +<li>One</li> +<li>Two</li> +</ol> + +!! end + +!! test +HTML ordered list, unclosed tags (bug 5497) +!! options +disabled +!! input +<ol> +<li>One +<li>Two +</ol> +!! result +<ol> +<li>One +</li><li>Two +</li></ol> + +!! end + +!! test +HTML nested bullet list, closed tags (bug 5497) +!! input +<ul> +<li>One</li> +<li>Two: +<ul> +<li>Sub-one</li> +<li>Sub-two</li> +</ul> +</li> +</ul> +!! result +<ul> +<li>One</li> +<li>Two: +<ul> +<li>Sub-one</li> +<li>Sub-two</li> +</ul> +</li> +</ul> + +!! end + +!! test +HTML nested bullet list, open tags (bug 5497) +!! options +disabled +!! input +<ul> +<li>One +<li>Two: +<ul> +<li>Sub-one +<li>Sub-two +</ul> +</ul> +!! result +<ul> +<li>One +</li><li>Two: +<ul> +<li>Sub-one +</li><li>Sub-two +</li></ul> +</li></ul> + +!! end + +!! test +HTML nested ordered list, closed tags (bug 5497) +!! input +<ol> +<li>One</li> +<li>Two: +<ol> +<li>Sub-one</li> +<li>Sub-two</li> +</ol> +</li> +</ol> +!! result +<ol> +<li>One</li> +<li>Two: +<ol> +<li>Sub-one</li> +<li>Sub-two</li> +</ol> +</li> +</ol> + +!! end + +!! test +HTML nested ordered list, open tags (bug 5497) +!! options +disabled +!! input +<ol> +<li>One +<li>Two: +<ol> +<li>Sub-one +<li>Sub-two +</ol> +</ol> +!! result +<ol> +<li>One +</li><li>Two: +<ol> +<li>Sub-one +</li><li>Sub-two +</li></ol> +</li></ol> + +!! end + +!! test +HTML ordered list item with parameters oddity +!! input +<ol><li id="fragment">One</li></ol> +!! result +<ol><li id="fragment">One</li></ol> + +!! end + +!!test +bug 5918: autonumbering +!! input +[http://first/] [http://second] [ftp://ftp] + +ftp://inlineftp + +[mailto:enclosed@mail.tld With target] + +[mailto:enclosed@mail.tld] + +mailto:inline@mail.tld +!! result +<p><a href="http://first/" class="external autonumber" rel="nofollow">[1]</a> <a href="http://second" class="external autonumber" rel="nofollow">[2]</a> <a href="ftp://ftp" class="external autonumber" rel="nofollow">[3]</a> +</p><p><a href="ftp://inlineftp" class="external free" rel="nofollow">ftp://inlineftp</a> +</p><p><a href="mailto:enclosed@mail.tld" class="external text" rel="nofollow">With target</a> +</p><p><a href="mailto:enclosed@mail.tld" class="external autonumber" rel="nofollow">[4]</a> +</p><p><a href="mailto:inline@mail.tld" class="external free" rel="nofollow">mailto:inline@mail.tld</a> +</p> +!! end + + +# +# Security and HTML correctness +# From Nick Jenkins' fuzz testing +# + +!! test +Fuzz testing: Parser13 +!! input +{| +| http://a| +!! result +<table> +<tr> +<td> +</td> +</tr> +</table> + +!! end + +!! test +Fuzz testing: Parser14 +!! input +== onmouseover= == +http://__TOC__ +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: onmouseover=">edit</a>]</span> <span class="mw-headline" id="onmouseover.3D"> onmouseover= </span></h2> +http://<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#onmouseover.3D"><span class="tocnumber">1</span> <span class="toctext">onmouseover=</span></a></li> +</ul> +</td></tr></table> + +!! end + +!! test +Fuzz testing: Parser14-table +!! input +==a== +{| STYLE=__TOC__ +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: a">edit</a>]</span> <span class="mw-headline" id="a">a</span></h2> +<table style="__TOC__"> +<tr><td></td></tr> +</table> + +!! end + +# Known to produce bogus xml (extra </td>) +!! test +Fuzz testing: Parser16 +!! options +noxml +!! input +{| +!https://|||||| +!! result +<table> +<tr> +<th>https://</th> +<th></th> +<th></th> +<th> +</td> +</tr> +</table> + +!! end + +!! test +Fuzz testing: Parser21 +!! input +{| +! irc://{{ftp://a" onmouseover="alert('hello world');" +| +!! result +<table> +<tr> +<th> <a href="irc://{{ftp://a" class="external free" rel="nofollow">irc://{{ftp://a</a>" onmouseover="alert('hello world');" +</th> +<td> +</td> +</tr> +</table> + +!! end + +!! test +Fuzz testing: Parser22 +!! input +http://===r:::https://b + +{| +!!result +<p><a href="http://===r:::https://b" class="external free" rel="nofollow">http://===r:::https://b</a> +</p> +<table> +<tr><td></td></tr> +</table> + +!! end + +# Known to produce bad XML for now +!! test +Fuzz testing: Parser24 +!! options +noxml +!! input +{| +{{{| +<u CLASS= +| {{{{SSSll!!!!!!!VVVV)]]][[Special:*xxxxxxx--><noinclude>}}}} > +<br style="onmouseover='alert(document.cookie);' " /> + +MOVE YOUR MOUSE CURSOR OVER THIS TEXT +| +!! result +<table> +{{{| +<u class="|">}}}} > +<br style="onmouseover='alert(document.cookie);'" /> + +MOVE YOUR MOUSE CURSOR OVER THIS TEXT +<tr> +<td></u> +</td> +</tr> +</table> + +!! end + +# Note: the current result listed for this is not what the original one was, +# but the original bug was JavaScript injection, which is fixed in any case. +# It's not clear that the original result listed was any more correct than the +# current one. Original result: +# <p>{{{| +# </p> +# <li class="||"> +# }}}blah" onmouseover="alert('hello world');" align="left"<b>MOVE MOUSE CURSOR OVER HERE</b> +!!test +Fuzz testing: Parser25 (bug 6055) +!! input +{{{ +| +<LI CLASS=|| + > +}}}blah" onmouseover="alert('hello world');" align="left"'''MOVE MOUSE CURSOR OVER HERE +!! result +<p><LI CLASS=blah" onmouseover="alert('hello world');" align="left"<b>MOVE MOUSE CURSOR OVER HERE</b> +</p> +!! end + +!!test +Fuzz testing: URL adjacent extension (with space, clean) +!! options +!! input +http://example.com <nowiki>junk</nowiki> +!! result +<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a> junk +</p> +!!end + +!!test +Fuzz testing: URL adjacent extension (no space, dirty; nowiki) +!! options +!! input +http://example.com<nowiki>junk</nowiki> +!! result +<p><a href="http://example.com" class="external free" rel="nofollow">http://example.com</a>junk +</p> +!!end + +!!test +Fuzz testing: URL adjacent extension (no space, dirty; pre) +!! options +!! input +http://example.com<pre>junk</pre> +!! result +<a href="http://example.com" class="external free" rel="nofollow">http://example.com</a><pre>junk</pre> + +!!end + +!!test +Fuzz testing: image with bogus manual thumbnail +!!input +[[Image:foobar.jpg|thumbnail= ]] +!!result +<div class="thumb tright"><div class="thumbinner" style="width:1943px;">Error creating thumbnail: <div class="thumbcaption"></div></div></div> + +!!end + +!! test +Fuzz testing: encoded newline in generated HTML replacements (bug 6577) +!! input +<pre dir=" "></pre> +!! result +<pre dir=" "></pre> + +!! end + +!! test +Parsing optional HTML elements (Bug 6171) +!! options +!! input +<table> + <tr> + <td> Some tabular data</td> + <td> More tabular data ... + <td> And yet som tabular data</td> + </tr> +</table> +!! result +<table> + <tr> + <td> Some tabular data</td> + <td> More tabular data ... + </td><td> And yet som tabular data</td> + </tr> +</table> + +!! end + +!! test +Correct handling of <td>, <tr> (Bug 6171) +!! options +!! input +<table> + <tr> + <td> Some tabular data</td> + <td> More tabular data ...</td> + <td> And yet som tabular data</td> + </tr> +</table> +!! result +<table> + <tr> + <td> Some tabular data</td> + <td> More tabular data ...</td> + <td> And yet som tabular data</td> + </tr> +</table> + +!! end + + +!! test +Parsing crashing regression (fr:JavaScript) +!! input +</body></x> +!! result +<p></body></x> +</p> +!! end + +!! test +Inline wiki vs wiki block nesting +!! input +'''Bold paragraph + +New wiki paragraph +!! result +<p><b>Bold paragraph</b> +</p><p>New wiki paragraph +</p> +!! end + +!! test +Inline HTML vs wiki block nesting +!! options +disabled +!! input +<b>Bold paragraph + +New wiki paragraph +!! result +<p><b>Bold paragraph</b> +</p><p>New wiki paragraph +</p> +!! end + +# Original result was this: +# <p><b>bold</b><b>bold<i>bolditalics</i></b> +# </p> +# While that might be marginally more intuitive, maybe, the six-apostrophe +# construct is clearly pathological and the result stated here (which is what +# the parser actually does) is about as reasonable as anything. +!!test +Mixing markup for italics and bold +!! options +!! input +'''bold''''''bold''bolditalics''''' +!! result +<p>'<i>bold'</i><b>bold<i>bolditalics</i></b> +</p> +!! end + + +!! article +Xyzzyx +!! text +Article for special page transclusion test +!! endarticle + +!! test +Special page transclusion +!! options +!! input +{{Special:Prefixindex/Xyzzyx}} +!! result +<p><br /> +</p> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> + +!! end + +!! test +Special page transclusion twice (bug 5021) +!! options +!! input +{{Special:Prefixindex/Xyzzyx}} +{{Special:Prefixindex/Xyzzyx}} +!! result +<p><br /> +</p> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> +<p><br /> +</p> +<table border="0" id="mw-prefixindex-list-table"><tr><td><a href="/wiki/Xyzzyx">Xyzzyx</a></td></tr></table> + +!! end + +!! test +Transclusion of default MediaWiki message +!! input +{{MediaWiki:Mainpage}} +!!result +<p>Main Page +</p> +!! end + +!! test +Transclusion of nonexistent MediaWiki message +!! input +{{MediaWiki:Mainpagexxx}} +!!result +<p><a href="/index.php?title=MediaWiki:Mainpagexxx&action=edit&redlink=1" class="new" title="MediaWiki:Mainpagexxx (page does not exist)">MediaWiki:Mainpagexxx</a> +</p> +!! end + +!! test +Transclusion of MediaWiki message with underscore +!! input +{{MediaWiki:history_short}} +!! result +<p>History +</p> +!! end + +!! test +Transclusion of MediaWiki message with space +!! input +{{MediaWiki:history short}} +!! result +<p>History +</p> +!! end + +!! test +Invalid header with following text +!! input += x = y +!! result +<p>= x = y +</p> +!! end + + +!! test +Section extraction test (section 0) +!! options +section=0 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +!! end + +!! test +Section extraction test (section 1) +!! options +section=1 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +==a== +===aa=== +====aaa==== +!! end + +!! test +Section extraction test (section 2) +!! options +section=2 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +===aa=== +====aaa==== +!! end + +!! test +Section extraction test (section 3) +!! options +section=3 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +====aaa==== +!! end + +!! test +Section extraction test (section 4) +!! options +section=4 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +==b== +===ba=== +===bb=== +====bba==== +===bc=== +!! end + +!! test +Section extraction test (section 5) +!! options +section=5 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +===ba=== +!! end + +!! test +Section extraction test (section 6) +!! options +section=6 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +===bb=== +====bba==== +!! end + +!! test +Section extraction test (section 7) +!! options +section=7 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +====bba==== +!! end + +!! test +Section extraction test (section 8) +!! options +section=8 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +===bc=== +!! end + +!! test +Section extraction test (section 9) +!! options +section=9 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +==c== +===ca=== +!! end + +!! test +Section extraction test (section 10) +!! options +section=10 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +===ca=== +!! end + +!! test +Section extraction test (nonexistent section 11) +!! options +section=11 +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +!! end + +!! test +Section extraction test with bogus heading (section 1) +!! options +section=1 +!! input +==a== +==bogus== not a legal section +==b== +!! result +==a== +==bogus== not a legal section +!! end + +!! test +Section extraction test with bogus heading (section 2) +!! options +section=2 +!! input +==a== +==bogus== not a legal section +==b== +!! result +==b== +!! end + +!! test +Section extraction test with comment after heading (section 1) +!! options +section=1 +!! input +==a== +==b== <!-- --> +==c== +!! result +==a== +!! end + +!! test +Section extraction test with comment after heading (section 2) +!! options +section=2 +!! input +==a== +==b== <!-- --> +==c== +!! result +==b== <!-- --> +!! end + +!! test +Section extraction test with bogus <nowiki> heading (section 1) +!! options +section=1 +!! input +==a== +==bogus== <nowiki>not a legal section</nowiki> +==b== +!! result +==a== +==bogus== <nowiki>not a legal section</nowiki> +!! end + +!! test +Section extraction test with bogus <nowiki> heading (section 2) +!! options +section=2 +!! input +==a== +==bogus== <nowiki>not a legal section</nowiki> +==b== +!! result +==b== +!! end + + +# Formerly testing for bug 2587, now resolved by the use of unmarked sections +# instead of respecting commented sections +!! test +Section extraction prefixed by comment (section 1) +!! options +section=1 +!! input +<!-- -->==sec1== +==sec2== +!!result +==sec2== +!!end + +!! test +Section extraction prefixed by comment (section 2) +!! options +section=2 +!! input +<!-- -->==sec1== +==sec2== +!!result + +!!end + + +# Formerly testing for bug 2607, now resolved by the use of unmarked sections +# instead of respecting HTML-style headings +!! test +Section extraction, mixed wiki and html (section 1) +!! options +section=1 +!! input +<h2>unmarked</h2> +unmarked +==1== +one +==2== +two +!! result +==1== +one +!! end + +!! test +Section extraction, mixed wiki and html (section 2) +!! options +section=2 +!! input +<h2>unmarked</h2> +unmarked +==1== +one +==2== +two +!! result +==2== +two +!! end + + +# Formerly testing for bug 3342 +!! test +Section extraction, heading surrounded by <noinclude> +!! options +section=1 +!! input +<noinclude>==unmarked==</noinclude> +==marked== +!! result +==marked== +!!end + +# Test behaviour of bug 19910 +!! test +Sectiion with all-equals +!! options +section=2 +!! input +=== +The line above must have a trailing space +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! result +=== <!-- +--> <!-- --> +But just in case it doesn't... +!! end + +!! test +Section replacement test (section 0) +!! options +replace=0,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +xxx + +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 1) +!! options +replace=1,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +xxx + +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 2) +!! options +replace=2,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +xxx + +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 3) +!! options +replace=3,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +xxx + +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 4) +!! options +replace=4,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +xxx + +==c== +===ca=== +!! end + +!! test +Section replacement test (section 5) +!! options +replace=5,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +xxx + +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 6) +!! options +replace=6,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +xxx + +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 7) +!! options +replace=7,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +xxx + +===bc=== +==c== +===ca=== +!! end + +!! test +Section replacement test (section 8) +!! options +replace=8,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +xxx + +==c== +===ca=== +!!end + +!! test +Section replacement test (section 9) +!! options +replace=9,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +xxx +!! end + +!! test +Section replacement test (section 10) +!! options +replace=10,"xxx" +!! input +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +===ca=== +!! result +start +==a== +===aa=== +====aaa==== +==b== +===ba=== +===bb=== +====bba==== +===bc=== +==c== +xxx +!! end + +!! test +Section replacement test with initial whitespace (bug 13728) +!! options +replace=2,"xxx" +!! input + Preformatted initial line +==a== +===a=== +!! result + Preformatted initial line +==a== +xxx +!! end + + +!! test +Section extraction, heading followed by pre with 20 spaces (bug 6398) +!! options +section=1 +!! input +==a== + a +!! result +==a== + a +!! end + +!! test +Section extraction, heading followed by pre with 19 spaces (bug 6398 sanity check) +!! options +section=1 +!! input +==a== + a +!! result +==a== + a +!! end + + +!! test +Section extraction, <pre> around bogus header (bug 10309) +!! options +noxml section=2 +!! input +== Section One == +<pre> +======= +</pre> + +== Section Two == +stuff +!! result +== Section Two == +stuff +!! end + +!! test +Section replacement, <pre> around bogus header (bug 10309) +!! options +noxml replace=2,"xxx" +!! input +== Section One == +<pre> +======= +</pre> + +== Section Two == +stuff +!! result +== Section One == +<pre> +======= +</pre> + +xxx +!! end + + + +!! test +Handling of 
 in URLs +!! input +**irc://
a +!! result +<ul><li><ul><li><a href="irc://%0Aa" class="external free" rel="nofollow">irc://%0Aa</a> +</li></ul> +</li></ul> + +!!end + +!! test +5 quotes, code coverage +1 line +!! input +''''' +!! result +!! end + +!! test +Special:Search page linking. +!! input +{{Special:search}} +!! result +<p><a href="/wiki/Special:Search">Special:Search</a> +</p> +!! end + +!! test +Say the magic word +!! input +* {{PAGENAME}} +* {{BASEPAGENAME}} +* {{SUBPAGENAME}} +* {{SUBPAGENAMEE}} +* {{BASEPAGENAME}} +* {{BASEPAGENAMEE}} +* {{TALKPAGENAME}} +* {{TALKPAGENAMEE}} +* {{SUBJECTPAGENAME}} +* {{SUBJECTPAGENAMEE}} +* {{NAMESPACEE}} +* {{NAMESPACE}} +* {{TALKSPACE}} +* {{TALKSPACEE}} +* {{SUBJECTSPACE}} +* {{SUBJECTSPACEE}} +* {{Dynamic|{{NUMBEROFUSERS}}|{{NUMBEROFPAGES}}|{{CURRENTVERSION}}|{{CONTENTLANGUAGE}}|{{DIRECTIONMARK}}|{{CURRENTTIMESTAMP}}|{{NUMBEROFARTICLES}}}} +!! result +<ul><li> Parser test +</li><li> Parser test +</li><li> Parser test +</li><li> Parser_test +</li><li> Parser test +</li><li> Parser_test +</li><li> Talk:Parser test +</li><li> Talk:Parser_test +</li><li> Parser test +</li><li> Parser_test +</li><li> +</li><li> +</li><li> Talk +</li><li> Talk +</li><li> +</li><li> +</li><li> <a href="/index.php?title=Template:Dynamic&action=edit&redlink=1" class="new" title="Template:Dynamic (page does not exist)">Template:Dynamic</a> +</li></ul> + +!! end +### Note: Above tests excludes the "{{NUMBEROFADMINS}}" magic word because it generates a MySQL error when included. + +!! test +Gallery +!! input +<gallery> +image1.png | +image2.gif||||| + +image3| +image4 |300px| centre + image5.svg| http:///////// +[[x|xx]]]] +* image6 +</gallery> +!! result +<ul class="gallery"> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image1.png</div> + <div class="gallerytext"> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image2.gif</div> + <div class="gallerytext"> +<p>|||| +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image3</div> + <div class="gallerytext"> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image4</div> + <div class="gallerytext"> +<p>300px| centre +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Image5.svg</div> + <div class="gallerytext"> +<p><a href="http://///////" class="external free" rel="nofollow">http://///////</a> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">* image6</div> + <div class="gallerytext"> + </div> + </div></li> +</ul> + +!! end + +!! test +Gallery (with options) +!! input +<gallery widths='60px' heights='40px' perrow='2' caption='Foo [[Main Page]]' > +File:Nonexistant.jpg|caption +File:Nonexistant.jpg +image:foobar.jpg|some '''caption''' [[Main Page]] +image:foobar.jpg +</gallery> +!! result +<ul class="gallery" style="max-width: 206px;_width: 206px;"> + <li class='gallerycaption'>Foo <a href="/wiki/Main_Page">Main Page</a></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div style="height: 70px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p>caption +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div style="height: 70px;">Nonexistant.jpg</div> + <div class="gallerytext"> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div class="thumb" style="width: 90px; height: 70px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="60" height="7" /></a></div></div> + <div class="gallerytext"> +<p>some <b>caption</b> <a href="/wiki/Main_Page">Main Page</a> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 95px"><div style="width: 95px"> + <div class="thumb" style="width: 90px; height: 70px;"><div style="margin:31px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="60" height="7" /></a></div></div> + <div class="gallerytext"> + </div> + </div></li> +</ul> + +!! end + +!! test +gallery (with showfilename option) +!! input +<gallery showfilename> +File:Nonexistant.jpg|caption +File:Nonexistant.jpg +image:foobar.jpg|some '''caption''' [[Main Page]] +File:Foobar.jpg +</gallery> +!! result +<ul class="gallery"> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> +caption +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div style="height: 150px;">Nonexistant.jpg</div> + <div class="gallerytext"> +<p><a href="/wiki/File:Nonexistant.jpg" title="File:Nonexistant.jpg">Nonexistant.jpg</a><br /> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div class="thumb" style="width: 150px; height: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="gallerytext"> +<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> +some <b>caption</b> <a href="/wiki/Main_Page">Main Page</a> +</p> + </div> + </div></li> + <li class="gallerybox" style="width: 155px"><div style="width: 155px"> + <div class="thumb" style="width: 150px; height: 150px;"><div style="margin:68px auto;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="120" height="14" /></a></div></div> + <div class="gallerytext"> +<p><a href="/wiki/File:Foobar.jpg" title="File:Foobar.jpg">Foobar.jpg</a><br /> +</p> + </div> + </div></li> +</ul> + +!! end + +!! test +HTML Hex character encoding (spells the word "JavaScript") +!! input +JavaScript +!! result +<p>JavaScript +</p> +!! end + +!! test +__FORCETOC__ override +!! input +__NEWSECTIONLINK__ +__FORCETOC__ +!! result +<p><br /> +</p> +!! end + +!! test +ISBN code coverage +!! input +ISBN 978-0-1234-56 789 +!! result +<p><a href="/wiki/Special:BookSources/9780123456" class="internal mw-magiclink-isbn">ISBN 978-0-1234-56</a> 789 +</p> +!! end + +!! test +ISBN followed by 5 spaces +!! input +ISBN +!! result +<p>ISBN +</p> +!! end + +!! test +Double ISBN +!! input +ISBN ISBN 1234567890 +!! result +<p>ISBN <a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a> +</p> +!! end + +!! test +Bug 22905: <abbr> followed by ISBN followed by </a> +!! input +<abbr>(fr)</abbr> ISBN 2753300917 [http://www.example.com example.com] +!! result +<p><abbr>(fr)</abbr> <a href="/wiki/Special:BookSources/2753300917" class="internal mw-magiclink-isbn">ISBN 2753300917</a> <a href="http://www.example.com" class="external text" rel="nofollow">example.com</a> +</p> +!! end + +!! test +Double RFC +!! input +RFC RFC 1234 +!! result +<p>RFC <a href="http://tools.ietf.org/html/rfc1234" class="external mw-magiclink-rfc">RFC 1234</a> +</p> +!! end + +!! test +Double RFC with a wiki link +!! input +RFC [[RFC 1234]] +!! result +<p>RFC <a href="/index.php?title=RFC_1234&action=edit&redlink=1" class="new" title="RFC 1234 (page does not exist)">RFC 1234</a> +</p> +!! end + +!! test +RFC code coverage +!! input +RFC 983 987 +!! result +<p><a href="http://tools.ietf.org/html/rfc983" class="external mw-magiclink-rfc">RFC 983</a> 987 +</p> +!! end + +!! test +Centre-aligned image +!! input +[[Image:foobar.jpg|centre]] +!! result +<div class="center"><div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div></div> + +!!end + +!! test +None-aligned image +!! input +[[Image:foobar.jpg|none]] +!! result +<div class="floatnone"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="1941" height="220" /></a></div> + +!!end + +!! test +Width + Height sized image (using px) (height is ignored) +!! input +[[Image:foobar.jpg|640x480px]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +</p> +!!end + +!! test +Width-sized image (using px, no following whitespace) +!! input +[[Image:foobar.jpg|640px]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +</p> +!!end + +!! test +Width-sized image (using px, with following whitespace - test regression from r39467) +!! input +[[Image:foobar.jpg|640px ]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +</p> +!!end + +!! test +Width-sized image (using px, with preceding whitespace - test regression from r39467) +!! input +[[Image:foobar.jpg| 640px]] +!! result +<p><a href="/wiki/File:Foobar.jpg" class="image"><img alt="Foobar.jpg" src="http://example.com/images/3/3a/Foobar.jpg" width="640" height="73" /></a> +</p> +!!end + +!! test +Another italics / bold test +!! input + ''' ''x' +!! result +<pre>'<i> </i>x' +</pre> +!!end + +# Note the results may be incorrect, as parserTest output included this: +# XML error: Mismatched tag at byte 6120: +# ...<dd> </dt></dl> </dd... +!! test +dt/dd/dl test +!! options +disabled +!! input +:;;;:: +!! result +<dl><dd><dl><dt><dl><dt><dl><dt><dl><dd><dl><dd> +</dd></dl> +</dd></dl> +</dt></dl> +</dt></dl> +</dt></dl> +</dd></dl> + +!!end + + +# Images with the "|" character in external URLs in comment tags; Eats half the comment, leaves unmatched "</a>" tag. +!! test +Images with the "|" character in the comment +!! input +[[image:Foobar.jpg|thumb|An [http://test/?param1=|left|¶m2=|x external] URL]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>An <a href="http://test/?param1=%7Cleft%7C&param2=%7Cx" class="external text" rel="nofollow">external</a> URL</div></div></div> + +!!end + +!! test +[Before] HTML without raw HTML enabled ($wgRawHtml==false) +!! input +<html><script>alert(1);</script></html> +!! result +<p><html><script>alert(1);</script></html> +</p> +!! end + +!! test +HTML with raw HTML ($wgRawHtml==true) +!! options +rawhtml +!! input +<html><script>alert(1);</script></html> +!! result +<p><script>alert(1);</script> +</p> +!! end + +!! test +Parents of subpages, one level up +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +[[../|L2]] +!! result +<p><a href="/index.php?title=Subpage_test/L1/L2&action=edit&redlink=1" class="new" title="Subpage test/L1/L2 (page does not exist)">L2</a> +</p> +!! end + + +!! test +Parents of subpages, one level up, not named +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +[[../]] +!! result +<p><a href="/index.php?title=Subpage_test/L1/L2&action=edit&redlink=1" class="new" title="Subpage test/L1/L2 (page does not exist)">Subpage test/L1/L2</a> +</p> +!! end + + + +!! test +Parents of subpages, two levels up +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +[[../../|L1]]2 + +[[../../|L1]]l +!! result +<p><a href="/index.php?title=Subpage_test/L1&action=edit&redlink=1" class="new" title="Subpage test/L1 (page does not exist)">L1</a>2 +</p><p><a href="/index.php?title=Subpage_test/L1&action=edit&redlink=1" class="new" title="Subpage test/L1 (page does not exist)">L1l</a> +</p> +!! end + +!! test +Parents of subpages, two levels up, without trailing slash or name. +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +[[../..]] +!! result +<p>[[../..]] +</p> +!! end + +!! test +Parents of subpages, two levels up, with lots of extra trailing slashes. +!! options +subpage title=[[Subpage test/L1/L2/L3]] +!! input +[[../../////]] +!! result +<p><a href="/index.php?title=Subpage_test/L1////&action=edit&redlink=1" class="new" title="Subpage test/L1//// (page does not exist)">///</a> +</p> +!! end + +!! test +Definition list code coverage +!! input +; title : def +; title : def +;title: def +!! result +<dl><dt> title  </dt><dd> def +</dd><dt> title </dt><dd> def +</dd><dt>title</dt><dd> def +</dd></dl> + +!! end + +!! test +Don't fall for the self-closing div +!! input +<div>hello world</div/> +!! result +<div>hello world</div> + +!! end + +!! test +MSGNW magic word +!! input +{{MSGNW:msg}} +!! result +<p>[[:Template:Msg]] +</p> +!! end + +!! test +RAW magic word +!! input +{{RAW:QUERTY}} +!! result +<p><a href="/index.php?title=Template:QUERTY&action=edit&redlink=1" class="new" title="Template:QUERTY (page does not exist)">Template:QUERTY</a> +</p> +!! end + +# This isn't needed for XHTML conformance, but would be handy as a fallback security measure +!! test +Always escape literal '>' in output, not just after '<' +!! input +><> +!! result +<p>><> +</p> +!! end + +!! test +Template caching +!! input +{{Test}} +{{Test}} +!! result +<p>This is a test template +This is a test template +</p> +!! end + + +!! article +MediaWiki:Fake +!! text +==header== +!! endarticle + +!! test +Inclusion of !userCanEdit() content +!! input +{{MediaWiki:Fake}} +!! result +<h2><span class="editsection">[<a href="/index.php?title=MediaWiki:Fake&action=edit&section=T-1" title="MediaWiki:Fake">edit</a>]</span> <span class="mw-headline" id="header">header</span></h2> + +!! end + + +!! test +Out-of-order TOC heading levels +!! input +==2== +======6====== +===3=== +=1= +=====5===== +==2== +!! result +<table id="toc" class="toc"><tr><td><div id="toctitle"><h2>Contents</h2></div> +<ul> +<li class="toclevel-1 tocsection-1"><a href="#2"><span class="tocnumber">1</span> <span class="toctext">2</span></a> +<ul> +<li class="toclevel-2 tocsection-2"><a href="#6"><span class="tocnumber">1.1</span> <span class="toctext">6</span></a></li> +<li class="toclevel-2 tocsection-3"><a href="#3"><span class="tocnumber">1.2</span> <span class="toctext">3</span></a></li> +</ul> +</li> +<li class="toclevel-1 tocsection-4"><a href="#1"><span class="tocnumber">2</span> <span class="toctext">1</span></a> +<ul> +<li class="toclevel-2 tocsection-5"><a href="#5"><span class="tocnumber">2.1</span> <span class="toctext">5</span></a></li> +<li class="toclevel-2 tocsection-6"><a href="#2_2"><span class="tocnumber">2.2</span> <span class="toctext">2</span></a></li> +</ul> +</li> +</ul> +</td></tr></table> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2">2</span></h2> +<h6><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=2" title="Edit section: 6">edit</a>]</span> <span class="mw-headline" id="6">6</span></h6> +<h3><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=3" title="Edit section: 3">edit</a>]</span> <span class="mw-headline" id="3">3</span></h3> +<h1><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=4" title="Edit section: 1">edit</a>]</span> <span class="mw-headline" id="1">1</span></h1> +<h5><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=5" title="Edit section: 5">edit</a>]</span> <span class="mw-headline" id="5">5</span></h5> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=6" title="Edit section: 2">edit</a>]</span> <span class="mw-headline" id="2_2">2</span></h2> + +!! end + + +!! test +ISBN with a dummy number +!! input +ISBN --- +!! result +<p>ISBN --- +</p> +!! end + + +!! test +ISBN with space-delimited number +!! input +ISBN 92 9017 032 8 +!! result +<p><a href="/wiki/Special:BookSources/9290170328" class="internal mw-magiclink-isbn">ISBN 92 9017 032 8</a> +</p> +!! end + + +!! test +ISBN with multiple spaces, no number +!! input +ISBN foo +!! result +<p>ISBN foo +</p> +!! end + + +!! test +ISBN length +!! input +ISBN 123456789 + +ISBN 1234567890 + +ISBN 12345678901 +!! result +<p>ISBN 123456789 +</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1234567890</a> +</p><p>ISBN 12345678901 +</p> +!! end + + +!! test +ISBN with trailing year (bug 8110) +!! input +ISBN 1-234-56789-0 - 2006 + +ISBN 1 234 56789 0 - 2006 +!! result +<p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1-234-56789-0</a> - 2006 +</p><p><a href="/wiki/Special:BookSources/1234567890" class="internal mw-magiclink-isbn">ISBN 1 234 56789 0</a> - 2006 +</p> +!! end + + +!! test +anchorencode +!! input +{{anchorencode:foo bar©#%n}} +!! result +<p>foo_bar.C2.A9.23.25n +</p> +!! end + +!! test +anchorencode trims spaces +!! input +{{anchorencode: __pretty__please__}} +!! result +<p>pretty_please +</p> +!! end + +!! test +anchorencode deals with links +!! input +{{anchorencode: [[hello|world]] [[hi]]}} +!! result +<p>world_hi +</p> +!! end + +!! test +anchorencode deals with templates +!! input +{{anchorencode: {{Foo}} }} +!! result +<p>FOO +</p> +!! end + +!! test +anchorencode encodes like the TOC generator: (bug 18431) +!! input +=== _ +:.3A%3A&&]] === +{{anchorencode: _ +:.3A%3A&&]] }} +__NOEDITSECTION__ +!! result +<h3> <span class="mw-headline" id=".2B:.3A.253A.26.26.5D.5D"> _ +:.3A%3A&&]] </span></h3> +<p>.2B:.3A.253A.26.26.5D.5D +</p> +!! end + +# Expected output in the following test is not necessarily expected (there +# should probably be <p> tags inside the <blockquote> in the output) -- it's +# only testing for well-formedness. +!! test +Bug 6200: blockquotes and paragraph formatting +!! input +<blockquote> +foo +</blockquote> + +bar + + baz +!! result +<blockquote> +foo +</blockquote> +<p>bar +</p> +<pre>baz +</pre> +!! end + +!! test +Bug 8293: Use of center tag ruins paragraph formatting +!! input +<center> +foo +</center> + +bar + + baz +!! result +<center> +<p>foo +</p> +</center> +<p>bar +</p> +<pre>baz +</pre> +!! end + + +### +### Language variants related tests +### +!! test +Self-link in language variants +!! options +title=[[Dunav]] language=sr +!! input +Both [[Dunav]] and [[Дунав]] are names for this river. +!! result +<p>Both <strong class="selflink">Dunav</strong> and <strong class="selflink">Дунав</strong> are names for this river. +</p> +!!end + + +!! test +Link to pages in language variants +!! options +language=sr +!! input +Main Page can be written as [[Маин Паге]] +!! result +<p>Main Page can be written as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a> +</p> +!!end + + +!! test +Multiple links to pages in language variants +!! options +language=sr +!! input +[[Main Page]] can be written as [[Маин Паге]] same as [[Маин Паге]]. +!! result +<p><a href="/wiki/Main_Page">Main Page</a> can be written as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a> same as <a href="/wiki/Main_Page" title="Main Page">Маин Паге</a>. +</p> +!!end + + +!! test +Simple template in language variants +!! options +language=sr +!! input +{{тест}} +!! result +<p>This is a test template +</p> +!! end + + +!! test +Template with explicit namespace in language variants +!! options +language=sr +!! input +{{Template:тест}} +!! result +<p>This is a test template +</p> +!! end + + +!! test +Basic test for template parameter in language variants +!! options +language=sr +!! input +{{парамтест|param=foo}} +!! result +<p>This is a test template with parameter foo +</p> +!! end + + +!! test +Simple category in language variants +!! options +language=sr cat +!! input +[[Category:МедиаWики Усер'с Гуиде]] +!! result +<a href="/wiki/%D0%9A%D0%B0%D1%82%D0%B5%D0%B3%D0%BE%D1%80%D0%B8%D1%98%D0%B0:MediaWiki_User%27s_Guide" title="Категорија:MediaWiki User's Guide">MediaWiki User's Guide</a> +!! end + + +!! test +Stripping -{}- tags (language variants) +!! options +language=sr +!! input +Latin proverb: -{Ne nuntium necare}- +!! result +<p>Latin proverb: Ne nuntium necare +</p> +!! end + + +!! test +Prevent conversion with -{}- tags (language variants) +!! options +language=sr variant=sr-ec +!! input +Latinski: -{Ne nuntium necare}- +!! result +<p>Латински: Ne nuntium necare +</p> +!! end + + +!! test +Prevent conversion of text with -{}- tags (language variants) +!! options +language=sr variant=sr-ec +!! input +Latinski: -{Ne nuntium necare}- +!! result +<p>Латински: Ne nuntium necare +</p> +!! end + + +!! test +Prevent conversion of links with -{}- tags (language variants) +!! options +language=sr variant=sr-ec +!! input +-{[[Main Page]]}- +!! result +<p><a href="/wiki/Main_Page">Main Page</a> +</p> +!! end + + +!! test +-{}- tags within headlines (within html for parserConvert()) +!! options +language=sr variant=sr-ec +!! input +== -{Naslov}- == +!! result +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Уреди део: Naslov">уреди</a>]</span> <span class="mw-headline" id="-.7BNaslov.7D-"> Naslov </span></h2> + +!! end + + +!! test +Explicit definition of language variant alternatives +!! options +language=zh variant=zh-tw +!! input +-{zh:China;zh-tw:Taiwan}-, not China +!! result +<p>Taiwan, not China +</p> +!! end + + +!! test +Explicit session-wise language variant mapping (A flag and - flag) +!! options +language=zh variant=zh-tw +!! input +Taiwan is not China. +But -{A|zh:China;zh-tw:Taiwan}- is China, +(This-{-|zh:China;zh-tw:Taiwan}- should be stripped!) +and -{China}- is China. +!! result +<p>Taiwan is not China. +But Taiwan is Taiwan, +(This should be stripped!) +and China is China. +</p> +!! end + +!! test +Explicit session-wise language variant mapping (H flag for hide) +!! options +language=zh variant=zh-tw +!! input +(This-{H|zh:China;zh-tw:Taiwan}- should be stripped!) +Taiwan is China. +!! result +<p>(This should be stripped!) +Taiwan is Taiwan. +</p> +!! end + +!! test +Adding explicit conversion rule for title (T flag) +!! options +language=zh variant=zh-tw showtitle +!! input +Should be stripped-{T|zh:China;zh-tw:Taiwan}-! +!! result +Taiwan +<p>Should be stripped! +</p> +!! end + +!! test +Testing that changing the language variant here in the tests actually works +!! options +language=zh variant=zh showtitle +!! input +Should be stripped-{T|zh:China;zh-tw:Taiwan}-! +!! result +China +<p>Should be stripped! +</p> +!! end + +!! test +Bug 24072: more test on conversion rule for title +!! options +language=zh variant=zh-tw showtitle +!! input +This should be stripped-{T|zh:China;zh-tw:Taiwan}-! +This won't take interferes with the title rule-{H|zh:Beijing;zh-tw:Taipei}-. +!! result +Taiwan +<p>This should be stripped! +This won't take interferes with the title rule. +</p> +!! end + +!! test +Raw output of variant escape tags (R flag) +!! options +language=zh variant=zh-tw +!! input +Raw: -{R|zh:China;zh-tw:Taiwan}- +!! result +<p>Raw: zh:China;zh-tw:Taiwan +</p> +!! end + +!! test +Nested using of manual convert syntax +!! options +language=zh variant=zh-hk +!! input +Nested: -{zh-hans:Hi -{zh-cn:China;zh-sg:Singapore;}-;zh-hant:Hello -{zh-tw:Taiwan;zh-hk:H-{ong}- K-{}-ong;}-;}-! +!! result +<p>Nested: Hello Hong Kong! +</p> +!! end + +!! test +Do not convert roman numbers to language variants +!! options +language=sr variant=sr-ec +!! input +Fridrih IV je car. +!! result +<p>Фридрих IV је цар. +</p> +!! end + +!! test +Unclosed language converter markup "-{" +!! options +language=sr +!! input +-{T|hello +!! result +<p>-{T|hello +</p> +!! end + +!! test +Don't convert raw rule "-{R|=>}-" to "=>" +!! options +language=sr +!! input +-{R|=>}- +!! result +<p>=> +</p> +!!end + +!!article +Template:Bullet +!!text +* Bar +!!endarticle + +!! test +Bug 529: Uncovered bullet +!! input +* Foo {{bullet}} +!! result +<ul><li> Foo +</li><li> Bar +</li></ul> + +!! end + +!! test +Bug 529: Uncovered table already at line-start +!! input +x + +{{table}} +y +!! result +<p>x +</p> +<table> +<tr> +<td> 1 </td> +<td> 2 +</td></tr> +<tr> +<td> 3 </td> +<td> 4 +</td></tr></table> +<p>y +</p> +!! end + +!! test +Bug 529: Uncovered bullet in parser function result +!! input +* Foo {{lc:{{bullet}} }} +!! result +<ul><li> Foo +</li><li> bar +</li></ul> + +!! end + +!! test +Bug 5678: Double-parsed template argument +!! input +{{lc:{{{1}}}|hello}} +!! result +<p>{{{1}}} +</p> +!! end + +!! test +Bug 5678: Double-parsed template invocation +!! input +{{lc:{{paramtest {{!}} param = hello }} }} +!! result +<p>{{paramtest | param = hello }} +</p> +!! end + +!! test +Case insensitivity of parser functions for non-ASCII characters (bug 8143) +!! options +language=cs +title=[[Main Page]] +!! input +{{PRVNÍVELKÉ:ěščř}} +{{prvnívelké:ěščř}} +{{PRVNÍMALÉ:ěščř}} +{{prvnímalé:ěščř}} +{{MALÁ:ěščř}} +{{malá:ěščř}} +{{VELKÁ:ěščř}} +{{velká:ěščř}} +!! result +<p>Ěščř +Ěščř +ěščř +ěščř +ěščř +ěščř +ĚŠČŘ +ĚŠČŘ +</p> +!! end + +!! test +Morwen/13: Unclosed link followed by heading +!! input +[[link +==heading== +!! result +<p>[[link +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2> + +!! end + +!! test +HHP2.1: Heuristics for headings in preprocessor parenthetical structures +!! input +{{foo| +=heading= +!! result +<p>{{foo| +</p> +<h1> <span class="mw-headline" id="heading">heading</span></h1> + +!! end + +!! test +HHP2.2: Heuristics for headings in preprocessor parenthetical structures +!! input +{{foo| +==heading== +!! result +<p>{{foo| +</p> +<h2><span class="editsection">[<a href="/index.php?title=Parser_test&action=edit&section=1" title="Edit section: heading">edit</a>]</span> <span class="mw-headline" id="heading">heading</span></h2> + +!! end + +!! test +Tildes in comments +!! options +pst +!! input +<!-- ~~~~ --> +!! result +<!-- ~~~~ --> +!! end + +!! test +Paragraphs inside divs (no extra line breaks) +!! input +<div>Line one + +Line two</div> +!! result +<div>Line one +Line two</div> + +!! end + +!! test +Paragraphs inside divs (extra line break on open) +!! input +<div> +Line one + +Line two</div> +!! result +<div> +<p>Line one +</p> +Line two</div> + +!! end + +!! test +Paragraphs inside divs (extra line break on close) +!! input +<div>Line one + +Line two +</div> +!! result +<div>Line one +<p>Line two +</p> +</div> + +!! end + +!! test +Paragraphs inside divs (extra line break on open and close) +!! input +<div> +Line one + +Line two +</div> +!! result +<div> +<p>Line one +</p><p>Line two +</p> +</div> + +!! end + +!! test +Nesting tags, paragraphs on lines which begin with <div> +!! options +disabled +!! input +<div></div><strong>A +B</strong> +!! result +<div></div> +<p><strong>A +B</strong> +</p> +!! end + +# Bug 6200: <blockquote> should behave like <div> with respect to line breaks +!! test +Bug 6200: paragraphs inside blockquotes (no extra line breaks) +!! options +disabled +!! input +<blockquote>Line one + +Line two</blockquote> +!! result +<blockquote>Line one +Line two</blockquote> + +!! end + +!! test +Bug 6200: paragraphs inside blockquotes (extra line break on open) +!! options +disabled +!! input +<blockquote> +Line one + +Line two</blockquote> +!! result +<blockquote> +<p>Line one +</p> +Line two</blockquote> + +!! end + +!! test +Bug 6200: paragraphs inside blockquotes (extra line break on close) +!! options +disabled +!! input +<blockquote>Line one + +Line two +</blockquote> +!! result +<blockquote>Line one +<p>Line two +</p> +</blockquote> + +!! end + +!! test +Bug 6200: paragraphs inside blockquotes (extra line break on open and close) +!! options +disabled +!! input +<blockquote> +Line one + +Line two +</blockquote> +!! result +<blockquote> +<p>Line one +</p><p>Line two +</p> +</blockquote> + +!! end + +!! test +Paragraphs inside blockquotes/divs (no extra line breaks) +!! input +<blockquote><div>Line one + +Line two</div></blockquote> +!! result +<blockquote><div>Line one +Line two</div></blockquote> + +!! end + +!! test +Paragraphs inside blockquotes/divs (extra line break on open) +!! input +<blockquote><div> +Line one + +Line two</div></blockquote> +!! result +<blockquote><div> +<p>Line one +</p> +Line two</div></blockquote> + +!! end + +!! test +Paragraphs inside blockquotes/divs (extra line break on close) +!! input +<blockquote><div>Line one + +Line two +</div></blockquote> +!! result +<blockquote><div>Line one +<p>Line two +</p> +</div></blockquote> + +!! end + +!! test +Paragraphs inside blockquotes/divs (extra line break on open and close) +!! input +<blockquote><div> +Line one + +Line two +</div></blockquote> +!! result +<blockquote><div> +<p>Line one +</p><p>Line two +</p> +</div></blockquote> + +!! end + +!! test +Interwiki links trounced by replaceExternalLinks after early LinkHolderArray expansion +!! options +wgLinkHolderBatchSize=0 +!! input +[[meatball:1]] +[[meatball:2]] +[[meatball:3]] +!! result +<p><a href="http://www.usemod.com/cgi-bin/mb.pl?1" class="extiw">meatball:1</a> +<a href="http://www.usemod.com/cgi-bin/mb.pl?2" class="extiw">meatball:2</a> +<a href="http://www.usemod.com/cgi-bin/mb.pl?3" class="extiw">meatball:3</a> +</p> +!! end + +!! test +Free external link invading image caption +!! input +[[Image:Foobar.jpg|thumb|http://x|hello]] +!! result +<div class="thumb tright"><div class="thumbinner" style="width:182px;"><a href="/wiki/File:Foobar.jpg" class="image"><img alt="" src="http://example.com/images/3/3a/Foobar.jpg" width="180" height="20" class="thumbimage" /></a> <div class="thumbcaption"><div class="magnify"><a href="/wiki/File:Foobar.jpg" class="internal" title="Enlarge"><img src="/skins/common/images/magnify-clip.png" width="15" height="11" alt="" /></a></div>hello</div></div></div> + +!! end + +!! test +Bug 15196: localised external link numbers +!! options +language=fa +!! input +[http://en.wikipedia.org/] +!! result +<p><a href="http://en.wikipedia.org/" class="external autonumber" rel="nofollow">[۱]</a> +</p> +!! end + +!! test +Multibyte character in padleft +!! input +{{padleft:-Hello|7|Æ}} +!! result +<p>Æ-Hello +</p> +!! end + +!! test +Multibyte character in padright +!! input +{{padright:Hello-|7|Æ}} +!! result +<p>Hello-Æ +</p> +!! end + +!! test +Formatted date +!! config +wgUseDynamicDates=1 +!! input +[[2009-03-24]] +!! result +<p><span class="mw-formatted-date" title="2009-03-24"><a href="/index.php?title=2009&action=edit&redlink=1" class="new" title="2009 (page does not exist)">2009</a>-<a href="/index.php?title=March_24&action=edit&redlink=1" class="new" title="March 24 (page does not exist)">03-24</a></span> +</p> +!!end + +!!test +formatdate parser function +!!input +{{#formatdate:2009-03-24}} +!! result +<p><span class="mw-formatted-date" title="2009-03-24">2009-03-24</span> +</p> +!! end + +!!test +formatdate parser function, with default format +!!input +{{#formatdate:2009-03-24|mdy}} +!! result +<p><span class="mw-formatted-date" title="2009-03-24">March 24, 2009</span> +</p> +!! end + +!! test +Linked date with autoformatting disabled +!! config +wgUseDynamicDates=false +!! input +[[2009-03-24]] +!! result +<p><a href="/index.php?title=2009-03-24&action=edit&redlink=1" class="new" title="2009-03-24 (page does not exist)">2009-03-24</a> +</p> +!! end + +!! test +Spacing of numbers in formatted dates +!! input +{{#formatdate:January 15}} +!! result +<p><span class="mw-formatted-date" title="01-15">January 15</span> +</p> +!! end + +!! test +Spacing of numbers in formatted dates (linked) +!! config +wgUseDynamicDates=true +!! input +[[January 15]] +!! result +<p><span class="mw-formatted-date" title="01-15"><a href="/index.php?title=January_15&action=edit&redlink=1" class="new" title="January 15 (page does not exist)">January 15</a></span> +</p> +!! end + +# +# +# + +# +# Edit comments +# + +!! test +Edit comment with link +!! options +comment +!! input +I like the [[Main Page]] a lot +!! result +I like the <a href="/wiki/Main_Page">Main Page</a> a lot +!!end + +!! test +Edit comment with link and link text +!! options +comment +!! input +I like the [[Main Page|best pages]] a lot +!! result +I like the <a href="/wiki/Main_Page" title="Main Page">best pages</a> a lot +!!end + +!! test +Edit comment with link and link text with suffix +!! options +comment +!! input +I like the [[Main Page|best page]]s a lot +!! result +I like the <a href="/wiki/Main_Page" title="Main Page">best pages</a> a lot +!!end + +!! test +Edit comment with section link (non-local, eg in history list) +!! options +comment title=[[Main Page]] +!! input +/* External links */ removed bogus entries +!! result +<span class="autocomment"><a href="/wiki/Main_Page#External_links" title="Main Page">→</a>External links: </span> removed bogus entries +!!end + +!! test +Edit comment with section link (local, eg in diff view) +!! options +comment local title=[[Main Page]] +!! input +/* External links */ removed bogus entries +!! result +<span class="autocomment"><a href="#External_links">→</a>External links: </span> removed bogus entries +!!end + +!! test +Edit comment with subpage link (bug 14080) +!! options +comment +subpage +title=[[Subpage test]] +!! input +Poked at a [[/subpage]] here... +!! result +Poked at a <a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">/subpage</a> here... +!!end + +!! test +Edit comment with subpage link and link text (bug 14080) +!! options +comment +subpage +title=[[Subpage test]] +!! input +Poked at a [[/subpage|neat little page]] here... +!! result +Poked at a <a href="/wiki/Subpage_test/subpage" title="Subpage test/subpage">neat little page</a> here... +!!end + +!! test +Edit comment with bogus subpage link in non-subpage NS (bug 14080) +!! options +comment +title=[[Subpage test]] +!! input +Poked at a [[/subpage]] here... +!! result +Poked at a <a href="/index.php?title=/subpage&action=edit&redlink=1" class="new" title="/subpage (page does not exist)">/subpage</a> here... +!!end + +!! test +Edit comment with bare anchor link (local, as on diff) +!! options +comment +local +title=[[Main Page]] +!!input +[[#section]] +!! result +<a href="#section">#section</a> +!! end + +!! test +Edit comment with bare anchor link (non-local, as on history) +!! options +comment +title=[[Main Page]] +!!input +[[#section]] +!! result +<a href="/wiki/Main_Page#section" title="Main Page">#section</a> +!! end + +!! test +Space normalisation on autocomment (bug 22784) +!! options +comment +title=[[Main Page]] +!!input +/* __hello__world__ */ +!! result +<span class="autocomment"><a href="/wiki/Main_Page#hello_world" title="Main Page">→</a>__hello__world__</span> +!! end + +!! test +Bad images - basic functionality +!! input +[[File:Bad.jpg]] +!! result +!! end + +!! test +Bad images - bug 16039: text after bad image disappears +!! input +Foo bar +[[File:Bad.jpg]] +Bar foo +!! result +<p>Foo bar +</p><p>Bar foo +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) no displaytitle +!! options +showtitle +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=false +!! input +this is not the the title +!! result +Parser test +<p>this is not the the title +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) RestrictDisplayTitle=false +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=false +!! input +this is not the the title +{{DISPLAYTITLE:whatever}} +!! result +whatever +<p>this is not the the title +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) RestrictDisplayTitle=true mismatch +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=true +!! input +this is not the the title +{{DISPLAYTITLE:whatever}} +!! result +Screen +<p>this is not the the title +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) RestrictDisplayTitle=true matching +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=true +wgRestrictDisplayTitle=true +!! input +this is not the the title +{{DISPLAYTITLE:screen}} +!! result +screen +<p>this is not the the title +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) AllowDisplayTitle=false +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=false +!! input +this is not the the title +{{DISPLAYTITLE:screen}} +!! result +Screen +<p>this is not the the title +<a href="/index.php?title=Template:DISPLAYTITLE:screen&action=edit&redlink=1" class="new" title="Template:DISPLAYTITLE:screen (page does not exist)">Template:DISPLAYTITLE:screen</a> +</p> +!! end + +!! test +Verify that displaytitle works (bug #22501) AllowDisplayTitle=false no DISPLAYTITLE +!! options +showtitle +title=[[Screen]] +!! config +wgAllowDisplayTitle=false +!! input +this is not the the title +!! result +Screen +<p>this is not the the title +</p> +!! end + +!! test +preload: check <noinclude> and <includeonly> +!! options +preload +!! input +Hello <noinclude>cruel</noinclude><includeonly>kind</includeonly> world. +!! result +Hello kind world. +!! end + +!! test +preload: check <onlyinclude> +!! options +preload +!! input +Goodbye <onlyinclude>Hello world</onlyinclude> +!! result +Hello world +!! end + +!! test +preload: can pass tags through if we want to +!! options +preload +!! input +<includeonly><</includeonly>includeonly>Hello world<includeonly><</includeonly>/includeonly> +!! result +<includeonly>Hello world</includeonly> +!! end + +!! test +preload: check that it doesn't try to do tricks +!! options +preload +!! input +* <!-- Hello --> ''{{world}}'' {{<includeonly>subst:</includeonly>How are you}}{{ {{{|safesubst:}}} #if:1|2|3}} +!! result +* <!-- Hello --> ''{{world}}'' {{subst:How are you}}{{ {{{|safesubst:}}} #if:1|2|3}} +!! end + +!! test +Play a bit with r67090 and bug 3158 +!! options +disabled +!! input +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="border : solid;"> </div> +!! result +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="width:50% !important"> </div> +<div style="border : solid;"> </div> + +!! end + +!! test +HTML5 data attributes +!! input +<span data-foo="bar">Baz</span> +<p data-abc-def_hij="">Quuz</p> +!! result +<p><span data-foo="bar">Baz</span> +</p> +<p data-abc-def_hij="">Quuz</p> + +!! end + + +TODO: +more images +more tables +math +character entities +and much more +Try for 100% code coverage diff --git a/maintenance/tests/parser/parserTestsParserHook.php b/maintenance/tests/parser/parserTestsParserHook.php new file mode 100644 index 00000000..6387208a --- /dev/null +++ b/maintenance/tests/parser/parserTestsParserHook.php @@ -0,0 +1,46 @@ +<?php +/** + * A basic extension that's used by the parser tests to test whether input and + * arguments are passed to extensions properly. + * + * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + */ + +class ParserTestParserHook { + + static function setup( &$parser ) { + $parser->setHook( 'tag', array( __CLASS__, 'hook' ) ); + + return true; + } + + static function hook( $in, $argv ) { + ob_start(); + var_dump( + $in, + $argv + ); + $ret = ob_get_clean(); + + return "<pre>\n$ret</pre>"; + } +} diff --git a/maintenance/tests/parser/parserTestsStaticParserHook.php b/maintenance/tests/parser/parserTestsStaticParserHook.php new file mode 100644 index 00000000..72f82276 --- /dev/null +++ b/maintenance/tests/parser/parserTestsStaticParserHook.php @@ -0,0 +1,58 @@ +<?php +/** + * A basic extension that's used by the parser tests to test whether the parser + * calls extensions when they're called inside comments, it shouldn't do that + * + * Copyright © 2005, 2006 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + */ + +class ParserTestStaticParserHook { + static function setup( &$parser ) { + $parser->setHook( 'statictag', array( __CLASS__, 'hook' ) ); + + return true; + } + + static function hook( $in, $argv, $parser ) { + if ( ! count( $argv ) ) { + $parser->static_tag_buf = $in; + return ''; + } else if ( count( $argv ) === 1 && isset( $argv['action'] ) + && $argv['action'] === 'flush' && $in === null ) + { + // Clear the buffer, we probably don't need to + if ( isset( $parser->static_tag_buf ) ) { + $tmp = $parser->static_tag_buf; + } else { + $tmp = ''; + } + $parser->static_tag_buf = null; + return $tmp; + } else + // wtf? + return + "\nCall this extension as <statictag>string</statictag> or as" . + " <statictag action=flush/>, not in any other way.\n" . + "text: " . var_export( $in, true ) . "\n" . + "argv: " . var_export( $argv, true ) . "\n"; + } +} diff --git a/maintenance/tests/parserTests.php b/maintenance/tests/parserTests.php new file mode 100644 index 00000000..7793e6b8 --- /dev/null +++ b/maintenance/tests/parserTests.php @@ -0,0 +1,92 @@ +<?php +/** + * MediaWiki parser test suite + * + * Copyright © 2004 Brion Vibber <brion@pobox.com> + * http://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Maintenance + */ + +$options = array( 'quick', 'color', 'quiet', 'help', 'show-output', 'record', 'run-disabled' ); +$optionsWithArgs = array( 'regex', 'seed', 'setversion' ); + +require_once( dirname( __FILE__ ) . '/../commandLine.inc' ); + +if ( isset( $options['help'] ) ) { + echo <<<ENDS +MediaWiki $wgVersion parser test suite +Usage: php parserTests.php [options...] + +Options: + --quick Suppress diff output of failed tests + --quiet Suppress notification of passed tests (shows only failed tests) + --show-output Show expected and actual output + --color[=yes|no] Override terminal detection and force color output on or off + use wgCommandLineDarkBg = true; if your term is dark + --regex Only run tests whose descriptions which match given regex + --file=<testfile> Run test cases from a custom file instead of parserTests.txt + --record Record tests in database + --compare Compare with recorded results, without updating the database. + --setversion When using --record, set the version string to use (useful + with git-svn so that you can get the exact revision) + --keep-uploads Re-use the same upload directory for each test, don't delete it + --fuzz Do a fuzz test instead of a normal test + --seed <n> Start the fuzz test from the specified seed + --help Show this help message + --run-disabled run disabled tests + --upload Upload test results to remote wiki (per \$wgParserTestRemote) + +ENDS; + exit( 0 ); +} + +# Cases of weird db corruption were encountered when running tests on earlyish +# versions of SQLite +if ( $wgDBtype == 'sqlite' ) { + $db = wfGetDB( DB_MASTER ); + $version = $db->getServerVersion(); + if ( version_compare( $version, '3.6' ) < 0 ) { + die( "Parser tests require SQLite version 3.6 or later, you have $version\n" ); + } +} + +# There is a convention that the parser should never +# refer to $wgTitle directly, but instead use the title +# passed to it. +$wgTitle = Title::newFromText( 'Parser test script do not use' ); +$tester = new ParserTest($options); + +if ( isset( $options['file'] ) ) { + $files = array( $options['file'] ); +} else { + // Default parser tests and any set from extensions or local config + $files = $wgParserTestFiles; +} + +# Print out software version to assist with locating regressions +$version = SpecialVersion::getVersion(); +echo( "This is MediaWiki version {$version}.\n\n" ); + +if ( isset( $options['fuzz'] ) ) { + $tester->fuzzTest( $files ); +} else { + $ok = $tester->runTestsFromFiles( $files ); + exit ( $ok ? 0 : 1 ); +} diff --git a/maintenance/tests/phpunit.xml b/maintenance/tests/phpunit.xml deleted file mode 100644 index ce7d44f5..00000000 --- a/maintenance/tests/phpunit.xml +++ /dev/null @@ -1,17 +0,0 @@ -<!-- See http://www.phpunit.de/manual/3.3/en/appendixes.configuration.html --> -<phpunit bootstrap="./bootstrap.php" - colors="false" - stopOnFailure="false"> - <!-- convertErrorsToExceptions="true" --> - <!-- convertNoticesToExceptions="true" --> - <!-- convertWarningsToExceptions="true" --> - <testsuite name="MediaWiki Test Suite"> - <directory>.</directory> - </testsuite> - <groups> - <exclude> - <group>Broken</group> - <group>Stub</group> - </exclude> - </groups> -</phpunit>
\ No newline at end of file diff --git a/maintenance/tests/selenium/Selenium.php b/maintenance/tests/selenium/Selenium.php new file mode 100644 index 00000000..ecf7f9ec --- /dev/null +++ b/maintenance/tests/selenium/Selenium.php @@ -0,0 +1,190 @@ +<?php +/** + * Selenium connector + * This is implemented as a singleton. + */ + +require( 'Testing/Selenium.php' ); + +class Selenium { + protected static $_instance = null; + + public $isStarted = false; + public $tester; + + protected $port; + protected $host; + protected $browser; + protected $browsers; + protected $logger; + protected $user; + protected $pass; + protected $timeout = 30000; + protected $verbose; + protected $junitlogfile; //processed by phpUnderControl + protected $runagainstgrid = false; + + /** + * @todo this shouldn't have to be static + */ + static protected $url; + + /** + * Override parent + */ + public function __construct() { + /** + * @todo this is an ugly hack to make information available to + * other tests. It should be fixed. + */ + if ( null === self::$_instance ) { + self::$_instance = $this; + } else { + throw new MWException( "Already have one Selenium instance." ); + } + } + + public function start() { + $this->tester = new Testing_Selenium( $this->browser, self::$url, $this->host, + $this->port, $this->timeout ); + if ( method_exists( $this->tester, "setVerbose" ) ) $this->tester->setVerbose( $this->verbose ); + + $this->tester->start(); + $this->isStarted = true; + } + + public function stop() { + $this->tester->stop(); + $this->tester = null; + $this->isStarted = false; + } + + public function login() { + if ( strlen( $this->user ) == 0 ) { + return; + } + $this->open( self::$url . '/index.php?title=Special:Userlogin' ); + $this->type( 'wpName1', $this->user ); + $this->type( 'wpPassword1', $this->pass ); + $this->click( "//input[@id='wpLoginAttempt']" ); + $this->waitForPageToLoad( 10000 ); + + // after login we redirect to the main page. So check whether the "Prefernces" top menu item exists + $value = $this->isElementPresent( "//li[@id='pt-preferences']" ); + + if ( $value != true ) { + throw new Testing_Selenium_Exception( "Login Failed" ); + } + + } + + public static function getInstance() { + if ( null === self::$_instance ) { + throw new MWException( "No instance set yet" ); + } + + return self::$_instance; + } + + public function loadPage( $title, $action ) { + $this->open( self::$url . '/index.php?title=' . $title . '&action=' . $action ); + } + + public function setLogger( $logger ) { + $this->logger = $logger; + } + + public function getLogger( ) { + return $this->logger; + } + + public function log( $message ) { + $this->logger->write( $message ); + } + + public function setUrl( $url ) { + self::$url = $url; + } + + static public function getUrl() { + return self::$url; + } + + public function setPort( $port ) { + $this->port = $port; + } + + public function getPort() { + return $this->port; + } + + public function setUser( $user ) { + $this->user = $user; + } + + // Function to get username + public function getUser() { + return $this->user; + } + + + public function setPass( $pass ) { + $this->pass = $pass; + } + + //add function to get password + public function getPass( ) { + return $this->pass; + } + + + public function setHost( $host ) { + $this->host = $host; + } + + public function setVerbose( $verbose ) { + $this->verbose = $verbose; + } + + public function setAvailableBrowsers( $availableBrowsers ) { + $this->browsers = $availableBrowsers; + } + + public function setJUnitLogfile( $junitlogfile ) { + $this->junitlogfile = $junitlogfile; + } + + public function getJUnitLogfile( ) { + return $this->junitlogfile; + } + + public function setRunAgainstGrid( $runagainstgrid ) { + $this->runagainstgrid = $runagainstgrid; + } + + public function setBrowser( $b ) { + if ($this->runagainstgrid) { + $this->browser = $b; + return true; + } + if ( !isset( $this->browsers[$b] ) ) { + throw new MWException( "Invalid Browser: $b.\n" ); + } + + $this->browser = $this->browsers[$b]; + } + + public function getAvailableBrowsers() { + return $this->browsers; + } + + public function __call( $name, $args ) { + $t = call_user_func_array( array( $this->tester, $name ), $args ); + return $t; + } + + // Prevent external cloning + protected function __clone() { } + // Prevent external construction + // protected function __construct() {} +} diff --git a/maintenance/tests/selenium/SeleniumConfig.php b/maintenance/tests/selenium/SeleniumConfig.php new file mode 100644 index 00000000..ca69b1f0 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumConfig.php @@ -0,0 +1,88 @@ +<?php +if ( !defined( 'SELENIUMTEST' ) ) { + die( 1 ); +} + +class SeleniumConfig { + + /* + * Retreives the Selenium configuration values from an ini file. + * See sample config file in selenium_settings.ini.sample + * + */ + + public static function getSeleniumSettings ( &$seleniumSettings, + &$seleniumBrowsers, + &$seleniumTestSuites, + $seleniumConfigFile = null ) { + if ( strlen( $seleniumConfigFile ) == 0 ) { + global $wgSeleniumConfigFile; + if ( isset( $wgSeleniumConfigFile ) ) $seleniumConfigFile = $wgSeleniumConfigFile ; + } + + if ( strlen( $seleniumConfigFile ) == 0 || !file_exists( $seleniumConfigFile ) ) { + throw new MWException( "Unable to read local Selenium Settings from " . $seleniumConfigFile . "\n" ); + } + + if ( !defined( 'PHP_VERSION_ID' ) || + ( PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION < 3 ) ) { + $configArray = self::parse_5_2_ini_file( $seleniumConfigFile ); + } else { + $configArray = parse_ini_file( $seleniumConfigFile, true ); + } + if ( $configArray === false ) { + throw new MWException( "Error parsing " . $seleniumConfigFile . "\n" ); + } + + if ( array_key_exists( 'SeleniumSettings', $configArray) ) { + wfSuppressWarnings(); + //we may need to change how this is set. But for now leave it in the ini file + $seleniumBrowsers = $configArray['SeleniumSettings']['browsers']; + + $seleniumSettings['host'] = $configArray['SeleniumSettings']['host']; + $seleniumSettings['port'] = $configArray['SeleniumSettings']['port']; + $seleniumSettings['wikiUrl'] = $configArray['SeleniumSettings']['wikiUrl']; + $seleniumSettings['username'] = $configArray['SeleniumSettings']['username']; + $seleniumSettings['userPassword'] = $configArray['SeleniumSettings']['userPassword']; + $seleniumSettings['testBrowser'] = $configArray['SeleniumSettings']['testBrowser']; + $seleniumSettings['startserver'] = $configArray['SeleniumSettings']['startserver']; + $seleniumSettings['stopserver'] = $configArray['SeleniumSettings']['stopserver']; + $seleniumSettings['seleniumserverexecpath'] = $configArray['SeleniumSettings']['seleniumserverexecpath']; + $seleniumSettings['jUnitLogFile'] = $configArray['SeleniumSettings']['jUnitLogFile']; + $seleniumSettings['runAgainstGrid'] = $configArray['SeleniumSettings']['runAgainstGrid']; + + wfRestoreWarnings(); + } + if ( array_key_exists( 'SeleniumTests', $configArray) ) { + wfSuppressWarnings(); + $seleniumTestSuites = $configArray['SeleniumTests']['testSuite']; + wfRestoreWarnings(); + } + return true; + } + + private static function parse_5_2_ini_file ( $ConfigFile ) { + + $configArray = parse_ini_file( $ConfigFile, true ); + if ( $configArray === false ) return false; + + // PHP 5.2 ini files have [browsers] and [testSuite] sections + // to get around lack of support for array keys. It then + // inserts the section arrays into the appropriate places in + // the SeleniumSettings and SeleniumTests arrays. + + if ( isset( $configArray['browsers'] ) ) { + $configArray['SeleniumSettings']['browsers'] = $configArray['browsers']; + unset ( $configArray['browsers'] ); + } + + if ( isset( $configArray['testSuite'] ) ) { + $configArray['SeleniumTests']['testSuite'] = $configArray['testSuite']; + unset ( $configArray['testSuite'] ); + } + + return $configArray; + + } + +} diff --git a/maintenance/tests/selenium/SeleniumLoader.php b/maintenance/tests/selenium/SeleniumLoader.php new file mode 100644 index 00000000..8d5e7713 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumLoader.php @@ -0,0 +1,9 @@ +<?php + +class SeleniumLoader { + static function load() { + require_once( 'Testing/Selenium.php' ); + require_once( 'PHPUnit/Framework.php' ); + require_once( 'PHPUnit/Extensions/SeleniumTestCase.php' ); + } +} diff --git a/maintenance/tests/selenium/SeleniumServerManager.php b/maintenance/tests/selenium/SeleniumServerManager.php new file mode 100644 index 00000000..ae5ea682 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumServerManager.php @@ -0,0 +1,239 @@ +<?php +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + */ + +class SeleniumServerManager { + private $SeleniumStartServer = false; + private $OS = ''; + private $SeleniumServerPid = 'NaN'; + private $SeleniumServerPort = 4444; + private $SeleniumServerStartTimeout = 10; // 10 secs. + private $SeleniumServerExecPath; + + public function __construct( $startServer, + $serverPort, + $serverExecPath ) { + $this->OS = (string) PHP_OS; + if ( isset( $startServer ) ) + $this->SeleniumStartServer = $startServer; + if ( isset( $serverPort ) ) + $this->SeleniumServerPort = $serverPort; + if ( isset( $serverExecPath ) ) + $this->SeleniumServerExecPath = $serverExecPath; + return; + } + + // Getters for certain private attributes. No setters, since they + // should not change after the manager object is created. + + public function getSeleniumStartServer() { + return $this->SeleniumStartServer; + } + + public function getSeleniumServerPort() { + return $this->SeleniumServerPort; + } + + public function getSeleniumServerPid() { + return $this->SeleniumServerPid; + } + + // Changing value of SeleniumStartServer allows starting server after + // creation of the class instance. Only allow setting SeleniumStartServer + // to true, since after server is started, it is shut down by stop(). + + public function setSeleniumStartServer( $startServer ) { + if ( $startServer == true ) $this->SeleniumStartServer = true; + } + + // return values are: 1) started - server started, 2) failed - + // server not started, 3) running - instructed to start server, but + // server already running + + public function start() { + + if ( !$this->SeleniumStartServer ) return 'failed'; + + // commented out cases are untested + + switch ( $this->OS ) { + case "Linux": +# case' CYGWIN_NT-5.1': + case 'Darwin': +# case 'FreeBSD': +# case 'HP-UX': +# case 'IRIX64': +# case 'NetBSD': +# case 'OpenBSD': +# case 'SunOS': +# case 'Unix': + // *nix based OS + return $this->startServerOnUnix(); + break; + case "Windows": + case "WIN32": + case "WINNT": + // Windows + return $this->startServerOnWindows(); + break; + default: + // An untested OS + return 'failed'; + break; + } + } + + public function stop() { + + // commented out cases are untested + + switch ( $this->OS ) { + case "Linux": +# case' CYGWIN_NT-5.1': + case 'Darwin': +# case 'FreeBSD': +# case 'HP-UX': +# case 'IRIX64': +# case 'NetBSD': +# case 'OpenBSD': +# case 'SunOS': +# case 'Unix': + // *nix based OS + return $this->stopServerOnUnix(); + break; + case "Windows": + case "WIN32": + case "WINNT": + // Windows + return $this->stopServerOnWindows(); + break; + default: + // An untested OS + return 'failed'; + break; + } + } + + private function startServerOnUnix() { + + $output = array(); + $user = $_ENV['USER']; + // @fixme this should be a little more generalized :) + if (PHP_OS == 'Darwin') { + // Mac OS X's ps barfs on the 'w' param, but doesn't need it. + $ps = "ps -U %s"; + } else { + // Good on Linux + $ps = "ps -U %s w"; + } + $psCommand = sprintf($ps, escapeshellarg($user)); + exec($psCommand . " | grep -i selenium-server", $output); + + // Start server. If there is already a server running, + // return running. + + if ( isset( $this->SeleniumServerExecPath ) ) { + $found = 0; + foreach ( $output as $string ) { + $found += preg_match( + '~^(.*)java(.+)-jar(.+)selenium-server~', + $string ); + } + if ( $found == 0 ) { + + // Didn't find the selenium server. Start it up. + // First set up comamand line suffix. + // NB: $! is pid of last job run in background + // The echo guarentees it is put into $op when + // the exec command is run. + + $commandSuffix = ' > /dev/null 2>&1'. ' & echo $!'; + $portText = ' -port ' . $this->SeleniumServerPort; + $command = "java -jar " . + escapeshellarg($this->SeleniumServerExecPath) . + $portText . $commandSuffix; + exec($command ,$op); + $pid = (int)$op[0]; + if ( $pid != "" ) + $this->SeleniumServerPid = $pid; + else { + $this->SeleniumServerPid = 'NaN'; + // Server start failed. + return 'failed'; + } + // Wait for the server to startup and listen + // on its port. Note: this solution kinda + // stinks, since it uses a wait loop - dnessett + + wfSuppressWarnings(); + for ( $cnt = 1; + $cnt <= $this->SeleniumServerStartTimeout; + $cnt++ ) { + $fp = fsockopen ( 'localhost', + $this->SeleniumServerPort, + $errno, $errstr, 0 ); + if ( !$fp ) { + sleep( 1 ); + continue; + // Server start succeeded. + } else { + fclose ( $fp ); + return 'started'; + } + } + wfRestoreWarnings(); + echo ( "Starting Selenium server timed out.\n" ); + return 'failed'; + } + // server already running. + else return 'running'; + + } + // No Server execution path defined. + return 'failed'; + } + + private function startServerOnWindows() { + // Unimplemented. + return 'failed'; + } + + private function stopServerOnUnix() { + + if ( !empty( $this->SeleniumServerPid ) && + $this->SeleniumServerPid != 'NaN' ) { + exec( "kill -9 " . $this->SeleniumServerPid ); + return 'stopped'; + } + else return 'failed'; + } + + private function stopServerOnWindows() { + // Unimplemented. + return 'failed'; + + } +} diff --git a/maintenance/tests/selenium/SeleniumTestCase.php b/maintenance/tests/selenium/SeleniumTestCase.php new file mode 100644 index 00000000..11e1b192 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestCase.php @@ -0,0 +1,103 @@ +<?php +class SeleniumTestCase extends PHPUnit_Framework_TestCase { // PHPUnit_Extensions_SeleniumTestCase + protected $selenium; + + public function setUp() { + set_time_limit( 60 ); + $this->selenium = Selenium::getInstance(); + } + + public function tearDown() { + + } + + public function __call( $method, $args ) { + return call_user_func_array( array( $this->selenium, $method ), $args ); + } + + public function assertSeleniumAttributeEquals( $attribute, $value ) { + $attr = $this->getAttribute( $attribute ); + $this->assertEquals( $attr, $value ); + } + + public function assertSeleniumHTMLContains( $element, $text ) { + $innerHTML = $this->getText( $element ); + // or assertContains + $this->assertRegExp( "/$text/", $innerHTML ); + } + +//Common Funtions Added for Selenium Tests + + public function getExistingPage(){ + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type("searchInput", "new" ); + $this->click("searchGoButton"); + $this->waitForPageToLoad("30000"); + } + + public function getNewPage($pageName){ + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type("searchInput", $pageName ); + $this->click("searchGoButton"); + $this->waitForPageToLoad("30000"); + $this->click("link=".$pageName); + $this->waitForPageToLoad("600000"); + + + } + // Loading the mediawiki editor + public function loadWikiEditor(){ + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + } + + // Clear the content of the mediawiki editor + public function clearWikiEditor(){ + $this->type("wpTextbox1", ""); + } + + // Click on the 'Show preview' button of the mediawiki editor + public function clickShowPreviewBtn(){ + $this->click("wpPreview"); + } + + // Click on the 'Save Page' button of the mediawiki editor + public function clickSavePageBtn(){ + $this->click("wpSave"); + } + + // Click on the 'Edit' link + public function clickEditLink(){ + $this->click("link=Edit"); + $this->waitForPageToLoad("30000"); + } + + public function deletePage($pageName){ + $isLinkPresent = False; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click("link=Log out"); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "wpName1", "nadeesha" ); + $this->type( "wpPassword1", "12345" ); + $this->click( "wpLoginAttempt" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "searchInput", $pageName ); + $this->click( "searchGoButton"); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Delete" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "wpConfirmB" ); + $this->waitForPageToLoad( "30000" ); + + } + + + +} diff --git a/maintenance/tests/selenium/SeleniumTestConsoleLogger.php b/maintenance/tests/selenium/SeleniumTestConsoleLogger.php new file mode 100644 index 00000000..b6f5496c --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestConsoleLogger.php @@ -0,0 +1,25 @@ +<?php + +class SeleniumTestConsoleLogger { + public function __construct() { + // Prepare testsuite for immediate output + @ini_set( 'zlib.output_compression', 0 ); + @ini_set( 'implicit_flush', 1 ); + for ( $i = 0; $i < ob_get_level(); $i++ ) { + ob_end_flush(); + } + ob_implicit_flush( 1 ); + } + + public function write( $message, $mode = false ) { + $out = ''; + // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '<font color="green">'; + $out .= htmlentities( $message ); + // if ( $mode == SeleniumTestSuite::RESULT_OK ) $out .= '</font>'; + if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { + $out .= "\n"; + } + + echo $out; + } +} diff --git a/maintenance/tests/selenium/SeleniumTestHTMLLogger.php b/maintenance/tests/selenium/SeleniumTestHTMLLogger.php new file mode 100644 index 00000000..21332cf0 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestHTMLLogger.php @@ -0,0 +1,36 @@ +<?php + +class SeleniumTestHTMLLogger { + public function setHeaders() { + global $wgOut; + $wgOut->addHeadItem( 'selenium', '<style type="text/css"> + .selenium pre { + overflow-x: auto; /* Use horizontal scroller if needed; for Firefox 2, not needed in Firefox 3 */ + white-space: pre-wrap; /* css-3 */ + white-space: -moz-pre-wrap !important; /* Mozilla, since 1999 */ + white-space: -pre-wrap; /* Opera 4-6 */ + white-space: -o-pre-wrap; /* Opera 7 */ + /* width: 99%; */ + word-wrap: break-word; /* Internet Explorer 5.5+ */ + } + .selenium-success { color: green } + </style>' ); + } + + public function write( $message, $mode = false ) { + global $wgOut; + $out = ''; + if ( $mode == SeleniumTestSuite::RESULT_OK ) { + $out .= '<span class="selenium-success">'; + } + $out .= htmlspecialchars( $message ); + if ( $mode == SeleniumTestSuite::RESULT_OK ) { + $out .= '</span>'; + } + if ( $mode != SeleniumTestSuite::CONTINUE_LINE ) { + $out .= '<br />'; + } + + $wgOut->addHTML( $out ); + } +} diff --git a/maintenance/tests/selenium/SeleniumTestListener.php b/maintenance/tests/selenium/SeleniumTestListener.php new file mode 100644 index 00000000..9436f672 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestListener.php @@ -0,0 +1,68 @@ +<?php + +class SeleniumTestListener implements PHPUnit_Framework_TestListener { + private $logger; + private $tests_ok = 0; + private $tests_failed = 0; + + public function __construct( $loggerInstance ) { + $this->logger = $loggerInstance; + } + + public function addError( PHPUnit_Framework_Test $test, Exception $e, $time ) { + $this->logger->write( 'Error: ' . $e->getMessage() ); + $this->tests_failed++; + } + + public function addFailure( PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time ) + { + $this->logger->write( 'Failed: ' . $e->getMessage() ); + $this->tests_failed++; + } + + public function addIncompleteTest( PHPUnit_Framework_Test $test, Exception $e, $time ) + { + $this->logger->write( 'Incomplete.' ); + $this->tests_failed++; + } + + public function addSkippedTest( PHPUnit_Framework_Test $test, Exception $e, $time ) + { + $this->logger->write( 'Skipped.' ); + $this->tests_failed++; + } + + public function startTest( PHPUnit_Framework_Test $test ) { + $this->logger->write( + 'Testing ' . $test->getName() . ' ... ', + SeleniumTestSuite::CONTINUE_LINE + ); + } + + public function endTest( PHPUnit_Framework_Test $test, $time ) { + if ( !$test->hasFailed() ) { + $this->logger->write( 'OK', SeleniumTestSuite::RESULT_OK ); + $this->tests_ok++; + } + } + + public function startTestSuite( PHPUnit_Framework_TestSuite $suite ) { + $this->logger->write( 'Testsuite ' . $suite->getName() . ' started.' ); + $this->tests_ok = 0; + $this->tests_failed = 0; + } + + public function endTestSuite( PHPUnit_Framework_TestSuite $suite ) { + $this->logger->write('Testsuite ' . $suite->getName() . ' ended.' ); + if ( $this->tests_ok > 0 || $this->tests_failed > 0 ) { + $this->logger->write( ' OK: ' . $this->tests_ok . ' Failed: ' . $this->tests_failed ); + } + $this->tests_ok = 0; + $this->tests_failed = 0; + } + + public function statusMessage( $message ) { + $this->logger->write( $message ); + } +} + diff --git a/maintenance/tests/selenium/SeleniumTestSuite.php b/maintenance/tests/selenium/SeleniumTestSuite.php new file mode 100644 index 00000000..ba178051 --- /dev/null +++ b/maintenance/tests/selenium/SeleniumTestSuite.php @@ -0,0 +1,46 @@ +<?php + +abstract class SeleniumTestSuite extends PHPUnit_Framework_TestSuite { + private $selenium; + private $isSetUp = false; + private $loginBeforeTests = true; + + // Do not add line break after test output + const CONTINUE_LINE = 1; + const RESULT_OK = 2; + const RESULT_ERROR = 3; + + public abstract function addTests(); + + public function setUp() { + // Hack because because PHPUnit version 3.0.6 which is on prototype does not + // run setUp as part of TestSuite::run + if ( $this->isSetUp ) { + return; + } + $this->isSetUp = true; + $this->selenium = Selenium::getInstance(); + $this->selenium->start(); + $this->selenium->open( $this->selenium->getUrl() . '/index.php?setupTestSuite=' . $this->getName() ); + if ( $this->loginBeforeTests ) { + $this->login(); + } + } + + public function tearDown() { + $this->selenium->open( $this->selenium->getUrl() . '/index.php?clearTestSuite=' . $this->getName() ); + $this->selenium->stop(); + } + + public function login() { + $this->selenium->login(); + } + + public function loadPage( $title, $action ) { + $this->selenium->loadPage( $title, $action ); + } + + protected function setLoginBeforeTests( $loginBeforeTests = true ) { + $this->loginBeforeTests = $loginBeforeTests; + } +} diff --git a/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png b/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png Binary files differnew file mode 100644 index 00000000..70385243 --- /dev/null +++ b/maintenance/tests/selenium/data/Wikipedia-logo-v2-de.png diff --git a/maintenance/tests/selenium/selenium_settings.ini.php52.sample b/maintenance/tests/selenium/selenium_settings.ini.php52.sample new file mode 100644 index 00000000..ad21037e --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings.ini.php52.sample @@ -0,0 +1,23 @@ +[browsers] + +firefox = "*firefox" +iexploreproxy = "*iexploreproxy" +chrome = "*chrome" + +[SeleniumSettings] + +host = "localhost" +port = "4444" +wikiUrl = "http://localhost/mediawiki/latest_trunk/trunk/phase3" +username = "Wikiadmin" +userPassword = "Wikiadminpw" +testBrowser = "firefox" +startserver = +stopserver = +jUnitLogFile = +runAgainstGrid = false + +[testSuite] + +SimpleSeleniumTestSuite = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php" +WikiEditorTestSuite = "extensions/WikiEditor/selenium/WikiEditorTestSuite.php" diff --git a/maintenance/tests/selenium/selenium_settings.ini.sample b/maintenance/tests/selenium/selenium_settings.ini.sample new file mode 100644 index 00000000..bacc0a90 --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings.ini.sample @@ -0,0 +1,32 @@ +[SeleniumSettings] + +; Set up the available browsers that Selenium can control. +browsers[firefox] = "*firefox" +browsers[iexplorer] = "*iexploreproxy" +browsers[chrome] = "*chrome" + +; The simple configurations above usually work on Linux, but Windows and +; Mac OS X hosts may need to specify a full path: +;browsers[firefox] = "*firefox /Applications/Firefox.app/Contents/MacOS/firefox-bin" +;browsers[firefox] = "*firefox C:\Program Files\Mozilla Firefox\firefox.exe" + +host = "localhost" +port = "4444" +wikiUrl = "http://localhost/deployment" +username = "wikiuser" +userPassword = "wikipass" +testBrowser = "firefox" +startserver = +stopserver = +jUnitLogFile = +runAgainstGrid = false + +; To let the test runner start and stop the selenium server, it needs the full +; path to selenium-server.jar from the selenium-remote-control package. +seleniumserverexecpath = "/opt/local/selenium-remote-control-1.0.3/selenium-server-1.0.3/selenium-server.jar" + +[SeleniumTests] + +testSuite[SimpleSeleniumTestSuite] = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php" +testSuite[WikiEditorTestSuite] = "extensions/WikiEditor/selenium/WikiEditorTestSuite.php" + diff --git a/maintenance/tests/selenium/selenium_settings_grid.ini.sample b/maintenance/tests/selenium/selenium_settings_grid.ini.sample new file mode 100644 index 00000000..eca60b0a --- /dev/null +++ b/maintenance/tests/selenium/selenium_settings_grid.ini.sample @@ -0,0 +1,14 @@ +[SeleniumSettings] + +host = "grid.tesla.usability.wikimedia.org" +port = "4444" +wikiUrl = "http://208.80.152.253:5001" +username = "wikiuser" +userPassword = "wikipass" +testBrowser = "Safari on OS X Snow Leopard" +jUnitLogFile = +runAgainstGrid = true + +[testSuite] + +SimpleSeleniumTestSuite = "maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php"
\ No newline at end of file diff --git a/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php b/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php new file mode 100644 index 00000000..dd4bc005 --- /dev/null +++ b/maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php @@ -0,0 +1,182 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class AddContentToNewPageTestCase extends SeleniumTestCase { + + + // Add bold text and verify output + public function testAddBoldText() { + + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-bold']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify bold text displayed on mediawiki preview + $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/p/b" )); + $this->assertTrue($this->isTextPresent( "Bold text" )); + } + + // Add italic text and verify output + public function testAddItalicText() { + + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-italic']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify italic text displayed on mediawiki preview + $this->assertTrue($this->isElementPresent("//div[@id='wikiPreview']/p/i")); + $this->assertTrue($this->isTextPresent( "Italic text" )); + } + + // Add internal link for a new page and verify output in the preview + public function testAddInternalLinkNewPage() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-link']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify internal link displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $correct = strstr( $source, "Link title" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Link title" ); + $this->waitForPageToLoad( "600000" ); + + // Verify internal link open as a new page - editing mode + $source = $this->getText( "firstHeading" ); + $correct = strstr( $source, "Editing Link title" ); + $this->assertEquals( $correct, true ); + } + + // Add external link and verify output in the preview + public function testAddExternalLink() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-extlink']" ); + $this->type( "wpTextbox1", "[http://www.google.com Google]" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify external links displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $correct = strstr( $source, "Google" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Google" ); + $this->waitForPageToLoad( "600000" ); + + // Verify external link opens + $source = $this->getTitle(); + $correct = strstr( $source, "Google" ); + $this->assertEquals( $correct, true); + } + + // Add level 2 headline and verify output in the preview + public function testAddLevel2HeadLine() { + $blnElementPresent = False; + $blnTextPresent = False; + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-headline" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + $this->assertTrue($this->isElementPresent( "//div[@id='wikiPreview']/h2" )); + + // Verify level 2 headline displayed on mediawiki preview + $source = $this->getText( "//*[@id='Headline_text']" ); + $correct = strstr( $source, "Headline text" ); + $this->assertEquals( $correct, true ); + } + + // Add text with ignore wiki format and verify output the preview + public function testAddNoWikiFormat() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "//*[@id='mw-editbutton-nowiki']" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify ignore wiki format text displayed on mediawiki preview + $source = $this->getText( "//div[@id='wikiPreview']/p" ); + $correct = strstr( $source, "Insert non-formatted text here" ); + $this->assertEquals( $correct, true ); + } + + // Add signature and verify output in the preview + public function testAddUserSignature() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-signature" ); + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify signature displayed on mediawiki preview + $source = $this->getText( "//*[@id='wikiPreview']/p/a" ); + $username = $this->getText( "//*[@id='pt-userpage']/a" ); + $correct = strstr( $source, $username ); + $this->assertEquals( $correct, true ); + } + + // Add horizontal line and verify output in the preview + public function testHorizontalLine() { + $this->getExistingPage(); + $this->clickEditLink(); + $this->loadWikiEditor(); + $this->clearWikiEditor(); + $this->click( "mw-editbutton-hr" ); + + $this->clickShowPreviewBtn(); + $this->waitForPageToLoad( "600000" ); + + // Verify horizontal line displayed on mediawiki preview + $this->assertTrue( $this->isElementPresent( "//div[@id='wikiPreview']/hr" )); + $this->deletePage( "new" ); + } +} diff --git a/maintenance/tests/selenium/suites/AddNewPageTestCase.php b/maintenance/tests/selenium/suites/AddNewPageTestCase.php new file mode 100644 index 00000000..423f2a2c --- /dev/null +++ b/maintenance/tests/selenium/suites/AddNewPageTestCase.php @@ -0,0 +1,65 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class AddNewPageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testAddNewPage() { + $newPage = "new"; + $displayName = "New"; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "600000" ); + + // Verify 'Search results' text available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Search results" ); + $this->assertEquals( $correct, true); + + // Verify 'Create the page "<page name>" on this wiki' text available + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr ( $source, "Create the page \"New\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + + $this->assertTrue($this->isElementPresent( "link=Create" )); + $this->type( "wpTextbox1", "add new test page" ); + $this->click( "wpSave" ); + + // Verify new page added + $source = $this->gettext( "firstHeading" ); + $correct = strstr ( $source, $displayName ); + $this->assertEquals( $correct, true ); + } +} diff --git a/maintenance/tests/selenium/suites/CreateAccountTestCase.php b/maintenance/tests/selenium/suites/CreateAccountTestCase.php new file mode 100644 index 00000000..1cfda2e0 --- /dev/null +++ b/maintenance/tests/selenium/suites/CreateAccountTestCase.php @@ -0,0 +1,114 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +Class CreateAccountTestCase extends SeleniumTestCase { + + // Change these values before run the test + private $userName = "yourname4000"; + private $password = "yourpass4000"; + + // Verify 'Log in/create account' link existance in Main page. + public function testMainPageLink() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + $this->assertTrue($this->isElementPresent( "link=Log in / create account" )); + } + + // Verify 'Create an account' link existance in 'Log in / create account' Page. + public function testCreateAccountPageLink() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->assertTrue($this->isElementPresent( "link=Create an account" )); + } + + // Verify Create account + public function testCreateAccount() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Create an account" ); + $this->waitForPageToLoad( "30000" ); + + // Verify for blank user name + $this->type( "wpName2", "" ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + // Verify for invalid user name + $this->type( "wpName2", "@" ); + $this->click("wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + // start of test for blank password + $this->type( "wpName2", $this->userName); + $this->type( "wpPassword2", "" ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n Passwords must be at least 1 character.", + $this->getText("//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName2", $this->userName ); + $this->type( "wpPassword2", $this->password ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n The passwords you entered do not match.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName2", $this->userName ); + $this->type( "wpPassword2", $this->password ); + $this->type( "wpRetype", $this->password ); + $this->click( "wpCreateaccount" ); + $this->waitForPageToLoad( "30000 "); + + // Verify successful account creation for valid combination of 'Username', 'Password', 'Retype password' + $this->assertEquals( "Welcome, ".ucfirst( $this->userName )."!", + $this->getText( "Welcome,_".ucfirst( $this->userName )."!" )); + } +} + diff --git a/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php b/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php new file mode 100644 index 00000000..40628986 --- /dev/null +++ b/maintenance/tests/selenium/suites/DeletePageAdminTestCase.php @@ -0,0 +1,89 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class DeletePageAdminTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testDeletePage() { + + + $newPage = "new"; + $displayName = "New"; + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "60000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpName1", $this->selenium->getUser() ); + $this->type( "wpPassword1", $this->selenium->getPass() ); + $this->click( "wpLoginAttempt" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "searchInput", "new" ); + $this->click( "searchGoButton"); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Delete' link displayed + $source = $this->gettext( "link=Delete" ); + $correct = strstr ( $source, "Delete" ); + $this->assertEquals($correct, true ); + + $this->click( "link=Delete" ); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Delete' button available + $this->assertTrue($this->isElementPresent( "wpConfirmB" )); + + $this->click( "wpConfirmB" ); + $this->waitForPageToLoad( "30000" ); + + // Verify 'Action complete' text displayed + $source = $this->gettext( "firstHeading" ); + $correct = strstr ( $source, "Action complete" ); + $this->assertEquals( $correct, true ); + + // Verify '<Page Name> has been deleted. See deletion log for a record of recent deletions.' text displayed + $source = $this->gettext( "//div[@id='bodyContent']/p[1]" ); + $correct = strstr ( $source, "\"New\" has been deleted. See deletion log for a record of recent deletions." ); + $this->assertEquals( $correct, true ); + } +} diff --git a/maintenance/tests/selenium/suites/EmailPasswordTestCase.php b/maintenance/tests/selenium/suites/EmailPasswordTestCase.php new file mode 100644 index 00000000..8356f43a --- /dev/null +++ b/maintenance/tests/selenium/suites/EmailPasswordTestCase.php @@ -0,0 +1,81 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class EmailPasswordTestCase extends SeleniumTestCase { + + // change user name for each and every test (with in 24 hours) + private $userName = "test1"; + + public function testEmailPasswordButton() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + $this->assertTrue($this->isElementPresent( "wpMailmypassword" )); + } + + // Verify Email password functionality + public function testEmailPasswordMessages() { + + $this->click( "link=Log out" ); + $this->waitForPageToLoad( "30000" ); + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + // click Log in / create account link to open Log in / create account' page + $this->click( "link=Log in / create account" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpName1", "" ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n You have not specified a valid user name.", + $this->getText("//div[@id='bodyContent']/div[4]")); + + $this->type( "wpName1", $this->userName ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + + // Can not run on localhost + $this->assertEquals( "A new password has been sent to the e-mail address registered for ".ucfirst($this->userName).". Please log in again after you receive it.", + $this->getText("//div[@id='bodyContent']/div[4]" )); + + $this->type( "wpName1", $this->userName ); + $this->click( "wpMailmypassword" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Login error\n A password reminder has already been sent, within the last 24 hours. To prevent abuse, only one password reminder will be sent per 24 hours.", + $this->getText( "//div[@id='bodyContent']/div[4]" )); + } +} + diff --git a/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php b/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php new file mode 100644 index 00000000..205cb332 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikExtraTestSuite.php @@ -0,0 +1,20 @@ +<?php + +class MediaWikExtraTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/MyContributionsTestCase.php', + 'maintenance/tests/selenium/suites/MyWatchListTestCase.php', + 'maintenance/tests/selenium/suites/UserPreferencesTestCase.php', + 'maintenance/tests/selenium/suites/MovePageTestCase.php', + 'maintenance/tests/selenium/suites/PageSearchTestCase.php', + 'maintenance/tests/selenium/suites/EmailPasswordTestCase.php', + 'maintenance/tests/selenium/suites/CreateAccountTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } +} diff --git a/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php b/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php new file mode 100644 index 00000000..2803117d --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikiEditorConfig.php @@ -0,0 +1,47 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MediaWikiEditorConfig { + + public static function getSettings(&$includeFiles, &$globalConfigs) { + $includes = array( + //files that needed to be included would go here + 'maintenance/tests/selenium/suites/MediaWikiCommonFunction.php' + ); + $configs = array( + 'wgPageLoadTime' => "600000" + ); + $includeFiles = array_merge( $includeFiles, $includes ); + $globalConfigs = array_merge( $globalConfigs, $configs); + return true; + } +} + + + diff --git a/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php b/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php new file mode 100644 index 00000000..06046365 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediaWikiEditorTestSuite.php @@ -0,0 +1,18 @@ +<?php + +class MediaWikiEditorTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/AddNewPageTestCase.php', + 'maintenance/tests/selenium/suites/AddContentToNewPageTestCase.php', + 'maintenance/tests/selenium/suites/PreviewPageTestCase.php', + 'maintenance/tests/selenium/suites/SavePageTestCase.php', + ); + parent::addTestFiles( $testFiles ); + } +} + diff --git a/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php new file mode 100644 index 00000000..7b9525af --- /dev/null +++ b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php @@ -0,0 +1,69 @@ +<?php +/* + * Stub of tests be need as part of the hack-a-ton + */ +class MediawikiCoreSmokeTestCase extends SeleniumTestCase { + public function testUserLogin() { + + } + + public function testChangeUserPreference() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testCreateNewPageVector() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testEditExistingPageVector() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testCreateNewPageMonobook() { + + } + + /* + * TODO: generalize this test to be reusable for different skins + */ + public function testEditExistingPageMonobook() { + + } + + public function testImageUpload() { + $this->login(); + $this->open( $this->getUrl() . + '/index.php?title=Special:Upload' ); + $this->type( 'wpUploadFile', dirname( __FILE__ ) . + "\\..\\data\\Wikipedia-logo-v2-de.png" ); + $this->check( 'wpIgnoreWarning' ); + $this->click( 'wpUpload' ); + $this->waitForPageToLoad( 30000 ); + + $this->assertSeleniumHTMLContains( + '//h1[@class="firstHeading"]', "Wikipedia-logo-v2-de.png" ); + + /* + $this->open( $this->getUrl() . '/index.php?title=Image:' + . ucfirst( $this->filename ) . '&action=delete' ); + $this->type( 'wpReason', 'Remove test file' ); + $this->click( 'mw-filedelete-submit' ); + $this->waitForPageToLoad( 10000 ); + + // Todo: This message is localized + $this->assertSeleniumHTMLContains( '//div[@id="bodyContent"]/p', + ucfirst( $this->filename ) . '.*has been deleted.' ); + */ + } + + +} diff --git a/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php new file mode 100644 index 00000000..76287b23 --- /dev/null +++ b/maintenance/tests/selenium/suites/MediawikiCoreSmokeTestSuite.php @@ -0,0 +1,19 @@ +<?php +/* + * Stubs for now. We're going to start populating this test. + */ +class MediawikiCoreSmokeTestSuite extends SeleniumTestSuite +{ + public function setUp() { + $this->setLoginBeforeTests( false ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/MediawikiCoreSmokeTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/MovePageTestCase.php b/maintenance/tests/selenium/suites/MovePageTestCase.php new file mode 100644 index 00000000..d4d3b1f2 --- /dev/null +++ b/maintenance/tests/selenium/suites/MovePageTestCase.php @@ -0,0 +1,117 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MovePageTestCase extends SeleniumTestCase { + + // Verify move(rename) wiki page + public function testMovePage() { + + $newPage = "mypage99"; + $displayName = "Mypage99"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "60000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "60000" ); + + // Verify link 'Move' available + $this->assertTrue($this->isElementPresent( "link=Move" )); + + $this->click( "link=Move" ); + $this->waitForPageToLoad( "30000" ); + + // Verify correct page name displayed under 'Move Page' field + $this->assertEquals($displayName, + $this->getText("//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a")); + $movePageName = $this->getText( "//table[@id='mw-movepage-table']/tbody/tr[1]/td[2]/strong/a" ); + + // Verify 'To new title' field has current page name as the default name + $newTitle = $this->getValue( "wpNewTitle" ); + $correct = strstr( $movePageName , $newTitle ); + $this->assertEquals( $correct, true ); + + $this->type( "wpNewTitle", $displayName ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify warning message for the same source and destination titles + $this->assertEquals( "Source and destination titles are the same; cannot move a page over itself.", + $this->getText("//div[@id='bodyContent']/p[4]/strong" )); + + // Verify warning message for the blank title + $this->type( "wpNewTitle", "" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify warning message for the blank title + $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", + $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); + + // Verify warning messages for the invalid titles + $this->type( "wpNewTitle", "# < > [ ] | { }" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title. It may contain one or more characters which cannot be used in titles.", + $this->getText( "//div[@id='bodyContent']/p[4]/strong" )); + + $this->type( "wpNewTitle", $displayName."move" ); + $this->click( "wpMove" ); + $this->waitForPageToLoad( "30000" ); + + // Verify move success message displayed correctly + $this->assertEquals( "\"".$displayName."\" has been moved to \"".$displayName."move"."\"", + $this->getText( "//div[@id='bodyContent']/p[1]/b" )); + + $this->type( "searchInput", $newPage."move" ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify search using new page name + $this->assertEquals( $displayName."move", $this->getText( "firstHeading" )); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify search using old page name + $redirectPageName = $this->getText( "//*[@id='contentSub']" ); + $this->assertEquals( "(Redirected from ".$displayName.")" , $redirectPageName ); + + // newpage delete + $this->deletePage( $newPage."move" ); + $this->deletePage( $newPage ); + } +} + diff --git a/maintenance/tests/selenium/suites/MyContributionsTestCase.php b/maintenance/tests/selenium/suites/MyContributionsTestCase.php new file mode 100644 index 00000000..95011c3b --- /dev/null +++ b/maintenance/tests/selenium/suites/MyContributionsTestCase.php @@ -0,0 +1,76 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class MyContributionsTestCase extends SeleniumTestCase { + + // Verify user contributions + public function testRecentChangesAvailability() { + + $newPage = "mypage999"; + $displayName = "Mypage999"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + + $this->type( "searchInput", $newPage); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "60000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + $this->type( "wpTextbox1", $newPage." text" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "60000" ); + + // Verify My contributions Link available + $this->assertTrue($this->isElementPresent( "link=My contributions" )); + + $this->click( "link=My contributions" ); + $this->waitForPageToLoad( "30000" ); + + // Verify recent page adding available on My Contributions list + $this->assertEquals( $displayName, $this->getText( "link=".$displayName )); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Edit" ); + $this->waitForPageToLoad( "30000" ); + $this->type( "wpTextbox1", $newPage." text changed" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=My contributions" ); + $this->waitForPageToLoad( "30000" ); + + // Verify recent page changes available on My Contributions + $this->assertTrue($this->isTextPresent($displayName." (top)")); + $this->deletePage($newPage); + } +} + diff --git a/maintenance/tests/selenium/suites/MyWatchListTestCase.php b/maintenance/tests/selenium/suites/MyWatchListTestCase.php new file mode 100644 index 00000000..150c1f51 --- /dev/null +++ b/maintenance/tests/selenium/suites/MyWatchListTestCase.php @@ -0,0 +1,73 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + + +class MyWatchListTestCase extends SeleniumTestCase { + + // Verify user watchlist + public function testMyWatchlist() { + + $newPage = "mypage"; + $displayName = "Mypage"; + $wikiText = "watch page"; + + $this->open( $this->getUrl().'/index.php?title=Main_Page' ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=".$displayName ); + $this->waitForPageToLoad( "600000" ); + + $this->click( "wpWatchthis" ); + $this->type( "wpTextbox1",$wikiText ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "30000" ); + + // Verify link 'My Watchlist' available + $this->assertTrue( $this->isElementPresent( "link=My watchlist" ) ); + + $this->click( "link=My watchlist" ); + $this->waitForPageToLoad( "30000" ); + + // Verify newly added page to the watchlist is available + $watchList = $this->getText( "//*[@id='bodyContent']" ); + $this->assertContains( $displayName, $watchList ); + + $this->type( "searchInput", $newPage ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "30000" ); + $this->click("link=Edit"); + $this->waitForPageToLoad( "30000" ); + $this->click( "wpWatchthis" ); + $this->click( "wpSave" ); + $this->deletePage( $newPage ); + } +} + diff --git a/maintenance/tests/selenium/suites/PageDeleteTestSuite.php b/maintenance/tests/selenium/suites/PageDeleteTestSuite.php new file mode 100644 index 00000000..2e535c11 --- /dev/null +++ b/maintenance/tests/selenium/suites/PageDeleteTestSuite.php @@ -0,0 +1,16 @@ +<?php + +class PageDeleteTestSuite extends SeleniumTestSuite { + public function setUp() { + $this->setLoginBeforeTests( true ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/DeletePageAdminTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/PageSearchTestCase.php b/maintenance/tests/selenium/suites/PageSearchTestCase.php new file mode 100644 index 00000000..e139f7a0 --- /dev/null +++ b/maintenance/tests/selenium/suites/PageSearchTestCase.php @@ -0,0 +1,105 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class PageSearchTestCase extends SeleniumTestCase { + + // Verify the functionality of the 'Go' button + public function testPageSearchBtnGo() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", "calcey qa" ); + $this->click( "searchGoButton" ); + $this->waitForPageToLoad( "600000" ); + + // Verify no page matched with the entered search text + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr ( $source, "Create the page \"Calcey qa\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Calcey qa" ); + $this->waitForPageToLoad( "600000" ); + + $this->type( "wpTextbox1", "Calcey QA team" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "600000" ); + + } + + // Verify the functionality of the 'Search' button + public function testPageSearchBtnSearch() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->type( "searchInput", "Calcey web" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify no page is available as the search text + $source = $this->gettext( "//div[@id='bodyContent']/div[4]/p[2]/b" ); + $correct = strstr ( $source, "Create the page \"Calcey web\" on this wiki!" ); + $this->assertEquals( $correct, true ); + + $this->click( "link=Calcey web" ); + $this->waitForPageToLoad( "600000" ); + + $this->type( "wpTextbox1", "Calcey web team" ); + $this->click( "wpSave" ); + $this->waitForPageToLoad( "600000" ); + + // Verify saved page is opened when the exact page name is given + $this->type( "searchInput", "Calcey web" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify exact page matched with the entered search text using 'Search' button + $source = $this->getText( "//*[@id='bodyContent']/div[4]/p/b" ); + $correct = strstr( $source, "There is a page named \"Calcey web\" on this wiki." ); + $this->assertEquals( $correct, true ); + + // Verify resutls available when partial page name is entered as the search text + $this->type( "searchInput", "Calcey" ); + $this->click( "mw-searchButton" ); + $this->waitForPageToLoad( "30000" ); + + // Verify text avaialble in the search result under the page titles + if($this->isElementPresent( "Page_title_matches" )) { + $textPageTitle = $this->getText( "//*[@id='bodyContent']/div[4]/ul[1]/li[1]/div[1]/a" ); + $this->assertContains( 'Calcey', $textPageTitle ); + } + + // Verify text avaialble in the search result under the page text + if($this->isElementPresent( "Page_text_matches" )) { + $textPageText = $this->getText( "//*[@id='bodyContent']/div[4]/ul[2]/li[2]/div[2]/span" ); + $this->assertContains( 'Calcey', $textPageText ); + } + $this->deletePage("Calcey QA"); + $this->deletePage("Calcey web"); + } +} diff --git a/maintenance/tests/selenium/suites/PreviewPageTestCase.php b/maintenance/tests/selenium/suites/PreviewPageTestCase.php new file mode 100644 index 00000000..b704bf39 --- /dev/null +++ b/maintenance/tests/selenium/suites/PreviewPageTestCase.php @@ -0,0 +1,53 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class PreviewPageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testPreviewPage() { + $wikiText = "Adding this page to test the \n Preview button functionality"; + $newPage = "Test Preview Page"; + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->getNewPage( $newPage ); + $this->type( "wpTextbox1", $wikiText."" ); + $this->assertTrue($this->isElementPresent( "//*[@id='wpPreview']" )); + + $this->click( "wpPreview" ); + + // Verify saved page available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Test Preview Page" ); + $this->assertEquals( $correct, true); + + // Verify page content previewed succesfully + $contentOfPreviewPage = $this->getText( "//*[@id='content']" ); + $this->assertContains( $wikiText, $contentOfPreviewPage ); + } +} diff --git a/maintenance/tests/selenium/suites/SavePageTestCase.php b/maintenance/tests/selenium/suites/SavePageTestCase.php new file mode 100644 index 00000000..7f1a6924 --- /dev/null +++ b/maintenance/tests/selenium/suites/SavePageTestCase.php @@ -0,0 +1,58 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class SavePageTestCase extends SeleniumTestCase { + + // Verify adding a new page + public function testSavePage() { + $wikiText = "Adding this page to test the Save button functionality"; + $newPage = "Test Save Page"; + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->getNewPage($newPage); + $this->type("wpTextbox1", $wikiText); + + // verify 'Save' button available + $this->assertTrue($this->isElementPresent( "wpSave" )); + $this->click( "wpSave" ); + + // Verify saved page available + $source = $this->gettext( "firstHeading" ); + $correct = strstr( $source, "Test Save Page" ); + + // Verify Saved page name displayed correctly + $this->assertEquals( $correct, true ); + + // Verify page content saved succesfully + $contentOfSavedPage = $this->getText( "//*[@id='content']" ); + $this->assertContains( $wikiText, $contentOfSavedPage ); + $this->deletePage( $newPage ); + } +} diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php b/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php new file mode 100644 index 00000000..cffa83c4 --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumConfig.php @@ -0,0 +1,15 @@ +<?php +class SimpleSeleniumConfig { + + public static function getSettings(&$includeFiles, &$globalConfigs) { + $includes = array( + //files that needed to be included would go here + ); + $configs = array( + 'wgDefaultSkin' => 'chick' + ); + $includeFiles = array_merge( $includeFiles, $includes ); + $globalConfigs = array_merge( $globalConfigs, $configs); + return true; + } +}
\ No newline at end of file diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php b/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php new file mode 100644 index 00000000..8f01b437 --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php @@ -0,0 +1,30 @@ +<?php +/* + * This test case is part of the SimpleSeleniumTestSuite. + * Configuration for these tests are dosumented as part of SimpleSeleniumTestSuite.php + */ +class SimpleSeleniumTestCase extends SeleniumTestCase { + public function testBasic() { + $this->open( $this->getUrl() . + '/index.php?title=Selenium&action=edit' ); + $this->type( "wpTextbox1", "This is a basic test" ); + $this->click( "wpPreview" ); + $this->waitForPageToLoad( 10000 ); + + // check result + $source = $this->getText( "//div[@id='wikiPreview']/p" ); + $correct = strstr( $source, "This is a basic test" ); + $this->assertEquals( $correct, true ); + } + + /* + * All this test really does is verify that a global var was set. + * It depends on $wgDefaultSkin = 'chick'; being set + */ + public function testGlobalVariableForDefaultSkin() { + $this->open( $this->getUrl() . '/index.php?&action=purge' ); + $bodyClass = $this->getAttribute( "//body/@class" ); + $this-> assertContains('skin-chick', $bodyClass, 'Chick skin not set'); + } + +} diff --git a/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php b/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php new file mode 100644 index 00000000..a04f33ed --- /dev/null +++ b/maintenance/tests/selenium/suites/SimpleSeleniumTestSuite.php @@ -0,0 +1,26 @@ +<?php +/* + * Sample test suite. + * Two ways to configure MW for these tests + * 1) If you are running multiple test suites, add the following in LocalSettings.php + * require_once("maintenance/tests/selenium/SimpleSeleniumConfig.php"); + * $wgSeleniumTestConfigs['SimpleSeleniumTestSuite'] = 'SimpleSeleniumConfig::getSettings'; + * OR + * 2) Add the following to your Localsettings.php + * $wgDefaultSkin = 'chick'; + */ +class SimpleSeleniumTestSuite extends SeleniumTestSuite +{ + public function setUp() { + $this->setLoginBeforeTests( false ); + parent::setUp(); + } + public function addTests() { + $testFiles = array( + 'maintenance/tests/selenium/suites/SimpleSeleniumTestCase.php' + ); + parent::addTestFiles( $testFiles ); + } + + +} diff --git a/maintenance/tests/selenium/suites/UserPreferencesTestCase.php b/maintenance/tests/selenium/suites/UserPreferencesTestCase.php new file mode 100644 index 00000000..12824307 --- /dev/null +++ b/maintenance/tests/selenium/suites/UserPreferencesTestCase.php @@ -0,0 +1,179 @@ +<?php + +/** + * Selenium server manager + * + * @file + * @ingroup Maintenance + * Copyright (C) 2010 Dan Nessett <dnessett@yahoo.com> + * http://citizendium.org/ + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @addtogroup Maintenance + * + */ + +class UserPreferencesTestCase extends SeleniumTestCase { + + // Verify user information + public function testUserInfoDisplay() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + // Verify correct username displayed in User Preferences + $this->assertEquals( $this->getText( "//li[@id='pt-userpage']/a" ), + $this->getText( "//table[@id='mw-htmlform-info']/tbody/tr[1]/td[2]" )); + + // Verify existing Signature Displayed correctly + $this->assertEquals( $this->selenium->getUser(), + $this->getTable( "mw-htmlform-signature.0.1" ) ); + } + + // Verify change password + public function testChangePassword() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "12345" ); + $this->type( "wpNewPassword", "54321" ); + $this->type( "wpRetype", "54321" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + + $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "54321" ); + $this->type( "wpNewPassword", "12345" ); + $this->type( "wpRetype", "12345" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + $this->assertEquals( "Preferences", $this->getText( "firstHeading" )); + + $this->click( "link=Change password" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "wpPassword", "54321" ); + $this->type( "wpNewPassword", "12345" ); + $this->type( "wpRetype", "12345" ); + $this->click( "//input[@value='Change password']" ); + $this->waitForPageToLoad( "30000" ); + } + + // Verify successful preferences save + public function testSuccessfullSave() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "mw-input-realname", "Test User" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify "Your preferences have been saved." message + $this->assertEquals( "Your preferences have been saved.", + $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); + $this->type( "mw-input-realname", "" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + } + + // Verify change signature + public function testChangeSignature() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + $this->type( "mw-input-nickname", "TestSignature" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify change user signature + $this->assertEquals( "TestSignature", $this->getText( "link=TestSignature" )); + $this->type( "mw-input-nickname", "Test" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad("30000"); + } + + // Verify change date format + public function testChangeDateFormatTimeZone() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + $this->click( "link=Date and time" ); + $this->waitForPageToLoad( "30000" ); + + $this->click( "mw-input-date-dmy" ); + $this->select( "mw-input-timecorrection", "label=Asia/Colombo" ); + $this->click( "prefcontrol" ); + $this->waitForPageToLoad( "30000" ); + + // Verify Date format and time zome saved + $this->assertEquals( "Your preferences have been saved.", + $this->getText( "//div[@id='bodyContent']/div[4]/strong/p" )); + } + + // Verify restoring all default settings + public function testSetAllDefault() { + + $this->open( $this->getUrl() . + '/index.php?title=Main_Page&action=edit' ); + $this->click( "link=My preferences" ); + $this->waitForPageToLoad( "30000" ); + + // Verify restoring all default settings + $this->assertEquals( "Restore all default settings", + $this->getText( "link=Restore all default settings" )); + + $this->click("//*[@id='preferences']/div/a"); + $this->waitForPageToLoad("30000"); + + // Verify 'This can not be undone' warning message displayed + $this->assertTrue($this->isElementPresent("//input[@value='Restore all default settings']")); + + // Verify 'Restore all default settings' button available + $this->assertEquals("You can use this page to reset your preferences to the site defaults. This cannot be undone.", + $this->getText("//div[@id='bodyContent']/p")); + + $this->click("//input[@value='Restore all default settings']"); + $this->waitForPageToLoad("30000"); + + // Verify preferences saved successfully + $this->assertEquals("Your preferences have been saved.", + $this->getText("//div[@id='bodyContent']/div[4]/strong/p")); + } +} + diff --git a/maintenance/tests/test-prefetch-current.xml b/maintenance/tests/test-prefetch-current.xml deleted file mode 100644 index a4c8bda3..00000000 --- a/maintenance/tests/test-prefetch-current.xml +++ /dev/null @@ -1,75 +0,0 @@ -<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en"> -<siteinfo> - <sitename>DemoWiki</sitename> - <base>http://example.com/wiki/Main_Page</base> - <generator>MediaWiki 1.5.0</generator> - <case>first-letter</case> - <namespaces> - <namespace key="-2">Media</namespace> - <namespace key="-1">Special</namespace> - <namespace key="0"></namespace> - <namespace key="1">Talk</namespace> - <namespace key="2">User</namespace> - <namespace key="3">User talk</namespace> - <namespace key="4">DemoWiki</namespace> - <namespace key="5">DemoWIki talk</namespace> - <namespace key="6">Image</namespace> - <namespace key="7">Image talk</namespace> - <namespace key="8">MediaWiki</namespace> - <namespace key="9">MediaWiki talk</namespace> - <namespace key="10">Template</namespace> - <namespace key="11">Template talk</namespace> - <namespace key="12">Help</namespace> - <namespace key="13">Help talk</namespace> - <namespace key="14">Category</namespace> - <namespace key="15">Category talk</namespace> - </namespaces> -</siteinfo> -<page> - <title>First page</title> - <id>1</id> - <revision> - <id>1</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 1</comment> - <text>page 1, rev 1</text> - </revision> - <revision> - <id>2</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 2</comment> - <text>page 1, rev 2</text> - </revision> - <revision> - <id>4</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 4</comment> - <text>page 1, rev 4</text> - </revision> -</page> -<page> - <title>Second page</title> - <id>2</id> - <revision> - <id>3</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 2, rev 3</comment> - <text>page 2, rev 3</text> - </revision> -</page> -<page> - <title>Third page</title> - <id>3</id> - <revision> - <id>5</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 3, rev 5</comment> - <text>page 3, rev 5</text> - </revision> -</page> -</mediawiki> diff --git a/maintenance/tests/test-prefetch-previous.xml b/maintenance/tests/test-prefetch-previous.xml deleted file mode 100644 index 95eb82dd..00000000 --- a/maintenance/tests/test-prefetch-previous.xml +++ /dev/null @@ -1,57 +0,0 @@ -<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en"> -<siteinfo> - <sitename>DemoWiki</sitename> - <base>http://example.com/wiki/Main_Page</base> - <generator>MediaWiki 1.5.0</generator> - <case>first-letter</case> - <namespaces> - <namespace key="-2">Media</namespace> - <namespace key="-1">Special</namespace> - <namespace key="0"></namespace> - <namespace key="1">Talk</namespace> - <namespace key="2">User</namespace> - <namespace key="3">User talk</namespace> - <namespace key="4">DemoWiki</namespace> - <namespace key="5">DemoWIki talk</namespace> - <namespace key="6">Image</namespace> - <namespace key="7">Image talk</namespace> - <namespace key="8">MediaWiki</namespace> - <namespace key="9">MediaWiki talk</namespace> - <namespace key="10">Template</namespace> - <namespace key="11">Template talk</namespace> - <namespace key="12">Help</namespace> - <namespace key="13">Help talk</namespace> - <namespace key="14">Category</namespace> - <namespace key="15">Category talk</namespace> - </namespaces> -</siteinfo> -<page> - <title>First page</title> - <id>1</id> - <revision> - <id>1</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 1</comment> - <text>page 1, rev 1</text> - </revision> - <revision> - <id>2</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 2</comment> - <text>page 1, rev 2</text> - </revision> -</page> -<page> - <title>Second page</title> - <id>2</id> - <revision> - <id>3</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 2, rev 3</comment> - <text>page 2, rev 3</text> - </revision> -</page> -</mediawiki> diff --git a/maintenance/tests/test-prefetch-stub.xml b/maintenance/tests/test-prefetch-stub.xml deleted file mode 100644 index 59d43d2f..00000000 --- a/maintenance/tests/test-prefetch-stub.xml +++ /dev/null @@ -1,75 +0,0 @@ -<mediawiki xmlns="http://www.mediawiki.org/xml/export-0.3/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.mediawiki.org/xml/export-0.3/ http://www.mediawiki.org/xml/export-0.3.xsd" version="0.3" xml:lang="en"> -<siteinfo> - <sitename>DemoWiki</sitename> - <base>http://example.com/wiki/Main_Page</base> - <generator>MediaWiki 1.5.0</generator> - <case>first-letter</case> - <namespaces> - <namespace key="-2">Media</namespace> - <namespace key="-1">Special</namespace> - <namespace key="0"></namespace> - <namespace key="1">Talk</namespace> - <namespace key="2">User</namespace> - <namespace key="3">User talk</namespace> - <namespace key="4">DemoWiki</namespace> - <namespace key="5">DemoWIki talk</namespace> - <namespace key="6">Image</namespace> - <namespace key="7">Image talk</namespace> - <namespace key="8">MediaWiki</namespace> - <namespace key="9">MediaWiki talk</namespace> - <namespace key="10">Template</namespace> - <namespace key="11">Template talk</namespace> - <namespace key="12">Help</namespace> - <namespace key="13">Help talk</namespace> - <namespace key="14">Category</namespace> - <namespace key="15">Category talk</namespace> - </namespaces> -</siteinfo> -<page> - <title>First page</title> - <id>1</id> - <revision> - <id>1</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 1</comment> - <text id="1" /> - </revision> - <revision> - <id>2</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 2</comment> - <text id="2" /> - </revision> - <revision> - <id>4</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 1, rev 4</comment> - <text id="4" /> - </revision> -</page> -<page> - <title>Second page</title> - <id>2</id> - <revision> - <id>3</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 2, rev 3</comment> - <text id="3" /> - </revision> -</page> -<page> - <title>Third page</title> - <id>3</id> - <revision> - <id>5</id> - <timestamp>2001-01-15T12:00:00Z</timestamp> - <contributor><ip>10.0.0.1</ip></contributor> - <comment>page 3, rev 5</comment> - <text id="5" /> - </revision> -</page> -</mediawiki> diff --git a/maintenance/tests/testHelpers.inc b/maintenance/tests/testHelpers.inc new file mode 100644 index 00000000..94ca6abc --- /dev/null +++ b/maintenance/tests/testHelpers.inc @@ -0,0 +1,652 @@ +<?php + +/** + * @ingroup Maintenance + * + * Set of classes to help with test output and such. Right now pretty specific + * to the parser tests but could be more useful one day :) + * + * @todo Fixme: Make this more generic + */ + +class AnsiTermColorer { + function __construct() { + } + + /** + * Return ANSI terminal escape code for changing text attribs/color + * + * @param $color String: semicolon-separated list of attribute/color codes + * @return String + */ + public function color( $color ) { + global $wgCommandLineDarkBg; + + $light = $wgCommandLineDarkBg ? "1;" : "0;"; + + return "\x1b[{$light}{$color}m"; + } + + /** + * Return ANSI terminal escape code for restoring default text attributes + * + * @return String + */ + public function reset() { + return $this->color( 0 ); + } +} + +/* A colour-less terminal */ +class DummyTermColorer { + public function color( $color ) { + return ''; + } + + public function reset() { + return ''; + } +} + +class TestRecorder { + var $parent; + var $term; + + function __construct( $parent ) { + $this->parent = $parent; + $this->term = $parent->term; + } + + function start() { + $this->total = 0; + $this->success = 0; + } + + function record( $test, $result ) { + $this->total++; + $this->success += ( $result ? 1 : 0 ); + } + + function end() { + // dummy + } + + function report() { + if ( $this->total > 0 ) { + $this->reportPercentage( $this->success, $this->total ); + } else { + wfDie( "No tests found.\n" ); + } + } + + function reportPercentage( $success, $total ) { + $ratio = wfPercent( 100 * $success / $total ); + print $this->term->color( 1 ) . "Passed $success of $total tests ($ratio)... "; + + if ( $success == $total ) { + print $this->term->color( 32 ) . "ALL TESTS PASSED!"; + } else { + $failed = $total - $success ; + print $this->term->color( 31 ) . "$failed tests failed!"; + } + + print $this->term->reset() . "\n"; + + return ( $success == $total ); + } +} + +class DbTestPreviewer extends TestRecorder { + protected $lb; // /< Database load balancer + protected $db; // /< Database connection to the main DB + protected $curRun; // /< run ID number for the current run + protected $prevRun; // /< run ID number for the previous run, if any + protected $results; // /< Result array + + /** + * This should be called before the table prefix is changed + */ + function __construct( $parent ) { + parent::__construct( $parent ); + + $this->lb = wfGetLBFactory()->newMainLB(); + // This connection will have the wiki's table prefix, not parsertest_ + $this->db = $this->lb->getConnection( DB_MASTER ); + } + + /** + * Set up result recording; insert a record for the run with the date + * and all that fun stuff + */ + function start() { + parent::start(); + + if ( ! $this->db->tableExists( 'testrun' ) + or ! $this->db->tableExists( 'testitem' ) ) + { + print "WARNING> `testrun` table not found in database.\n"; + $this->prevRun = false; + } else { + // We'll make comparisons against the previous run later... + $this->prevRun = $this->db->selectField( 'testrun', 'MAX(tr_id)' ); + } + + $this->results = array(); + } + + function record( $test, $result ) { + parent::record( $test, $result ); + $this->results[$test] = $result; + } + + function report() { + if ( $this->prevRun ) { + // f = fail, p = pass, n = nonexistent + // codes show before then after + $table = array( + 'fp' => 'previously failing test(s) now PASSING! :)', + 'pn' => 'previously PASSING test(s) removed o_O', + 'np' => 'new PASSING test(s) :)', + + 'pf' => 'previously passing test(s) now FAILING! :(', + 'fn' => 'previously FAILING test(s) removed O_o', + 'nf' => 'new FAILING test(s) :(', + 'ff' => 'still FAILING test(s) :(', + ); + + $prevResults = array(); + + $res = $this->db->select( 'testitem', array( 'ti_name', 'ti_success' ), + array( 'ti_run' => $this->prevRun ), __METHOD__ ); + + foreach ( $res as $row ) { + if ( !$this->parent->regex + || preg_match( "/{$this->parent->regex}/i", $row->ti_name ) ) + { + $prevResults[$row->ti_name] = $row->ti_success; + } + } + + $combined = array_keys( $this->results + $prevResults ); + + # Determine breakdown by change type + $breakdown = array(); + foreach ( $combined as $test ) { + if ( !isset( $prevResults[$test] ) ) { + $before = 'n'; + } elseif ( $prevResults[$test] == 1 ) { + $before = 'p'; + } else /* if ( $prevResults[$test] == 0 )*/ { + $before = 'f'; + } + + if ( !isset( $this->results[$test] ) ) { + $after = 'n'; + } elseif ( $this->results[$test] == 1 ) { + $after = 'p'; + } else /*if ( $this->results[$test] == 0 ) */ { + $after = 'f'; + } + + $code = $before . $after; + + if ( isset( $table[$code] ) ) { + $breakdown[$code][$test] = $this->getTestStatusInfo( $test, $after ); + } + } + + # Write out results + foreach ( $table as $code => $label ) { + if ( !empty( $breakdown[$code] ) ) { + $count = count( $breakdown[$code] ); + printf( "\n%4d %s\n", $count, $label ); + + foreach ( $breakdown[$code] as $differing_test_name => $statusInfo ) { + print " * $differing_test_name [$statusInfo]\n"; + } + } + } + } else { + print "No previous test runs to compare against.\n"; + } + + print "\n"; + parent::report(); + } + + /** + * Returns a string giving information about when a test last had a status change. + * Could help to track down when regressions were introduced, as distinct from tests + * which have never passed (which are more change requests than regressions). + */ + private function getTestStatusInfo( $testname, $after ) { + // If we're looking at a test that has just been removed, then say when it first appeared. + if ( $after == 'n' ) { + $changedRun = $this->db->selectField ( 'testitem', + 'MIN(ti_run)', + array( 'ti_name' => $testname ), + __METHOD__ ); + $appear = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( 'tr_id' => $changedRun ), + __METHOD__ ); + + return "First recorded appearance: " + . date( "d-M-Y H:i:s", strtotime ( $appear->tr_date ) ) + . ", " . $appear->tr_mw_version; + } + + // Otherwise, this test has previous recorded results. + // See when this test last had a different result to what we're seeing now. + $conds = array( + 'ti_name' => $testname, + 'ti_success' => ( $after == 'f' ? "1" : "0" ) ); + + if ( $this->curRun ) { + $conds[] = "ti_run != " . $this->db->addQuotes ( $this->curRun ); + } + + $changedRun = $this->db->selectField ( 'testitem', 'MAX(ti_run)', $conds, __METHOD__ ); + + // If no record of ever having had a different result. + if ( is_null ( $changedRun ) ) { + if ( $after == "f" ) { + return "Has never passed"; + } else { + return "Has never failed"; + } + } + + // Otherwise, we're looking at a test whose status has changed. + // (i.e. it used to work, but now doesn't; or used to fail, but is now fixed.) + // In this situation, give as much info as we can as to when it changed status. + $pre = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( 'tr_id' => $changedRun ), + __METHOD__ ); + $post = $this->db->selectRow ( 'testrun', + array( 'tr_date', 'tr_mw_version' ), + array( "tr_id > " . $this->db->addQuotes ( $changedRun ) ), + __METHOD__, + array( "LIMIT" => 1, "ORDER BY" => 'tr_id' ) + ); + + if ( $post ) { + $postDate = date( "d-M-Y H:i:s", strtotime ( $post->tr_date ) ) . ", {$post->tr_mw_version}"; + } else { + $postDate = 'now'; + } + + return ( $after == "f" ? "Introduced" : "Fixed" ) . " between " + . date( "d-M-Y H:i:s", strtotime ( $pre->tr_date ) ) . ", " . $pre->tr_mw_version + . " and $postDate"; + + } + + /** + * Commit transaction and clean up for result recording + */ + function end() { + $this->lb->commitMasterChanges(); + $this->lb->closeAll(); + parent::end(); + } + +} + +class DbTestRecorder extends DbTestPreviewer { + var $version; + + /** + * Set up result recording; insert a record for the run with the date + * and all that fun stuff + */ + function start() { + global $wgDBtype; + $this->db->begin(); + + if ( ! $this->db->tableExists( 'testrun' ) + or ! $this->db->tableExists( 'testitem' ) ) + { + print "WARNING> `testrun` table not found in database. Trying to create table.\n"; + $this->db->sourceFile( $this->db->patchPath( 'patch-testrun.sql' ) ); + echo "OK, resuming.\n"; + } + + parent::start(); + + $this->db->insert( 'testrun', + array( + 'tr_date' => $this->db->timestamp(), + 'tr_mw_version' => $this->version, + 'tr_php_version' => phpversion(), + 'tr_db_version' => $this->db->getServerVersion(), + 'tr_uname' => php_uname() + ), + __METHOD__ ); + if ( $wgDBtype === 'postgres' ) { + $this->curRun = $this->db->currentSequenceValue( 'testrun_id_seq' ); + } else { + $this->curRun = $this->db->insertId(); + } + } + + /** + * Record an individual test item's success or failure to the db + * + * @param $test String + * @param $result Boolean + */ + function record( $test, $result ) { + parent::record( $test, $result ); + + $this->db->insert( 'testitem', + array( + 'ti_run' => $this->curRun, + 'ti_name' => $test, + 'ti_success' => $result ? 1 : 0, + ), + __METHOD__ ); + } +} + +class RemoteTestRecorder extends TestRecorder { + function start() { + parent::start(); + + $this->results = array(); + $this->ping( 'running' ); + } + + function record( $test, $result ) { + parent::record( $test, $result ); + $this->results[$test] = (bool)$result; + } + + function end() { + $this->ping( 'complete', $this->results ); + parent::end(); + } + + /** + * Inform a CodeReview instance that we've started or completed a test run... + * + * @param $status string: "running" - tell it we've started + * "complete" - provide test results array + * "abort" - something went horribly awry + * @param $results array of test name => true/false + */ + function ping( $status, $results = false ) { + global $wgParserTestRemote, $IP; + + $remote = $wgParserTestRemote; + $revId = SpecialVersion::getSvnRevision( $IP ); + $jsonResults = FormatJson::encode( $results ); + + if ( !$remote ) { + print "Can't do remote upload without configuring \$wgParserTestRemote!\n"; + exit( 1 ); + } + + // Generate a hash MAC to validate our credentials + $message = array( + $remote['repo'], + $remote['suite'], + $revId, + $status, + ); + + if ( $status == "complete" ) { + $message[] = $jsonResults; + } + $hmac = hash_hmac( "sha1", implode( "|", $message ), $remote['secret'] ); + + $postData = array( + 'action' => 'codetestupload', + 'format' => 'json', + 'repo' => $remote['repo'], + 'suite' => $remote['suite'], + 'rev' => $revId, + 'status' => $status, + 'hmac' => $hmac, + ); + + if ( $status == "complete" ) { + $postData['results'] = $jsonResults; + } + + $response = $this->post( $remote['api-url'], $postData ); + + if ( $response === false ) { + print "CodeReview info upload failed to reach server.\n"; + exit( 1 ); + } + + $responseData = FormatJson::decode( $response, true ); + + if ( !is_array( $responseData ) ) { + print "CodeReview API response not recognized...\n"; + wfDebug( "Unrecognized CodeReview API response: $response\n" ); + exit( 1 ); + } + + if ( isset( $responseData['error'] ) ) { + $code = $responseData['error']['code']; + $info = $responseData['error']['info']; + print "CodeReview info upload failed: $code $info\n"; + exit( 1 ); + } + } + + function post( $url, $data ) { + return Http::post( $url, array( 'postData' => $data ) ); + } +} + +class TestFileIterator implements Iterator { + private $file; + private $fh; + private $parserTest; /* An instance of ParserTest (parserTests.php) or MediaWikiParserTest (phpunit) */ + private $index = 0; + private $test; + private $lineNum; + private $eof; + + function __construct( $file, $parserTest = null ) { + global $IP; + + $this->file = $file; + $this->fh = fopen( $this->file, "rt" ); + + if ( !$this->fh ) { + wfDie( "Couldn't open file '$file'\n" ); + } + + $this->parserTest = $parserTest; + + if ( $this->parserTest ) { + $this->parserTest->showRunFile( wfRelativePath( $this->file, $IP ) ); + } + + $this->lineNum = $this->index = 0; + } + + function rewind() { + if ( fseek( $this->fh, 0 ) ) { + wfDie( "Couldn't fseek to the start of '$this->file'\n" ); + } + + $this->index = -1; + $this->lineNum = 0; + $this->eof = false; + $this->next(); + + return true; + } + + function current() { + return $this->test; + } + + function key() { + return $this->index; + } + + function next() { + if ( $this->readNextTest() ) { + $this->index++; + return true; + } else { + $this->eof = true; + } + } + + function valid() { + return $this->eof != true; + } + + function readNextTest() { + $data = array(); + $section = null; + + while ( false !== ( $line = fgets( $this->fh ) ) ) { + $this->lineNum++; + $matches = array(); + + if ( preg_match( '/^!!\s*(\w+)/', $line, $matches ) ) { + $section = strtolower( $matches[1] ); + + if ( $section == 'endarticle' ) { + if ( !isset( $data['text'] ) ) { + wfDie( "'endarticle' without 'text' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['article'] ) ) { + wfDie( "'endarticle' without 'article' at line {$this->lineNum} of $this->file\n" ); + } + + if ( $this->parserTest ) { + $this->parserTest->addArticle( ParserTest::chomp( $data['article'] ), $data['text'], $this->lineNum ); + } else {wfDie("JAJA"); + ParserTest::addArticle( $data['article'], $data['text'], $this->lineNum ); + } + $data = array(); + $section = null; + + continue; + } + + if ( $section == 'endhooks' ) { + if ( !isset( $data['hooks'] ) ) { + wfDie( "'endhooks' without 'hooks' at line {$this->lineNum} of $this->file\n" ); + } + + foreach ( explode( "\n", $data['hooks'] ) as $line ) { + $line = trim( $line ); + + if ( $line ) { + if ( $this->parserTest && !$this->parserTest->requireHook( $line ) ) { + return false; + } + } + } + + $data = array(); + $section = null; + + continue; + } + + if ( $section == 'endfunctionhooks' ) { + if ( !isset( $data['functionhooks'] ) ) { + wfDie( "'endfunctionhooks' without 'functionhooks' at line {$this->lineNum} of $this->file\n" ); + } + + foreach ( explode( "\n", $data['functionhooks'] ) as $line ) { + $line = trim( $line ); + + if ( $line ) { + if ( $this->parserTest && !$this->parserTest->requireFunctionHook( $line ) ) { + return false; + } + } + } + + $data = array(); + $section = null; + + continue; + } + + if ( $section == 'end' ) { + if ( !isset( $data['test'] ) ) { + wfDie( "'end' without 'test' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['input'] ) ) { + wfDie( "'end' without 'input' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['result'] ) ) { + wfDie( "'end' without 'result' at line {$this->lineNum} of $this->file\n" ); + } + + if ( !isset( $data['options'] ) ) { + $data['options'] = ''; + } + + if ( !isset( $data['config'] ) ) + $data['config'] = ''; + + if ( $this->parserTest + && ( ( preg_match( '/\\bdisabled\\b/i', $data['options'] ) && !$this->parserTest->runDisabled ) + || !preg_match( "/" . $this->parserTest->regex . "/i", $data['test'] ) ) ) { + # disabled test + $data = array(); + $section = null; + + continue; + } + + global $wgUseTeX; + + if ( $this->parserTest && + preg_match( '/\\bmath\\b/i', $data['options'] ) && !$wgUseTeX ) { + # don't run math tests if $wgUseTeX is set to false in LocalSettings + $data = array(); + $section = null; + + continue; + } + + if ( $this->parserTest ) { + $this->test = array( + 'test' => ParserTest::chomp( $data['test'] ), + 'input' => ParserTest::chomp( $data['input'] ), + 'result' => ParserTest::chomp( $data['result'] ), + 'options' => ParserTest::chomp( $data['options'] ), + 'config' => ParserTest::chomp( $data['config'] ) ); + } else { + $this->test['test'] = $data['test']; + } + + return true; + } + + if ( isset ( $data[$section] ) ) { + wfDie( "duplicate section '$section' at line {$this->lineNum} of $this->file\n" ); + } + + $data[$section] = ''; + + continue; + } + + if ( $section ) { + $data[$section] .= $line; + } + } + + return false; + } +} |