Skip to content

Commit 6b667a5

Browse files
1 parent 1d90b39 commit 6b667a5

6 files changed

Lines changed: 157 additions & 1 deletion

ChangeLog-13.2.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable changes of the PHPUnit 13.2 release series are documented in this fi
77
### Fixed
88

99
* [#6817](https://github.com/sebastianbergmann/phpunit/issues/6817): Issue is reported even when previously registered error handler turns the error into an exception
10+
* [#6818](https://github.com/sebastianbergmann/phpunit/issues/6818): Issue is reported when custom error handler checks `error_reporting()` output dynamically
1011

1112
## [13.2.3] - 2026-07-06
1213

src/Runner/ErrorHandler.php

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,32 @@ private function forwardToPreviousErrorHandler(int $errorNumber, string $errorSt
10061006
return false;
10071007
}
10081008

1009-
return (bool) ($this->previousErrorHandler)($errorNumber, $errorString, $errorFile, $errorLine);
1009+
/**
1010+
* The previously registered error handler must observe the error reporting level
1011+
* as it would be without PHPUnit's manipulation of it. The error reporting level
1012+
* is only restored when it currently is the masked level configured by enable():
1013+
* for errors suppressed using the @ operator it is the suppression mask set by
1014+
* PHP and for errors triggered before enable() masked it (or after test code
1015+
* changed it) it already is the level the previous error handler must observe.
1016+
*
1017+
* @see https://github.com/sebastianbergmann/phpunit/issues/6818
1018+
*/
1019+
$errorReportingLevel = error_reporting();
1020+
$restoreRequired = false;
1021+
1022+
if ($this->originalErrorReportingLevel !== null &&
1023+
$errorReportingLevel === ($this->originalErrorReportingLevel & self::UNHANDLEABLE_LEVELS)) {
1024+
error_reporting($this->originalErrorReportingLevel);
1025+
1026+
$restoreRequired = true;
1027+
}
1028+
1029+
try {
1030+
return (bool) ($this->previousErrorHandler)($errorNumber, $errorString, $errorFile, $errorLine);
1031+
} finally {
1032+
if ($restoreRequired) {
1033+
error_reporting($errorReportingLevel);
1034+
}
1035+
}
10101036
}
10111037
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\TestFixture\ErrorHandler;
11+
12+
use const E_USER_WARNING;
13+
use function trigger_error;
14+
use ErrorException;
15+
use PHPUnit\Framework\TestCase;
16+
17+
final class PreviousErrorHandlerErrorReportingTest extends TestCase
18+
{
19+
public function testWarningIsTurnedIntoException(): void
20+
{
21+
$this->expectException(ErrorException::class);
22+
23+
trigger_error('warning from test', E_USER_WARNING);
24+
}
25+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <sebastian@phpunit.de>
6+
*
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*/
10+
namespace PHPUnit\TestFixture\ErrorHandler;
11+
12+
use const E_USER_WARNING;
13+
use function trigger_error;
14+
use PHPUnit\Framework\TestCase;
15+
16+
final class PreviousErrorHandlerSuppressedTest extends TestCase
17+
{
18+
public function testSuppressedWarning(): void
19+
{
20+
@trigger_error('suppressed warning from test', E_USER_WARNING);
21+
22+
$this->assertTrue(true);
23+
}
24+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
--TEST--
2+
A previously registered error handler that checks error_reporting() dynamically observes the unmasked error reporting level
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = __DIR__ . '/_files/PreviousErrorHandlerErrorReportingTest.php';
9+
10+
require __DIR__ . '/../../bootstrap.php';
11+
12+
error_reporting(E_ALL);
13+
14+
set_error_handler(static function (int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool {
15+
if (($errorNumber & error_reporting()) === 0) {
16+
return false;
17+
}
18+
19+
throw new ErrorException($errorString, 0, $errorNumber, $errorFile, $errorLine);
20+
});
21+
22+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
23+
--EXPECTF--
24+
PHPUnit Started (PHPUnit %s using %s)
25+
Test Runner Configured
26+
Event Facade Sealed
27+
Test Suite Loaded (1 test)
28+
Test Runner Started
29+
Test Suite Sorted
30+
Test Runner Execution Started (1 test)
31+
Test Suite Started (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest, 1 test)
32+
Test Preparation Started (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest::testWarningIsTurnedIntoException)
33+
Test Prepared (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest::testWarningIsTurnedIntoException)
34+
Test Passed (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest::testWarningIsTurnedIntoException)
35+
Test Finished (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest::testWarningIsTurnedIntoException)
36+
Test Suite Finished (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerErrorReportingTest, 1 test)
37+
Test Runner Execution Finished
38+
Test Runner Finished
39+
PHPUnit Finished (Shell Exit Code: 0)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
--TEST--
2+
A previously registered error handler that checks error_reporting() dynamically observes the suppression mask for errors suppressed using the @ operator
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = '--debug';
8+
$_SERVER['argv'][] = __DIR__ . '/_files/PreviousErrorHandlerSuppressedTest.php';
9+
10+
require __DIR__ . '/../../bootstrap.php';
11+
12+
error_reporting(E_ALL);
13+
14+
set_error_handler(static function (int $errorNumber, string $errorString, string $errorFile, int $errorLine): bool {
15+
if (($errorNumber & error_reporting()) === 0) {
16+
return false;
17+
}
18+
19+
throw new ErrorException($errorString, 0, $errorNumber, $errorFile, $errorLine);
20+
});
21+
22+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
23+
--EXPECTF--
24+
PHPUnit Started (PHPUnit %s using %s)
25+
Test Runner Configured
26+
Event Facade Sealed
27+
Test Suite Loaded (1 test)
28+
Test Runner Started
29+
Test Suite Sorted
30+
Test Runner Execution Started (1 test)
31+
Test Suite Started (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest, 1 test)
32+
Test Preparation Started (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest::testSuppressedWarning)
33+
Test Prepared (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest::testSuppressedWarning)
34+
Test Triggered Warning (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest::testSuppressedWarning, suppressed using operator) in %s:%d
35+
suppressed warning from test
36+
Test Passed (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest::testSuppressedWarning)
37+
Test Finished (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest::testSuppressedWarning)
38+
Test Suite Finished (PHPUnit\TestFixture\ErrorHandler\PreviousErrorHandlerSuppressedTest, 1 test)
39+
Test Runner Execution Finished
40+
Test Runner Finished
41+
PHPUnit Finished (Shell Exit Code: 0)

0 commit comments

Comments
 (0)