noticesWereEnabled = Deprecation::isEnabled(); $this->oldHandler = set_error_handler(function (int $errno, string $errstr, string $errfile, int $errline) { if ($errno === E_USER_DEPRECATED) { if (str_contains($errstr, 'SilverStripe\\Dev\\Tests\\DeprecationTest')) { throw new Deprecated($errstr, $errno, '', 1); } else { // Suppress any E_USER_DEPRECATED unrelated to this unit-test return true; } } if (is_callable($this->oldHandler)) { return call_user_func($this->oldHandler, $errno, $errstr, $errfile, $errline); } // Fallback to default PHP error handler return false; }); } protected function tearDown(): void { if ($this->noticesWereEnabled) { Deprecation::enable(); } else { Deprecation::disable(); } restore_error_handler(); $this->oldHandler = null; parent::tearDown(); } private function myDeprecatedMethod(): string { Deprecation::notice('1.2.3', 'My message'); return 'abc'; } public function testNotice() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.', 'My message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testNotice.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); $ret = $this->myDeprecatedMethod(); $this->assertSame('abc', $ret); // call outputNotices() directly because the regular shutdown function that emits // the notices within Deprecation won't be called until after this unit-test has finished Deprecation::outputNotices(); } public function testCallUserFunc() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.', 'My message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testCallUserFunc.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); $ret = call_user_func([$this, 'myDeprecatedMethod']); $this->assertSame('abc', $ret); Deprecation::outputNotices(); } public function testCallUserFuncArray() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.', 'My message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testCallUserFuncArray.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); $ret = call_user_func_array([$this, 'myDeprecatedMethod'], []); $this->assertSame('abc', $ret); Deprecation::outputNotices(); } public function testWithNoReplacementDefault() { Deprecation::enable(); $ret = Deprecation::withNoReplacement(function () { return $this->myDeprecatedMethod(); }); $this->assertSame('abc', $ret); Deprecation::outputNotices(); } public function testWithNoReplacementTrue() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.', 'My message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testWithNoReplacementTrue.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(true); $ret = Deprecation::withNoReplacement(function () { return $this->myDeprecatedMethod(); }); $this->assertSame('abc', $ret); Deprecation::outputNotices(); } public function testWithNoReplacementTrueCallUserFunc() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->myDeprecatedMethod is deprecated.', 'My message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testWithNoReplacementTrueCallUserFunc.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(true); $ret = Deprecation::withNoReplacement(function () { return call_user_func([$this, 'myDeprecatedMethod']); }); $this->assertSame('abc', $ret); Deprecation::outputNotices(); } public function testNoticeWithNoReplacementTrue() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest->testNoticeWithNoReplacementTrue is deprecated.', 'My message.', 'Called from PHPUnit\Framework\TestCase->runTest.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(true); Deprecation::withNoReplacement(function () { Deprecation::notice('123', 'My message.'); }); Deprecation::outputNotices(); } public function testClassWithNoReplacement() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject is deprecated.', 'Some class message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testClassWithNoReplacement.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(true); // using this syntax because my IDE was complaining about DeprecationTestObject not existing // when trying to use `new DeprecationTestObject();` $class = DeprecationTestObject::class; new $class(); Deprecation::outputNotices(); } public function testClassWithInjectorWithNoReplacement() { $message = implode(' ', [ 'SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject is deprecated.', 'Some class message.', 'Called from SilverStripe\Dev\Tests\DeprecationTest->testClassWithInjectorWithNoReplacement.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(true); Injector::inst()->get(DeprecationTestObject::class); Deprecation::outputNotices(); } /** * @doesNotPerformAssertions */ public function testDisabled() { if ($this->noticesWereEnabled) { $this->markTestSkipped('Notices are enabled for this project outside of this unit test'); } // test that no error error is raised because by default Deprecation is disabled $this->myDeprecatedMethod(); Deprecation::outputNotices(); } // The following tests would be better to put in the silverstripe/config module, however this is not // possible to do in a clean way as the config for the DeprecationTestObject will not load if it // is inside the silverstripe/config directory, as there is no _config.php file or _config folder. // Adding a _config.php file will break existing unit-tests within silverstripe/config // It is possible to put DeprecationTestObject in framework and the unit tests in config, however // that's probably messier then just having everything within framework public function testConfigGetFirst() { $message = implode(' ', [ 'Config SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject.first_config is deprecated.', 'My first config message.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); Config::inst()->get(DeprecationTestObject::class, 'first_config'); Deprecation::outputNotices(); } public function testConfigGetSecond() { $message = implode(' ', [ 'Config SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject.second_config is deprecated.', 'My second config message.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); Config::inst()->get(DeprecationTestObject::class, 'second_config'); Deprecation::outputNotices(); } public function testConfigGetThird() { $message = 'Config SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject.third_config is deprecated.'; $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); Config::inst()->get(DeprecationTestObject::class, 'third_config'); Deprecation::outputNotices(); } public function testConfigSet() { $message = implode(' ', [ 'Config SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject.first_config is deprecated.', 'My first config message.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); Config::modify()->set(DeprecationTestObject::class, 'first_config', 'abc'); Deprecation::outputNotices(); } public function testConfigMerge() { $message = implode(' ', [ 'Config SilverStripe\Dev\Tests\DeprecationTest\DeprecationTestObject.array_config is deprecated.', 'My array config message.' ]); $this->expectDeprecation(); $this->expectDeprecationMessage($message); Deprecation::enable(); Config::modify()->merge(DeprecationTestObject::class, 'array_config', ['abc']); Deprecation::outputNotices(); } public function provideConfigVsEnv() { return [ // env var not set - config setting is respected [ // false is returned when the env isn't set, so this simulates simply not having // set the variable in the first place 'envVal' => 'notset', 'configVal' => false, 'expected' => false, ], [ 'envVal' => 'notset', 'configVal' => true, 'expected' => true, ], // env var is set and truthy, config setting is ignored [ 'envVal' => true, 'configVal' => false, 'expected' => true, ], [ 'envVal' => true, 'configVal' => true, 'expected' => true, ], // env var is set and falsy, config setting is ignored [ 'envVal' => false, 'configVal' => false, 'expected' => false, ], [ 'envVal' => false, 'configVal' => true, 'expected' => false, ], ]; } /** * @dataProvider provideConfigVsEnv */ public function testEnabledConfigVsEnv($envVal, bool $configVal, bool $expected) { $this->runConfigVsEnvTest('SS_DEPRECATION_ENABLED', $envVal, $configVal, $expected); } /** * @dataProvider provideConfigVsEnv */ public function testShowHttpConfigVsEnv($envVal, bool $configVal, bool $expected) { $this->runConfigVsEnvTest('SS_DEPRECATION_SHOW_HTTP', $envVal, $configVal, $expected); } /** * @dataProvider provideConfigVsEnv */ public function testShowCliConfigVsEnv($envVal, bool $configVal, bool $expected) { $this->runConfigVsEnvTest('SS_DEPRECATION_SHOW_CLI', $envVal, $configVal, $expected); } private function runConfigVsEnvTest(string $varName, $envVal, bool $configVal, bool $expected) { $oldVars = Environment::getVariables(); if ($envVal === 'notset') { if (Environment::hasEnv($varName)) { $this->markTestSkipped("$varName is set, so we can't test behaviour when it's not"); return; } } else { Environment::setEnv($varName, $envVal); } switch ($varName) { case 'SS_DEPRECATION_ENABLED': if ($configVal) { Deprecation::enable(); } else { Deprecation::disable(); } $result = Deprecation::isEnabled(); break; case 'SS_DEPRECATION_SHOW_HTTP': $oldShouldShow = Deprecation::shouldShowForHttp(); Deprecation::setShouldShowForHttp($configVal); $result = Deprecation::shouldShowForHttp(); Deprecation::setShouldShowForHttp($oldShouldShow); break; case 'SS_DEPRECATION_SHOW_CLI': $oldShouldShow = Deprecation::shouldShowForCli(); Deprecation::setShouldShowForCli($configVal); $result = Deprecation::shouldShowForCli(); Deprecation::setShouldShowForCli($oldShouldShow); break; } Environment::setVariables($oldVars); $this->assertSame($expected, $result); } public function provideVarAsBoolean() { return [ [ 'rawValue' => true, 'expected' => true, ], [ 'rawValue' => 'true', 'expected' => true, ], [ 'rawValue' => 1, 'expected' => true, ], [ 'rawValue' => '1', 'expected' => true, ], [ 'rawValue' => 'on', 'expected' => true, ], [ 'rawValue' => false, 'expected' => false, ], [ 'rawValue' => 'false', 'expected' => false, ], [ 'rawValue' => 0, 'expected' => false, ], [ 'rawValue' => '0', 'expected' => false, ], [ 'rawValue' => 'off', 'expected' => false, ], ]; } /** * @dataProvider provideVarAsBoolean */ public function testVarAsBoolean($rawValue, bool $expected) { $reflectionVarAsBoolean = new ReflectionMethod(Deprecation::class, 'varAsBoolean'); $reflectionVarAsBoolean->setAccessible(true); $this->assertSame($expected, $reflectionVarAsBoolean->invoke(null, $rawValue)); } }