Skip to content

Commit 0064c3c

Browse files
Merge branch '12.3'
2 parents a561bbf + cc6414a commit 0064c3c

File tree

6 files changed

+239
-61
lines changed

6 files changed

+239
-61
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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\Framework;
11+
12+
/**
13+
* @no-named-arguments Parameter names are not covered by the backward compatibility promise for PHPUnit
14+
*
15+
* @internal This class is not covered by the backward compatibility promise for PHPUnit
16+
*/
17+
final class ErrorLogNotWritableException extends Exception
18+
{
19+
public function __construct()
20+
{
21+
parent::__construct('Could not create writable file for error_log()');
22+
}
23+
}

src/Framework/TestCase.php

Lines changed: 105 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
use function is_int;
3434
use function is_object;
3535
use function is_string;
36+
use function is_writable;
3637
use function libxml_clear_errors;
3738
use function method_exists;
3839
use function ob_end_clean;
@@ -219,6 +220,12 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
219220
*/
220221
private array $expectedUserDeprecationMessageRegularExpression = [];
221222

223+
/**
224+
* @var false|resource
225+
*/
226+
private mixed $errorLogCapture = false;
227+
private false|string $previousErrorLogTarget = false;
228+
222229
/**
223230
* @param non-empty-string $name
224231
*
@@ -1297,37 +1304,15 @@ private function runTest(): mixed
12971304
{
12981305
$testArguments = array_merge($this->data, array_values($this->dependencyInput));
12991306

1300-
$capture = tmpfile();
1301-
assert($capture !== false);
1302-
1303-
if (ini_get('display_errors') === '0') {
1304-
ShutdownHandler::setMessage('Fatal error: Premature end of PHP process. Use display_errors=On to see the error message.');
1305-
}
1306-
$errorLogPrevious = ini_set('error_log', stream_get_meta_data($capture)['uri']);
1307+
$this->startErrorLogCapture();
13071308

13081309
try {
13091310
/** @phpstan-ignore method.dynamicName */
13101311
$testResult = $this->{$this->methodName}(...$testArguments);
13111312

1312-
$errorLogOutput = stream_get_contents($capture);
1313-
1314-
if ($this->expectErrorLog) {
1315-
$this->assertNotEmpty($errorLogOutput, 'Test did not call error_log().');
1316-
} else {
1317-
if ($errorLogOutput !== false) {
1318-
// strip date from logged error, see https://github.com/php/php-src/blob/c696087e323263e941774ebbf902ac249774ec9f/main/main.c#L905
1319-
print preg_replace('/\[.+\] /', '', $errorLogOutput);
1320-
}
1321-
}
1313+
$this->verifyErrorLogExpectation();
13221314
} catch (Throwable $exception) {
1323-
if (!$this->expectErrorLog) {
1324-
$errorLogOutput = stream_get_contents($capture);
1325-
1326-
if ($errorLogOutput !== false) {
1327-
// strip date from logged error, see https://github.com/php/php-src/blob/c696087e323263e941774ebbf902ac249774ec9f/main/main.c#L905
1328-
print preg_replace('/\[.+\] /', '', $errorLogOutput);
1329-
}
1330-
}
1315+
$this->handleErrorLogError();
13311316

13321317
if (!$this->shouldExceptionExpectationsBeVerified($exception)) {
13331318
throw $exception;
@@ -1337,17 +1322,20 @@ private function runTest(): mixed
13371322

13381323
return null;
13391324
} finally {
1340-
ShutdownHandler::resetMessage();
1341-
fclose($capture);
1342-
1343-
ini_set('error_log', $errorLogPrevious);
1325+
$this->stopErrorLogCapture();
13441326
}
13451327

13461328
$this->expectedExceptionWasNotRaised();
13471329

13481330
return $testResult;
13491331
}
13501332

1333+
private function stripDateFromErrorLog(string $log): string
1334+
{
1335+
// https://github.com/php/php-src/blob/c696087e323263e941774ebbf902ac249774ec9f/main/main.c#L905
1336+
return preg_replace('/\[\d+-\w+-\d+ \d+:\d+:\d+ [^\r\n[\]]+?\] /', '', $log);
1337+
}
1338+
13511339
/**
13521340
* @throws ExpectationFailedException
13531341
*/
@@ -2325,6 +2313,94 @@ private function handleExceptionFromInvokedCountMockObjectRule(Throwable $t): vo
23252313
}
23262314
}
23272315

