diff --git a/src/Util/PHP/Windows.php b/src/Util/PHP/Windows.php index 17d97687563..d9b6ffc7be3 100644 --- a/src/Util/PHP/Windows.php +++ b/src/Util/PHP/Windows.php @@ -43,6 +43,8 @@ * @since File available since Release 3.5.12 */ +use SebastianBergmann\Environment\Runtime; + /** * Windows utility for PHP sub-processes. * @@ -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 diff --git a/tests/Regression/GitHub/1340.phpt b/tests/Regression/GitHub/1340.phpt new file mode 100644 index 00000000000..4e1cd1fe466 --- /dev/null +++ b/tests/Regression/GitHub/1340.phpt @@ -0,0 +1,34 @@ +--TEST-- +GH-1340: Process isolation blocks infinitely upon fatal error +--FILE-- + +--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. \ No newline at end of file diff --git a/tests/Regression/GitHub/1340/Issue1340Test.php b/tests/Regression/GitHub/1340/Issue1340Test.php new file mode 100644 index 00000000000..580d98ab293 --- /dev/null +++ b/tests/Regression/GitHub/1340/Issue1340Test.php @@ -0,0 +1,69 @@ +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()); + } +}