Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Process isolation blocks infinitely upon fatal error in child process #1340

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions src/Util/PHP/Windows.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
* @since File available since Release 3.5.12
*/

use SebastianBergmann\Environment\Runtime;

/**
* Windows utility for PHP sub-processes.
*
Expand All @@ -61,6 +63,57 @@ class PHPUnit_Util_PHP_Windows extends PHPUnit_Util_PHP_Default
*/
private $tempFile;

/**
* {@inheritdoc}
*
* Reading from STDOUT or STDERR hangs forever on Windows if the output is
* too large.
*
* @see https://bugs.php.net/bug.php?id=51800
*/
public function runJob($job, array $settings = array())
{
$runtime = new Runtime;

if (false === $stdout_handle = tmpfile()) {
throw new PHPUnit_Framework_Exception(
'A temporary file could not be created; verify that your TEMP environment variable is writable'
);
}

$process = proc_open(
$runtime->getBinary() . $this->settingsToParameters($settings),
array(
0 => array('pipe', 'r'),
1 => $stdout_handle,
2 => array('pipe', 'w')
),
$pipes
);

if (!is_resource($process)) {
throw new PHPUnit_Framework_Exception(
'Unable to spawn worker process'
);
}

$this->process($pipes[0], $job);
fclose($pipes[0]);

$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);

proc_close($process);

rewind($stdout_handle);
$stdout = stream_get_contents($stdout_handle);
fclose($stdout_handle);

$this->cleanup();

return array('stdout' => $stdout, 'stderr' => $stderr);
}

/**
* @param resource $pipe
* @param string $job
Expand Down
34 changes: 34 additions & 0 deletions tests/Regression/GitHub/1340.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
GH-1340: Process isolation blocks infinitely upon fatal error
--FILE--
<?php

$_SERVER['argv'][1] = '--no-configuration';
$_SERVER['argv'][3] = 'Issue1340Test';
$_SERVER['argv'][4] = dirname(__FILE__).'/1340/Issue1340Test.php';

require __DIR__ . '/../../bootstrap.php';
PHPUnit_TextUI_Command::main();
?>
--EXPECTF--
PHPUnit %s by Sebastian Bergmann.
%A
.E.EE

Time: %s, Memory: %sMb

There were 3 errors:

1) Issue1340Test::testLargeStderrOutputDoesNotBlockInIsolation
PHPUnit_Framework_Exception: testLargeStderrOutputDoesNotBlockInIsolation: stderr:%d
%A
2) Issue1340Test::testPhpNoticeWithStderrOutputIsAnError
PHPUnit_Framework_Exception: shutdown: stderr:%d
%A
3) Issue1340Test::testFatalErrorDoesNotPass
PHPUnit_Framework_Exception: Fatal error: Call to undefined function undefined_function() in %s on line %d
%A
shutdown: stderr:%d
%A
FAILURES!
Tests: 5, Assertions: 3, Errors: 3.
69 changes: 69 additions & 0 deletions tests/Regression/GitHub/1340/Issue1340Test.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php
/**
* @see https://bugs.php.net/bug.php?id=51800
*/
class Issue1340Test extends PHPUnit_Framework_TestCase
{
private static function get4KB()
{
return str_repeat('1', 4096 + 1);
}

/**
* Also fails despite no isolation, because a phpt test is executed in
* subprocess on its own.
*/
public function testLargeStderrOutputDoesNotBlock()
{
// STDERR of a phpt test is not caught/validated at this point, so this
// error output does not cause this test to fail.
// @see https://github.com/sebastianbergmann/phpunit/issues/1169
error_log("\n" . __FUNCTION__ . ": stderr:" . self::get4KB() . "\n");
$this->assertTrue(true);
}

/**
* @runInSeparateProcess
*/
public function testLargeStderrOutputDoesNotBlockInIsolation()
{
error_log("\n" . __FUNCTION__ . ": stderr:" . self::get4KB() . "\n");
$this->assertTrue(true);
}

/**
* @runInSeparateProcess
* @expectedException \PHPUnit_Framework_Error_Notice
* @expectedExceptionMessage Undefined variable: foo
*/
public function testPhpNoticeIsCaught()
{
$bar = $foo['foo'];
}

/**
* @runInSeparateProcess
* @expectedException \PHPUnit_Framework_Error_Notice
* @expectedExceptionMessage Undefined variable: foo
*/
public function testPhpNoticeWithStderrOutputIsAnError()
{
register_shutdown_function(__CLASS__ . '::onShutdown');
$bar = $foo['foo'];
}

/**
* @runInSeparateProcess
*/
public function testFatalErrorDoesNotPass()
{
register_shutdown_function(__CLASS__ . '::onShutdown');
$undefined = 'undefined_function';
$undefined();
}

public static function onShutdown() {
echo "\nshutdown: stdout:", self::get4KB(), "\n";
error_log("\nshutdown: stderr:" . self::get4KB());
}
}