DeprecationHelper currently breaks dynamic properties on phpunit mocks. This happens because phpunit starts mocking the magic methods if they're explicitly defined. By default, magic methods and up doing nothing, but if proxying to original methods is enabled, magic methods are called like regular methods, regarless of whether property exists or not. With this patch we can workaround this issue, and create mocks for classes with deprecations. Needed-By: I4297aea3489bb66c98c664da2332584c27793bfa Change-Id: Id60a7751ece05669eced6eddd3216da7149411c7
218 lines
7.2 KiB
PHP
218 lines
7.2 KiB
PHP
<?php
|
|
|
|
use Wikimedia\TestingAccessWrapper;
|
|
|
|
/**
|
|
* @covers DeprecationHelper
|
|
*/
|
|
class DeprecationHelperTest extends MediaWikiIntegrationTestCase {
|
|
|
|
/** @var TestDeprecatedClass */
|
|
private $testClass;
|
|
|
|
/** @var TestDeprecatedSubclass */
|
|
private $testSubclass;
|
|
|
|
protected function setUp(): void {
|
|
parent::setUp();
|
|
$this->testClass = new TestDeprecatedClass();
|
|
$this->testSubclass = new TestDeprecatedSubclass();
|
|
$this->setMwGlobals( 'wgDevelopmentWarnings', false );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideGet
|
|
*/
|
|
public function testGet( $propName, $expectedLevel, $expectedMessage ) {
|
|
if ( $expectedLevel ) {
|
|
$this->assertErrorTriggered( function () use ( $propName ) {
|
|
$this->assertSame( null, $this->testClass->$propName );
|
|
}, $expectedLevel, $expectedMessage );
|
|
} else {
|
|
$this->assertDeprecationWarningIssued( function () use ( $propName ) {
|
|
$this->assertSame( 1, $this->testClass->$propName );
|
|
} );
|
|
}
|
|
}
|
|
|
|
public function provideGet() {
|
|
return [
|
|
[ 'protectedDeprecated', 0, null ],
|
|
[ 'privateDeprecated', null, null ],
|
|
[ 'fallbackDeprecated', null, null ],
|
|
[ 'fallbackGetterOnly', null, null ],
|
|
[ 'protectedNonDeprecated', E_USER_ERROR,
|
|
'Cannot access non-public property TestDeprecatedClass::$protectedNonDeprecated' ],
|
|
[ 'privateNonDeprecated', E_USER_ERROR,
|
|
'Cannot access non-public property TestDeprecatedClass::$privateNonDeprecated' ],
|
|
[ 'nonExistent', E_USER_NOTICE, 'Undefined property: TestDeprecatedClass::$nonExistent' ],
|
|
];
|
|
}
|
|
|
|
public function testDeprecateDynamicPropertyAccess() {
|
|
$testObject = new class extends TestDeprecatedClass {
|
|
public function __construct() {
|
|
parent::__construct();
|
|
$this->deprecateDynamicPropertiesAccess( '1.23' );
|
|
}
|
|
};
|
|
$this->assertDeprecationWarningIssued( static function () use ( $testObject ) {
|
|
$testObject->dynamic_property = 'bla';
|
|
} );
|
|
}
|
|
|
|
public function testDynamicPropertyNullCoalesce() {
|
|
$testObject = new TestDeprecatedClass();
|
|
$this->assertSame( 'bla', $testObject->dynamic_property ?? 'bla' );
|
|
}
|
|
|
|
public function testDynamicPropertyNullCoalesceDeprecated() {
|
|
$testObject = new class extends TestDeprecatedClass {
|
|
public function __construct() {
|
|
parent::__construct();
|
|
$this->deprecateDynamicPropertiesAccess( '1.23' );
|
|
}
|
|
};
|
|
$this->assertDeprecationWarningIssued( function () use ( $testObject ) {
|
|
$this->assertSame( 'bla', $testObject->dynamic_property ?? 'bla' );
|
|
} );
|
|
}
|
|
|
|
public function testDynamicPropertyOnMockObject() {
|
|
$testObject = $this->getMockBuilder( TestDeprecatedClass::class )
|
|
->enableProxyingToOriginalMethods()
|
|
->getMock();
|
|
$testObject->blabla = 'test';
|
|
$this->assertSame( 'test', $testObject->blabla );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider provideSet
|
|
*/
|
|
public function testSet( $propName, $expectedLevel, $expectedMessage ) {
|
|
$this->assertPropertySame( 1, $this->testClass, $propName );
|
|
if ( $expectedLevel ) {
|
|
$this->assertErrorTriggered( function () use ( $propName ) {
|
|
$this->testClass->$propName = 0;
|
|
$this->assertPropertySame( 1, $this->testClass, $propName );
|
|
}, $expectedLevel, $expectedMessage );
|
|
} else {
|
|
if ( $propName === 'nonExistent' ) {
|
|
$this->testClass->$propName = 0;
|
|
} else {
|
|
$this->assertDeprecationWarningIssued( function () use ( $propName ) {
|
|
$this->testClass->$propName = 0;
|
|
} );
|
|
}
|
|
$this->assertPropertySame( 0, $this->testClass, $propName );
|
|
}
|
|
}
|
|
|
|
public function provideSet() {
|
|
return [
|
|
[ 'protectedDeprecated', null, null ],
|
|
[ 'privateDeprecated', null, null ],
|
|
[ 'fallbackDeprecated', null, null ],
|
|
[ 'fallbackGetterOnly', E_USER_ERROR,
|
|
'Cannot access non-public property TestDeprecatedClass::$fallbackGetterOnly' ],
|
|
[ 'protectedNonDeprecated', E_USER_ERROR,
|
|
'Cannot access non-public property TestDeprecatedClass::$protectedNonDeprecated' ],
|
|
[ 'privateNonDeprecated', E_USER_ERROR,
|
|
'Cannot access non-public property TestDeprecatedClass::$privateNonDeprecated' ],
|
|
[ 'nonExistent', null, null ],
|
|
];
|
|
}
|
|
|
|
public function testInternalGet() {
|
|
$this->assertSame( [
|
|
'prod' => 1,
|
|
'prond' => 1,
|
|
'prid' => 1,
|
|
'prind' => 1,
|
|
], $this->testClass->getThings() );
|
|
}
|
|
|
|
public function testInternalSet() {
|
|
$this->testClass->setThings( 2, 2, 2, 2 );
|
|
$wrapper = TestingAccessWrapper::newFromObject( $this->testClass );
|
|
$this->assertSame( 2, $wrapper->protectedDeprecated );
|
|
$this->assertSame( 2, $wrapper->protectedNonDeprecated );
|
|
$this->assertSame( 2, $wrapper->privateDeprecated );
|
|
$this->assertSame( 2, $wrapper->privateNonDeprecated );
|
|
}
|
|
|
|
public function testSubclassGetSet() {
|
|
$fullName = 'TestDeprecatedClass::$privateNonDeprecated';
|
|
$this->assertErrorTriggered( function () {
|
|
$this->assertSame( null, $this->testSubclass->getNondeprecatedPrivateParentProperty() );
|
|
}, E_USER_ERROR, "Cannot access non-public property $fullName" );
|
|
$this->assertErrorTriggered( function () {
|
|
$this->testSubclass->setNondeprecatedPrivateParentProperty( 0 );
|
|
$wrapper = TestingAccessWrapper::newFromObject( $this->testSubclass );
|
|
$this->assertSame( 1, $wrapper->privateNonDeprecated );
|
|
}, E_USER_ERROR, "Cannot access non-public property $fullName" );
|
|
|
|
$fullName = 'TestDeprecatedSubclass::$subclassPrivateNondeprecated';
|
|
$this->assertErrorTriggered( function () {
|
|
$this->assertSame( null, $this->testSubclass->subclassPrivateNondeprecated );
|
|
}, E_USER_ERROR, "Cannot access non-public property $fullName" );
|
|
$this->assertErrorTriggered( function () {
|
|
$this->testSubclass->subclassPrivateNondeprecated = 0;
|
|
$wrapper = TestingAccessWrapper::newFromObject( $this->testSubclass );
|
|
$this->assertSame( 1, $wrapper->subclassPrivateNondeprecated );
|
|
}, E_USER_ERROR, "Cannot access non-public property $fullName" );
|
|
}
|
|
|
|
protected function assertErrorTriggered( callable $callback, $level, $message ) {
|
|
$actualLevel = $actualMessage = null;
|
|
set_error_handler( static function ( $errorCode, $errorStr ) use ( &$actualLevel, &$actualMessage ) {
|
|
$actualLevel = $errorCode;
|
|
$actualMessage = $errorStr;
|
|
} );
|
|
$callback();
|
|
restore_error_handler();
|
|
$this->assertSame( $level, $actualLevel );
|
|
$this->assertSame( $message, $actualMessage );
|
|
}
|
|
|
|
protected function assertPropertySame( $expected, $object, $propName ) {
|
|
try {
|
|
$this->assertSame( $expected, TestingAccessWrapper::newFromObject( $object )->$propName );
|
|
} catch ( ReflectionException $e ) {
|
|
if ( !preg_match( "/Property (TestDeprecated(Class|Subclass)::\\$?)?$propName does not exist/",
|
|
$e->getMessage() )
|
|
) {
|
|
throw $e;
|
|
}
|
|
// property_exists accepts monkey-patching, Reflection / TestingAccessWrapper doesn't
|
|
if ( property_exists( $object, $propName ) ) {
|
|
$this->assertSame( $expected, $object->$propName );
|
|
}
|
|
}
|
|
}
|
|
|
|
protected function assertDeprecationWarningIssued( callable $callback ) {
|
|
$this->expectDeprecation();
|
|
$callback();
|
|
}
|
|
|
|
/**
|
|
* Test bad MW version values to throw exceptions as expected
|
|
*
|
|
* @dataProvider provideBadMWVersion
|
|
*/
|
|
public function testBadMWVersion( $version, $expected ) {
|
|
$this->expectException( $expected );
|
|
|
|
wfDeprecated( __METHOD__, $version );
|
|
}
|
|
|
|
public function provideBadMWVersion() {
|
|
return [
|
|
[ 1, Exception::class ],
|
|
[ 1.33, Exception::class ],
|
|
[ true, Exception::class ],
|
|
[ null, Exception::class ]
|
|
];
|
|
}
|
|
}
|