Skip to content

Commit f3a4356

Browse files
Merge branch '10.5'
2 parents def0086 + 93aa92b commit f3a4356

File tree

5 files changed

+269
-12
lines changed

5 files changed

+269
-12
lines changed

.psalm/baseline.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,10 @@
334334
<MissingThrowsDocblock>
335335
<code>getMethod</code>
336336
</MissingThrowsDocblock>
337+
<PossiblyNullArgument>
338+
<code><![CDATA[$this->backupGlobalErrorHandlers]]></code>
339+
<code><![CDATA[$this->backupGlobalExceptionHandlers]]></code>
340+
</PossiblyNullArgument>
337341
<PropertyNotSetInConstructor>
338342
<code>$outputBufferingLevel</code>
339343
</PropertyNotSetInConstructor>

src/Framework/TestCase.php

Lines changed: 139 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
use const PHP_URL_PATH;
2121
use function array_keys;
2222
use function array_merge;
23+
use function array_reverse;
2324
use function array_values;
2425
use function assert;
2526
use function basename;
@@ -28,6 +29,7 @@
2829
use function clearstatcache;
2930
use function count;
3031
use function defined;
32+
use function error_clear_last;
3133
use function explode;
3234
use function getcwd;
3335
use function implode;
@@ -49,6 +51,10 @@
4951
use function pathinfo;
5052
use function preg_match;
5153
use function preg_replace;
54+
use function restore_error_handler;
55+
use function restore_exception_handler;
56+
use function set_error_handler;
57+
use function set_exception_handler;
5258
use function setlocale;
5359
use function sprintf;
5460
use function str_contains;
@@ -125,14 +131,24 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
125131
*/
126132
private array $backupStaticPropertiesExcludeList = [];
127133
private ?Snapshot $snapshot = null;
128-
private ?bool $runClassInSeparateProcess = null;
129-
private ?bool $runTestInSeparateProcess = null;
130-
private bool $preserveGlobalState = false;
131-
private bool $inIsolation = false;
132-
private ?string $expectedException = null;
133-
private ?string $expectedExceptionMessage = null;
134-
private ?string $expectedExceptionMessageRegExp = null;
135-
private null|int|string $expectedExceptionCode = null;
134+
135+
/**
136+
* @psalm-var list<callable>
137+
*/
138+
private ?array $backupGlobalErrorHandlers = null;
139+
140+
/**
141+
* @psalm-var list<callable>
142+
*/
143+
private ?array $backupGlobalExceptionHandlers = null;
144+
private ?bool $runClassInSeparateProcess = null;
145+
private ?bool $runTestInSeparateProcess = null;
146+
private bool $preserveGlobalState = false;
147+
private bool $inIsolation = false;
148+
private ?string $expectedException = null;
149+
private ?string $expectedExceptionMessage = null;
150+
private ?string $expectedExceptionMessageRegExp = null;
151+
private null|int|string $expectedExceptionCode = null;
136152

