From: Máté Szabó Date: Mon, 17 Jun 2019 21:15:25 +0000 (+0200) Subject: Introduce separate unit tests PHPUnit configuration X-Git-Tag: 1.34.0-rc.0~1366^2 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=b4c546f5ae99575be6b96de50228b263cf6e0c28 Introduce separate unit tests PHPUnit configuration This changeset lays down the basic groundwork required to implement T89432 and related tickets and is based on exploration done at the Prague Hackathon. The goal is to identify tests in MediaWiki core that can be run without having to install & configure MediaWiki and its dependencies, and provide a way to execute these tests via the standard phpunit entry point, allowing for faster development and integration with existing tooling like IDEs. This changeset creates a new subdirectory under phpunit/ and organizes it into a separate test suite. The environment for this suite is set up via a PHPUnit bootstrap file without a custom entry point. For B/C, this directory is also registered in suite.xml, to ensure that existing CI jobs still pick up tests in the new suite. For initial testing, a single test class, PasswordFactoryTest, was moved to this new suite. You can run the new suite using the follwoing command: $ vendor/bin/phpunit -d memory_limit=512M -c tests/phpunit/unit-tests.xml Bug: T84948 Bug: T89432 Bug: T87781 Change-Id: I69b92db3e70093570e05cc0a64c7780a278b321a --- diff --git a/tests/common/TestsAutoLoader.php b/tests/common/TestsAutoLoader.php index 861111a775..3b643a5d97 100644 --- a/tests/common/TestsAutoLoader.php +++ b/tests/common/TestsAutoLoader.php @@ -60,6 +60,7 @@ $wgAutoloadClasses += [ 'MediaWikiPHPUnitResultPrinter' => "$testDir/phpunit/MediaWikiPHPUnitResultPrinter.php", 'MediaWikiPHPUnitTestListener' => "$testDir/phpunit/MediaWikiPHPUnitTestListener.php", 'MediaWikiTestCase' => "$testDir/phpunit/MediaWikiTestCase.php", + 'MediaWikiUnitTestCase' => "$testDir/phpunit/MediaWikiUnitTestCase.php", 'MediaWikiTestResult' => "$testDir/phpunit/MediaWikiTestResult.php", 'MediaWikiTestRunner' => "$testDir/phpunit/MediaWikiTestRunner.php", 'PHPUnit4And6Compat' => "$testDir/phpunit/PHPUnit4And6Compat.php", diff --git a/tests/phpunit/MediaWikiUnitTestCase.php b/tests/phpunit/MediaWikiUnitTestCase.php new file mode 100644 index 0000000000..407be208b6 --- /dev/null +++ b/tests/phpunit/MediaWikiUnitTestCase.php @@ -0,0 +1,29 @@ +assertEquals( [ '' ], array_keys( $pf->getTypes() ) ); - $this->assertEquals( '', $pf->getDefaultType() ); - - $pf = new PasswordFactory( [ - 'foo' => [ 'class' => 'FooPassword' ], - 'bar' => [ 'class' => 'BarPassword', 'baz' => 'boom' ], - ], 'foo' ); - $this->assertEquals( [ '', 'foo', 'bar' ], array_keys( $pf->getTypes() ) ); - $this->assertArraySubset( [ 'class' => 'BarPassword', 'baz' => 'boom' ], $pf->getTypes()['bar'] ); - $this->assertEquals( 'foo', $pf->getDefaultType() ); - } - - public function testRegister() { - $pf = new PasswordFactory; - $pf->register( 'foo', [ 'class' => InvalidPassword::class ] ); - $this->assertArrayHasKey( 'foo', $pf->getTypes() ); - } - - public function testSetDefaultType() { - $pf = new PasswordFactory; - $pf->register( '1', [ 'class' => InvalidPassword::class ] ); - $pf->register( '2', [ 'class' => InvalidPassword::class ] ); - $pf->setDefaultType( '1' ); - $this->assertSame( '1', $pf->getDefaultType() ); - $pf->setDefaultType( '2' ); - $this->assertSame( '2', $pf->getDefaultType() ); - } - - /** - * @expectedException Exception - */ - public function testSetDefaultTypeError() { - $pf = new PasswordFactory; - $pf->setDefaultType( 'bogus' ); - } - - public function testInit() { - $config = new HashConfig( [ - 'PasswordConfig' => [ - 'foo' => [ 'class' => InvalidPassword::class ], - ], - 'PasswordDefault' => 'foo' - ] ); - $pf = new PasswordFactory; - $pf->init( $config ); - $this->assertSame( 'foo', $pf->getDefaultType() ); - $this->assertArrayHasKey( 'foo', $pf->getTypes() ); - } - - public function testNewFromCiphertext() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' ); - $this->assertInstanceOf( MWSaltedPassword::class, $pw ); - } - - public function provideNewFromCiphertextErrors() { - return [ [ 'blah' ], [ ':blah:' ] ]; - } - - /** - * @dataProvider provideNewFromCiphertextErrors - * @expectedException PasswordError - */ - public function testNewFromCiphertextErrors( $hash ) { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->newFromCiphertext( $hash ); - } - - public function testNewFromType() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pw = $pf->newFromType( 'B' ); - $this->assertInstanceOf( MWSaltedPassword::class, $pw ); - } - - /** - * @expectedException PasswordError - */ - public function testNewFromTypeError() { - $pf = new PasswordFactory; - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->newFromType( 'bogus' ); - } - - public function testNewFromPlaintext() { - $pf = new PasswordFactory; - $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->setDefaultType( 'A' ); - - $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) ); - $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) ); - $this->assertInstanceOf( MWSaltedPassword::class, - $pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) ); - } - - public function testNeedsUpdate() { - $pf = new PasswordFactory; - $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); - $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); - $pf->setDefaultType( 'A' ); - - $this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) ); - $this->assertTrue( $pf->needsUpdate( $pf->newFromType( 'B' ) ) ); - } - - public function testGenerateRandomPasswordString() { - $this->assertSame( 13, strlen( PasswordFactory::generateRandomPasswordString( 13 ) ) ); - } - - public function testNewInvalidPassword() { - $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() ); - } -} diff --git a/tests/phpunit/suite.xml b/tests/phpunit/suite.xml index de68fec17e..a9824ef30a 100644 --- a/tests/phpunit/suite.xml +++ b/tests/phpunit/suite.xml @@ -56,12 +56,13 @@ documentation + + unit + - Utility Broken - Stub diff --git a/tests/phpunit/unit-tests.xml b/tests/phpunit/unit-tests.xml new file mode 100644 index 0000000000..cd4118ca0d --- /dev/null +++ b/tests/phpunit/unit-tests.xml @@ -0,0 +1,42 @@ + + + + + unit + + + + + Broken + + + + + ../../includes + ../../languages + ../../maintenance + + ../../languages/messages + ../../languages/data/normalize-ar.php + ../../languages/data/normalize-ml.php + + + + diff --git a/tests/phpunit/unit/includes/password/PasswordFactoryTest.php b/tests/phpunit/unit/includes/password/PasswordFactoryTest.php new file mode 100644 index 0000000000..cbfddd4d72 --- /dev/null +++ b/tests/phpunit/unit/includes/password/PasswordFactoryTest.php @@ -0,0 +1,124 @@ +assertEquals( [ '' ], array_keys( $pf->getTypes() ) ); + $this->assertEquals( '', $pf->getDefaultType() ); + + $pf = new PasswordFactory( [ + 'foo' => [ 'class' => 'FooPassword' ], + 'bar' => [ 'class' => 'BarPassword', 'baz' => 'boom' ], + ], 'foo' ); + $this->assertEquals( [ '', 'foo', 'bar' ], array_keys( $pf->getTypes() ) ); + $this->assertArraySubset( [ 'class' => 'BarPassword', 'baz' => 'boom' ], $pf->getTypes()['bar'] ); + $this->assertEquals( 'foo', $pf->getDefaultType() ); + } + + public function testRegister() { + $pf = new PasswordFactory; + $pf->register( 'foo', [ 'class' => InvalidPassword::class ] ); + $this->assertArrayHasKey( 'foo', $pf->getTypes() ); + } + + public function testSetDefaultType() { + $pf = new PasswordFactory; + $pf->register( '1', [ 'class' => InvalidPassword::class ] ); + $pf->register( '2', [ 'class' => InvalidPassword::class ] ); + $pf->setDefaultType( '1' ); + $this->assertSame( '1', $pf->getDefaultType() ); + $pf->setDefaultType( '2' ); + $this->assertSame( '2', $pf->getDefaultType() ); + } + + /** + * @expectedException Exception + */ + public function testSetDefaultTypeError() { + $pf = new PasswordFactory; + $pf->setDefaultType( 'bogus' ); + } + + public function testInit() { + $config = new HashConfig( [ + 'PasswordConfig' => [ + 'foo' => [ 'class' => InvalidPassword::class ], + ], + 'PasswordDefault' => 'foo' + ] ); + $pf = new PasswordFactory; + $pf->init( $config ); + $this->assertSame( 'foo', $pf->getDefaultType() ); + $this->assertArrayHasKey( 'foo', $pf->getTypes() ); + } + + public function testNewFromCiphertext() { + $pf = new PasswordFactory; + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pw = $pf->newFromCiphertext( ':B:salt:d529e941509eb9e9b9cfaeae1fe7ca23' ); + $this->assertInstanceOf( MWSaltedPassword::class, $pw ); + } + + public function provideNewFromCiphertextErrors() { + return [ [ 'blah' ], [ ':blah:' ] ]; + } + + /** + * @dataProvider provideNewFromCiphertextErrors + * @expectedException PasswordError + */ + public function testNewFromCiphertextErrors( $hash ) { + $pf = new PasswordFactory; + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pf->newFromCiphertext( $hash ); + } + + public function testNewFromType() { + $pf = new PasswordFactory; + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pw = $pf->newFromType( 'B' ); + $this->assertInstanceOf( MWSaltedPassword::class, $pw ); + } + + /** + * @expectedException PasswordError + */ + public function testNewFromTypeError() { + $pf = new PasswordFactory; + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pf->newFromType( 'bogus' ); + } + + public function testNewFromPlaintext() { + $pf = new PasswordFactory; + $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pf->setDefaultType( 'A' ); + + $this->assertInstanceOf( InvalidPassword::class, $pf->newFromPlaintext( null ) ); + $this->assertInstanceOf( MWOldPassword::class, $pf->newFromPlaintext( 'password' ) ); + $this->assertInstanceOf( MWSaltedPassword::class, + $pf->newFromPlaintext( 'password', $pf->newFromType( 'B' ) ) ); + } + + public function testNeedsUpdate() { + $pf = new PasswordFactory; + $pf->register( 'A', [ 'class' => MWOldPassword::class ] ); + $pf->register( 'B', [ 'class' => MWSaltedPassword::class ] ); + $pf->setDefaultType( 'A' ); + + $this->assertFalse( $pf->needsUpdate( $pf->newFromType( 'A' ) ) ); + $this->assertTrue( $pf->needsUpdate( $pf->newFromType( 'B' ) ) ); + } + + public function testGenerateRandomPasswordString() { + $this->assertSame( 13, strlen( PasswordFactory::generateRandomPasswordString( 13 ) ) ); + } + + public function testNewInvalidPassword() { + $this->assertInstanceOf( InvalidPassword::class, PasswordFactory::newInvalidPassword() ); + } +} diff --git a/tests/phpunit/unit/initUnitTests.php b/tests/phpunit/unit/initUnitTests.php new file mode 100644 index 0000000000..212187711a --- /dev/null +++ b/tests/phpunit/unit/initUnitTests.php @@ -0,0 +1,69 @@ + $value ) { + $GLOBALS[$varName] = $value; + } +} + +define( 'MEDIAWIKI', true ); +define( 'MW_PHPUNIT_TEST', true ); + +// We don't use a settings file here but some code still assumes that one exists +define( 'MW_CONFIG_FILE', 'LocalSettings.php' ); + +$IP = realpath( __DIR__ . '/../../..' ); + +// these variables must be defined before setup runs +$GLOBALS['IP'] = $IP; +$GLOBALS['wgCommandLineMode'] = true; + +require_once "$IP/tests/common/TestSetup.php"; + +wfRequireOnceInGlobalScope( "$IP/includes/AutoLoader.php" ); +wfRequireOnceInGlobalScope( "$IP/includes/Defines.php" ); +wfRequireOnceInGlobalScope( "$IP/includes/DefaultSettings.php" ); +wfRequireOnceInGlobalScope( "$IP/includes/GlobalFunctions.php" ); + +require_once "$IP/tests/common/TestsAutoLoader.php"; + +TestSetup::applyInitialConfig();