2316+
private function startErrorLogCapture(): void
2317+
{
2318+
if (ini_get('display_errors') === '0') {
2319+
ShutdownHandler::setMessage('Fatal error: Premature end of PHP process. Use display_errors=On to see the error message.');
2320+
}
2321+
2322+
$errorLogCapture = tmpfile();
2323+
2324+
if ($errorLogCapture === false) {
2325+
return;
2326+
}
2327+
2328+
$capturePath = stream_get_meta_data($errorLogCapture)['uri'];
2329+
2330+
if (!@is_writable($capturePath)) {
2331+
return;
2332+
}
2333+
2334+
$this->errorLogCapture = $errorLogCapture;
2335+
$this->previousErrorLogTarget = ini_set('error_log', $capturePath);
2336+
}
2337+
2338+
/**
2339+
* @throws ErrorLogNotWritableException
2340+
*/
2341+
private function verifyErrorLogExpectation(): void
2342+
{
2343+
if ($this->errorLogCapture === false) {
2344+
if ($this->expectErrorLog) {
2345+
throw new ErrorLogNotWritableException;
2346+
}
2347+
2348+
return;
2349+
}
2350+
2351+
$errorLogOutput = stream_get_contents($this->errorLogCapture);
2352+
2353+
if ($this->expectErrorLog) {
2354+
$this->assertNotEmpty($errorLogOutput, 'error_log() was not called');
2355+
2356+
return;
2357+
}
2358+
2359+
if ($errorLogOutput === false) {
2360+
return;
2361+
}
2362+
2363+
print $this->stripDateFromErrorLog($errorLogOutput);
2364+
}
2365+
2366+
private function handleErrorLogError(): void
2367+
{
2368+
if ($this->errorLogCapture === false) {
2369+
return;
2370+
}
2371+
2372+
if ($this->expectErrorLog) {
2373+
return;
2374+
}
2375+
2376+
$errorLogOutput = stream_get_contents($this->errorLogCapture);
2377+
2378+
if ($errorLogOutput !== false) {
2379+
print $this->stripDateFromErrorLog($errorLogOutput);
2380+
}
2381+
}
2382+
2383+
private function stopErrorLogCapture(): void
2384+
{
2385+
if ($this->errorLogCapture === false) {
2386+
return;
2387+
}
2388+
2389+
ShutdownHandler::resetMessage();
2390+
2391+
fclose($this->errorLogCapture);
2392+
2393+
$this->errorLogCapture = false;
2394+
2395+
if ($this->previousErrorLogTarget === false) {
2396+
return;
2397+
}
2398+
2399+
ini_set('error_log', $this->previousErrorLogTarget);
2400+
2401+
$this->previousErrorLogTarget = false;
2402+
}
2403+
23282404
/**
23292405
* Creates a test stub for the specified interface or class.
23302406
*
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
--TEST--
2+
https://github.com/sebastianbergmann/phpunit/issues/6197
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/ExpectErrorLogFailTest.php';
9+
10+
/*
11+
* Expected result should match the result of expect-error-log-fail-with-open_basedir.phpt test,
12+
* but at least one of these feature requests needs to be implemented in php-src:
13+
* - https://github.com/php/php-src/issues/17817
14+
* - https://github.com/php/php-src/issues/18530
15+
*
16+
* Until then, mark the test result as incomplete when TestCase::expectErrorLog() was called and an error_log file
17+
* could not be created (because of open_basedir php.ini in effect, readonly filesystem...).
18+
*/
19+
20+
ini_set('open_basedir', (ini_get('open_basedir') ? ini_get('open_basedir') . PATH_SEPARATOR : '') . dirname(__DIR__, 3));
21+
22+
require_once __DIR__ . '/../../bootstrap.php';
23+
24+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
25+
--EXPECTF--
26+
PHPUnit Started (PHPUnit %s using %s)
27+
Test Runner Configured
28+
Event Facade Sealed
29+
Test Suite Loaded (1 test)
30+
Test Runner Started
31+
Test Suite Sorted
32+
Test Runner Execution Started (1 test)
33+
Test Suite Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test)
34+
Test Preparation Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
35+
Test Prepared (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
36+
Test Errored (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
37+
Could not create writable file for error_log()
38+
Test Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
39+
Test Suite Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test)
40+
Test Runner Execution Finished
41+
Test Runner Finished
42+
PHPUnit Finished (Shell Exit Code: 2)

tests/end-to-end/generic/expect-error-log-fail.phpt

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,28 @@ https://github.com/sebastianbergmann/phpunit/issues/2155
44
<?php declare(strict_types=1);
55
$_SERVER['argv'][] = '--do-not-cache-result';
66
$_SERVER['argv'][] = '--no-configuration';
7-
$_SERVER['argv'][] = '--testdox';
7+
$_SERVER['argv'][] = '--debug';
88
$_SERVER['argv'][] = __DIR__ . '/_files/ExpectErrorLogFailTest.php';
99

1010
require_once __DIR__ . '/../../bootstrap.php';
1111

1212
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
1313
--EXPECTF--
14-
PHPUnit %s by Sebastian Bergmann and contributors.
15-
16-
Runtime: %s
17-
18-
F 1 / 1 (100%)
19-
20-
Time: %s, Memory: %s
21-
22-
Expect Error Log Fail (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFail)
23-
✘ One
24-
25-
│ Test did not call error_log().
26-
Failed asserting that a string is not empty.
27-
28-
29-
30-
FAILURES!
31-
Tests: 1, Assertions: 2, Failures: 1.
14+
PHPUnit Started (PHPUnit %s using %s)
15+
Test Runner Configured
16+
Event Facade Sealed
17+
Test Suite Loaded (1 test)
18+
Test Runner Started
19+
Test Suite Sorted
20+
Test Runner Execution Started (1 test)
21+
Test Suite Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test)
22+
Test Preparation Started (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
23+
Test Prepared (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
24+
Test Failed (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
25+
error_log() was not called
26+
Failed asserting that a string is not empty.
27+
Test Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest::testOne)
28+
Test Suite Finished (PHPUnit\TestFixture\ExpectNoErrorLog\ExpectErrorLogFailTest, 1 test)
29+
Test Runner Execution Finished
30+
Test Runner Finished
31+
PHPUnit Finished (Shell Exit Code: 1)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
https://github.com/sebastianbergmann/phpunit/issues/6197
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/ExpectErrorLogTest.php';
9+
10+
ini_set('open_basedir', (ini_get('open_basedir') ? ini_get('open_basedir') . PATH_SEPARATOR : '') . dirname(__DIR__, 3));
11+
12+
require_once __DIR__ . '/../../bootstrap.php';
13+
14+
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
15+
--EXPECTF--
16+
PHPUnit Started (PHPUnit %s using %s)
17+
Test Runner Configured
18+
Event Facade Sealed
19+
Test Suite Loaded (1 test)
20+
Test Runner Started
21+
Test Suite Sorted
22+
Test Runner Execution Started (1 test)
23+
Test Suite Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test)
24+
Test Preparation Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
25+
Test Prepared (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
26+
logged a side effect
27+
Test Errored (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
28+
Could not create writable file for error_log()
29+
Test Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
30+
Test Suite Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test)
31+
Test Runner Execution Finished
32+
Test Runner Finished
33+
PHPUnit Finished (Shell Exit Code: 2)

tests/end-to-end/generic/expect-error-log.phpt

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,26 @@ https://github.com/sebastianbergmann/phpunit/issues/2155
44
<?php declare(strict_types=1);
55
$_SERVER['argv'][] = '--do-not-cache-result';
66
$_SERVER['argv'][] = '--no-configuration';
7-
$_SERVER['argv'][] = '--testdox';
7+
$_SERVER['argv'][] = '--debug';
88
$_SERVER['argv'][] = __DIR__ . '/_files/ExpectErrorLogTest.php';
99

1010
require_once __DIR__ . '/../../bootstrap.php';
1111

1212
(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
1313
--EXPECTF--
14-
PHPUnit %s by Sebastian Bergmann and contributors.
15-
16-
Runtime: %s
17-
18-
. 1 / 1 (100%)
19-
20-
Time: %s, Memory: %s
21-
22-
Expect Error Log (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLog)
23-
✔ One
24-
25-
OK (1 test, 2 assertions)
14+
PHPUnit Started (PHPUnit %s using %s)
15+
Test Runner Configured
16+
Event Facade Sealed
17+
Test Suite Loaded (1 test)
18+
Test Runner Started
19+
Test Suite Sorted
20+
Test Runner Execution Started (1 test)
21+
Test Suite Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test)
22+
Test Preparation Started (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
23+
Test Prepared (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
24+
Test Passed (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
25+
Test Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest::testOne)
26+
Test Suite Finished (PHPUnit\TestFixture\ExpectErrorLog\ExpectErrorLogTest, 1 test)
27+
Test Runner Execution Finished
28+
Test Runner Finished
29+
PHPUnit Finished (Shell Exit Code: 0)

0 commit comments

Comments
 (0)