Skip to content

Commit

Permalink
[10.x] Allow deprecation logging in tests (#49457)
Browse files Browse the repository at this point in the history
* Allow more config to be controllable

* Introduce configurable value

* Use env, not config

* make mocks strict
  • Loading branch information
timacdonald authored Dec 21, 2023
1 parent 1a84e34 commit 0c68ae1
Show file tree
Hide file tree
Showing 2 changed files with 113 additions and 50 deletions.
3 changes: 2 additions & 1 deletion src/Illuminate/Foundation/Bootstrap/HandleExceptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Log\LogManager;
use Illuminate\Support\Env;
use Monolog\Handler\NullHandler;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\ErrorHandler\Error\FatalError;
Expand Down Expand Up @@ -118,7 +119,7 @@ protected function shouldIgnoreDeprecationErrors()
{
return ! class_exists(LogManager::class)
|| ! static::$app->hasBeenBootstrapped()
|| static::$app->runningUnitTests();
|| (static::$app->runningUnitTests() && ! Env::get('LOG_DEPRECATIONS_WHILE_TESTING'));
}

/**
Expand Down
160 changes: 111 additions & 49 deletions tests/Foundation/Bootstrap/HandleExceptionsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Bootstrap\HandleExceptions;
use Illuminate\Log\LogManager;
use Illuminate\Support\Env;
use Mockery as m;
use Monolog\Handler\NullHandler;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use RuntimeException;

class HandleExceptionsTest extends TestCase
{
protected $app;
protected $config;
protected $handleExceptions;

protected function setUp(): void
{
Expand All @@ -27,40 +28,38 @@ protected function setUp(): void
$this->app->singleton('config', function () {
return $this->config;
});
}

$this->handleExceptions = new HandleExceptions();

with(new ReflectionClass($this->handleExceptions), function ($reflection) {
$property = $reflection->getProperty('app');

$property->setValue(
$this->handleExceptions,
tap($this->app, function ($app) {
$app->shouldReceive('runningUnitTests')->andReturn(false);
$app->shouldReceive('hasBeenBootstrapped')->andReturn(true);
})
);
protected function handleExceptions()
{
return tap(new HandleExceptions(), function ($instance) {
with(new ReflectionClass($instance), function ($reflection) use ($instance) {
$reflection->getProperty('app')->setValue($instance, $this->app);
});
});
}

protected function tearDown(): void
{
Application::setInstance(null);
m::close();
}

public function testPhpDeprecations()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->with('deprecations')->andReturnSelf();
$logger->shouldReceive('warning')->with(sprintf('%s in %s on line %s',
$logger->expects('channel')->with('deprecations')->andReturnSelf();
$logger->expects('warning')->with(sprintf('%s in %s on line %s',
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
17
));

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -72,14 +71,16 @@ public function testPhpDeprecationsWithStackTraces()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$this->config->set('logging.deprecations', [
'channel' => 'null',
'trace' => true,
]);

$logger->shouldReceive('channel')->with('deprecations')->andReturnSelf();
$logger->shouldReceive('warning')->with(
$logger->expects('channel')->with('deprecations')->andReturnSelf();
$logger->expects('warning')->with(
m::on(fn (string $message) => (bool) preg_match(
<<<REGEXP
#ErrorException: str_contains\(\): Passing null to parameter \#2 \(\\\$needle\) of type string is deprecated in /home/user/laravel/routes/web\.php:17
Expand All @@ -94,7 +95,7 @@ public function testPhpDeprecationsWithStackTraces()
))
);

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -106,20 +107,22 @@ public function testNullValueAsChannelUsesNullDriver()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$this->config->set('logging.deprecations', [
'channel' => null,
'trace' => false,
]);

$logger->shouldReceive('channel')->with('deprecations')->andReturnSelf();
$logger->shouldReceive('warning')->with(sprintf('%s in %s on line %s',
$logger->expects('channel')->with('deprecations')->andReturnSelf();
$logger->expects('warning')->with(sprintf('%s in %s on line %s',
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
17
));

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -136,15 +139,17 @@ public function testUserDeprecations()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->with('deprecations')->andReturnSelf();
$logger->shouldReceive('warning')->with(sprintf('%s in %s on line %s',
$logger->expects('channel')->with('deprecations')->andReturnSelf();
$logger->expects('warning')->with(sprintf('%s in %s on line %s',
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
17
));

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -156,14 +161,16 @@ public function testUserDeprecationsWithStackTraces()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$this->config->set('logging.deprecations', [
'channel' => 'null',
'trace' => true,
]);

$logger->shouldReceive('channel')->with('deprecations')->andReturnSelf();
$logger->shouldReceive('warning')->with(
$logger->expects('channel')->with('deprecations')->andReturnSelf();
$logger->expects('warning')->with(
m::on(fn (string $message) => (bool) preg_match(
<<<REGEXP
#ErrorException: str_contains\(\): Passing null to parameter \#2 \(\\\$needle\) of type string is deprecated in /home/user/laravel/routes/web\.php:17
Expand All @@ -178,7 +185,7 @@ public function testUserDeprecationsWithStackTraces()
))
);

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -197,7 +204,7 @@ public function testErrors()
$this->expectException(ErrorException::class);
$this->expectExceptionMessage('Something went wrong');

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_ERROR,
'Something went wrong',
'/home/user/laravel/src/Providers/AppServiceProvider.php',
Expand All @@ -209,9 +216,11 @@ public function testEnsuresDeprecationsDriver()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->andReturnSelf();
$logger->shouldReceive('warning');
$logger->expects('channel')->andReturnSelf();
$logger->expects('warning');

$this->config->set('logging.channels.stack', [
'driver' => 'stack',
Expand All @@ -220,7 +229,7 @@ public function testEnsuresDeprecationsDriver()
]);
$this->config->set('logging.deprecations', 'stack');

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -241,11 +250,13 @@ public function testEnsuresNullDeprecationsDriver()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->andReturnSelf();
$logger->shouldReceive('warning');
$logger->expects('channel')->andReturnSelf();
$logger->expects('warning');

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -262,11 +273,13 @@ public function testEnsuresNullLogDriver()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->andReturnSelf();
$logger->shouldReceive('warning');
$logger->expects('channel')->andReturnSelf();
$logger->expects('warning');

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -283,16 +296,18 @@ public function testDoNotOverrideExistingNullLogDriver()
{
$logger = m::mock(LogManager::class);
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$logger->shouldReceive('channel')->andReturnSelf();
$logger->shouldReceive('warning');
$logger->expects('channel')->andReturnSelf();
$logger->expects('warning');

$this->config->set('logging.channels.null', [
'driver' => 'monolog',
'handler' => CustomNullHandler::class,
]);

$this->handleExceptions->handleError(
$this->handleExceptions()->handleError(
E_USER_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -313,7 +328,50 @@ public function testNoDeprecationsDriverIfNoDeprecationsHereSend()

public function testIgnoreDeprecationIfLoggerUnresolvable()
{
$this->handleExceptions->handleError(
$this->app->expects('runningUnitTests')->andReturn(false);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
17
);
}

public function testItIgnoreDeprecationLoggingWhenRunningUnitTests()
{
$resolved = false;
$this->app->bind(LogManager::class, function () use (&$resolved) {
$resolved = true;

throw new RuntimeException();
});
$this->app->expects('runningUnitTests')->andReturn(true);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
17
);

$this->assertFalse($resolved);
}

public function testItCanForceViaConfigDeprecationLoggingWhenRunningUnitTests()
{
$logger = m::mock(LogManager::class);
$logger->expects('channel')->andReturnSelf();
$logger->expects('warning');
$this->app->instance(LogManager::class, $logger);
$this->app->expects('runningUnitTests')->andReturn(true);
$this->app->expects('hasBeenBootstrapped')->andReturn(true);

Env::getRepository()->set('LOG_DEPRECATIONS_WHILE_TESTING', true);

$this->handleExceptions()->handleError(
E_DEPRECATED,
'str_contains(): Passing null to parameter #2 ($needle) of type string is deprecated',
'/home/user/laravel/routes/web.php',
Expand All @@ -323,31 +381,35 @@ public function testIgnoreDeprecationIfLoggerUnresolvable()

public function testForgetApp()
{
$appResolver = fn () => with(new ReflectionClass($this->handleExceptions), function ($reflection) {
$instance = $this->handleExceptions();

$appResolver = fn () => with(new ReflectionClass($instance), function ($reflection) use ($instance) {
$property = $reflection->getProperty('app');

return $property->getValue($this->handleExceptions);
return $property->getValue($instance);
});

$this->assertNotNull($appResolver());

handleExceptions::forgetApp();
HandleExceptions::forgetApp();

$this->assertNull($appResolver());
}

public function testHandlerForgetsPreviousApp()
{
$appResolver = fn () => with(new ReflectionClass($this->handleExceptions), function ($reflection) {
$instance = $this->handleExceptions();

$appResolver = fn () => with(new ReflectionClass($instance), function ($reflection) use ($instance) {
$property = $reflection->getProperty('app');

return $property->getValue($this->handleExceptions);
return $property->getValue($instance);
});

$this->assertSame($this->app, $appResolver());

$this->handleExceptions->bootstrap($newApp = tap(m::mock(Application::class), function ($app) {
$app->shouldReceive('environment')->once()->andReturn(true);
$instance->bootstrap($newApp = tap(m::mock(Application::class), function ($app) {
$app->expects('environment')->andReturn(true);
}));

$this->assertNotSame($this->app, $appResolver());
Expand Down

0 comments on commit 0c68ae1

Please sign in to comment.