137153
/**
138154
* @psalm-var list<ExecutionOrderDependency>
@@ -422,13 +438,16 @@ final public function runBare(): void
422438
{
423439
$emitter = Event\Facade::emitter();
424440

441+
error_clear_last();
442+
clearstatcache();
443+
425444
$emitter->testPreparationStarted(
426445
$this->valueObjectForEvents(),
427446
);
428447

429448
$this->snapshotGlobalState();
449+
$this->snapshotGlobalErrorExceptionHandlers();
430450
$this->startOutputBuffering();
431-
clearstatcache();
432451

433452
$hookMethods = (new HookMethods)->hookMethods(static::class);
434453
$hasMetRequirements = false;
@@ -581,6 +600,7 @@ final public function runBare(): void
581600
chdir($currentWorkingDirectory);
582601
}
583602

603+
$this->restoreGlobalErrorExceptionHandlers();
584604
$this->restoreGlobalState();
585605
$this->unregisterCustomComparators();
586606
$this->cleanupIniSettings();
@@ -1795,6 +1815,116 @@ private function stopOutputBuffering(): bool
17951815
return true;
17961816
}
17971817

1818+
private function snapshotGlobalErrorExceptionHandlers(): void
1819+
{
1820+
$this->backupGlobalErrorHandlers = $this->getActiveErrorHandlers();
1821+
$this->backupGlobalExceptionHandlers = $this->getActiveExceptionHandlers();
1822+
}
1823+
1824+
private function restoreGlobalErrorExceptionHandlers(): void
1825+
{
1826+
$activeErrorHandlers = $this->getActiveErrorHandlers();
1827+
$activeExceptionHandlers = $this->getActiveExceptionHandlers();
1828+
1829+
$message = null;
1830+
1831+
if ($activeErrorHandlers !== $this->backupGlobalErrorHandlers) {
1832+
if (count($activeErrorHandlers) > count($this->backupGlobalErrorHandlers)) {
1833+
$message = 'Test code or tested code did not remove its own error handlers';
1834+
} else {
1835+
$message = 'Test code or tested code removed error handlers other than its own';
1836+
}
1837+
1838+
foreach ($activeErrorHandlers as $handler) {
1839+
restore_error_handler();
1840+
}
1841+
1842+
foreach ($this->backupGlobalErrorHandlers as $handler) {
1843+
set_error_handler($handler);
1844+
}
1845+
}
1846+
1847+
if ($activeExceptionHandlers !== $this->backupGlobalExceptionHandlers) {
1848+
if (count($activeExceptionHandlers) > count($this->backupGlobalExceptionHandlers)) {
1849+
$message = 'Test code or tested code did not remove its own exception handlers';
1850+
} else {
1851+
$message = 'Test code or tested code removed exception handlers other than its own';
1852+
}
1853+
1854+
foreach ($activeExceptionHandlers as $handler) {
1855+
restore_exception_handler();
1856+
}
1857+
1858+
foreach ($this->backupGlobalExceptionHandlers as $handler) {
1859+
set_exception_handler($handler);
1860+
}
1861+
}
1862+
1863+
$this->backupGlobalErrorHandlers = null;
1864+
$this->backupGlobalExceptionHandlers = null;
1865+
1866+
if ($message !== null) {
1867+
Event\Facade::emitter()->testConsideredRisky(
1868+
$this->valueObjectForEvents(),
1869+
$message,
1870+
);
1871+
1872+
$this->status = TestStatus::risky($message);
1873+
}
1874+
}
1875+
1876+
/**
1877+
* @return list<callable>
1878+
*/
1879+
private function getActiveErrorHandlers(): array
1880+
{
1881+
$res = [];
1882+
1883+
while (true) {
1884+
$previousHandler = set_error_handler(static fn () => false);
1885+
restore_error_handler();
1886+
1887+
if ($previousHandler === null) {
1888+
break;
1889+
}
1890+
$res[] = $previousHandler;
1891+
restore_error_handler();
1892+
}
1893+
$res = array_reverse($res);
1894+
1895+
foreach ($res as $handler) {
1896+
set_error_handler($handler);
1897+
}
1898+
1899+
return $res;
1900+
}
1901+
1902+
/**
1903+
* @return list<callable>
1904+
*/
1905+
private function getActiveExceptionHandlers(): array
1906+
{
1907+
$res = [];
1908+
1909+
while (true) {
1910+
$previousHandler = set_exception_handler(static fn () => null);
1911+
restore_exception_handler();
1912+
1913+
if ($previousHandler === null) {
1914+
break;
1915+
}
1916+
$res[] = $previousHandler;
1917+
restore_exception_handler();
1918+
}
1919+
$res = array_reverse($res);
1920+
1921+
foreach ($res as $handler) {
1922+
set_exception_handler($handler);
1923+
}
1924+
1925+
return $res;
1926+
}
1927+
17981928
private function snapshotGlobalState(): void
17991929
{
18001930
if ($this->runTestInSeparateProcess || $this->inIsolation ||

src/Framework/TestRunner.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
use function assert;
1414
use function class_exists;
1515
use function defined;
16-
use function error_clear_last;
1716
use function extension_loaded;
1817
use function get_include_path;
1918
use function hrtime;
@@ -79,8 +78,6 @@ public function run(TestCase $test): void
7978
$risky = false;
8079
$skipped = false;
8180

82-
error_clear_last();
83-
8481
if ($this->shouldErrorHandlerBeUsed($test)) {
8582
ErrorHandler::instance()->enable();
8683
}

tests/end-to-end/regression/5592.phpt

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
--TEST--
2+
https://github.com/sebastianbergmann/phpunit/pull/5592
3+
--FILE--
4+
<?php declare(strict_types=1);
5+
$_SERVER['argv'][] = '--do-not-cache-result';
6+
$_SERVER['argv'][] = '--no-configuration';
7+
$_SERVER['argv'][] = __DIR__ . '/5592/Issue5592Test.php';
8+
9+
set_exception_handler(static fn () => null);
10+
11+
require_once __DIR__ . '/../../bootstrap.php';
12+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
13+
--EXPECTF--
14+
PHPUnit %s by Sebastian Bergmann and contributors.
15+
16+
Runtime: %s
17+
18+
.FF.FF 6 / 6 (100%)
19+
20+
Time: %s, Memory: %s
21+
22+
There were 4 failures:
23+
24+
1) PHPUnit\TestFixture\Issue5592Test::testAddedErrorHandler
25+
Failed asserting that false is true.
26+
27+
%sIssue5592Test.php:%i
28+
29+
2) PHPUnit\TestFixture\Issue5592Test::testRemovedErrorHandler
30+
Failed asserting that false is true.
31+
32+
%sIssue5592Test.php:%i
33+
34+
3) PHPUnit\TestFixture\Issue5592Test::testAddedExceptionHandler
35+
Failed asserting that false is true.
36+
37+
%sIssue5592Test.php:%i
38+
39+
4) PHPUnit\TestFixture\Issue5592Test::testRemovedExceptionHandler
40+
Failed asserting that false is true.
41+
42+
%sIssue5592Test.php:%i
43+
44+
--
45+
46+
There were 4 risky tests:
47+
48+
1) PHPUnit\TestFixture\Issue5592Test::testAddedErrorHandler
49+
Test code or tested code did not remove its own error handlers
50+
51+
%sIssue5592Test.php:%i
52+
53+
2) PHPUnit\TestFixture\Issue5592Test::testRemovedErrorHandler
54+
Test code or tested code removed error handlers other than its own
55+
56+
%sIssue5592Test.php:%i
57+
58+
3) PHPUnit\TestFixture\Issue5592Test::testAddedExceptionHandler
59+
Test code or tested code did not remove its own exception handlers
60+
61+
%sIssue5592Test.php:%i
62+
63+
4) PHPUnit\TestFixture\Issue5592Test::testRemovedExceptionHandler
64+
Test code or tested code removed exception handlers other than its own
65+
66+
%sIssue5592Test.php:%i
67+
68+
FAILURES!
69+
Tests: 6, Assertions: 6, Failures: 4, Risky: 4.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php declare(strict_types=1);
2+
/*
3+
* This file is part of PHPUnit.
4+
*
5+
* (c) Sebastian Bergmann <[email protected]>
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;
11+
12+
use function restore_error_handler;
13+
use function restore_exception_handler;
14+
use function set_error_handler;
15+
use function set_exception_handler;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class Issue5592Test extends TestCase
19+
{
20+
public function testAddedAndRemovedErrorHandler(): void
21+
{
22+
set_error_handler(static fn () => false);
23+
restore_error_handler();
24+
$this->assertTrue(true);
25+
}
26+
27+
public function testAddedErrorHandler(): void
28+
{
29+
set_error_handler(static fn () => false);
30+
$this->assertTrue(false);
31+
}
32+
33+
public function testRemovedErrorHandler(): void
34+
{
35+
restore_error_handler();
36+
$this->assertTrue(false);
37+
}
38+
39+
public function testAddedAndRemovedExceptionHandler(): void
40+
{
41+
set_exception_handler(static fn () => null);
42+
restore_exception_handler();
43+
$this->assertTrue(true);
44+
}
45+
46+
public function testAddedExceptionHandler(): void
47+
{
48+
set_exception_handler(static fn () => null);
49+
$this->assertTrue(false);
50+
}
51+
52+
public function testRemovedExceptionHandler(): void
53+
{
54+
restore_exception_handler();
55+
$this->assertTrue(false);
56+
}
57+
}

0 commit comments

Comments
 (0)