From 1cac77d545926eac5113ea0f1d1ebc1a74111f7f Mon Sep 17 00:00:00 2001 From: sun Date: Thu, 24 Jul 2014 17:31:32 +0200 Subject: [PATCH] [interim] PHPUnit process isolation fixes. - Fix process isolation infinitely blocks on Windows. https://github.com/sebastianbergmann/phpunit/pull/1340 - Fix standard IO streams are not defined in process isolation. https://github.com/sebastianbergmann/phpunit/pull/1348 - Fix isolated child process leaks into parent process. https://github.com/sebastianbergmann/phpunit/issues/1351 --- core/vendor/composer/autoload_classmap.php | 1 + .../phpunit/phpunit/src/Framework/Error.php | 2 +- .../phpunit/src/Framework/Exception.php | 62 +++++++++ .../src/Framework/ExceptionWrapper.php | 128 ++++++++++++++++++ .../phpunit/src/Framework/TestFailure.php | 23 ++-- .../phpunit/src/Framework/TestResult.php | 3 + .../phpunit/src/TextUI/ResultPrinter.php | 33 +---- .../phpunit/phpunit/src/Util/Filter.php | 9 +- .../Util/PHP/Template/TestCaseMethod.tpl.dist | 6 + .../phpunit/phpunit/src/Util/PHP/Windows.php | 54 ++++++++ 10 files changed, 280 insertions(+), 41 deletions(-) create mode 100644 core/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php diff --git a/core/vendor/composer/autoload_classmap.php b/core/vendor/composer/autoload_classmap.php index 87d8298b3472..1c619006de36 100644 --- a/core/vendor/composer/autoload_classmap.php +++ b/core/vendor/composer/autoload_classmap.php @@ -64,6 +64,7 @@ 'PHPUnit_Framework_Error_Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', 'PHPUnit_Framework_Error_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', 'PHPUnit_Framework_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception.php', + 'PHPUnit_Framework_ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', 'PHPUnit_Framework_ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', 'PHPUnit_Framework_IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', 'PHPUnit_Framework_IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', diff --git a/core/vendor/phpunit/phpunit/src/Framework/Error.php b/core/vendor/phpunit/phpunit/src/Framework/Error.php index 65ced265cfdb..afac325f55a9 100644 --- a/core/vendor/phpunit/phpunit/src/Framework/Error.php +++ b/core/vendor/phpunit/phpunit/src/Framework/Error.php @@ -54,7 +54,7 @@ * @link http://www.phpunit.de/ * @since Class available since Release 2.2.0 */ -class PHPUnit_Framework_Error extends Exception +class PHPUnit_Framework_Error extends PHPUnit_Framework_Exception { /** * Constructor. diff --git a/core/vendor/phpunit/phpunit/src/Framework/Exception.php b/core/vendor/phpunit/phpunit/src/Framework/Exception.php index 832b3a19b4f7..4d25d3f43150 100644 --- a/core/vendor/phpunit/phpunit/src/Framework/Exception.php +++ b/core/vendor/phpunit/phpunit/src/Framework/Exception.php @@ -44,6 +44,25 @@ */ /** + * Base class for all PHPUnit Framework exceptions. + * + * Ensures that exceptions thrown during a test run do not leave stray + * references behind. + * + * Every Exception contains a stack trace. Each stack frame contains the 'args' + * of the called function. The function arguments can contain references to + * instantiated objects. The references prevent the objects from being + * destructed (until test results are eventually printed), so memory cannot be + * freed up. + * + * With enabled process isolation, test results are serialized in the child + * process and unserialized in the parent process. The stack trace of Exceptions + * may contain objects that cannot be serialized or unserialized (e.g., PDO + * connections). Unserializing user-space objects from the child process into + * the parent would break the intended encapsulation of process isolation. + * + * @see http://fabien.potencier.org/article/9/php-serialization-stack-traces-and-exceptions + * * @package PHPUnit * @subpackage Framework * @author Sebastian Bergmann @@ -54,4 +73,47 @@ */ class PHPUnit_Framework_Exception extends RuntimeException implements PHPUnit_Exception { + /** + * @var array + */ + protected $serializableTrace; + + public function __construct($message = '', $code = 0, Exception $previous = null) + { + parent::__construct($message, $code, $previous); + + $this->serializableTrace = $this->getTrace(); + foreach ($this->serializableTrace as $i => $call) { + unset($this->serializableTrace[$i]['args']); + } + } + + /** + * Returns the serializable trace (without 'args'). + * + * @return array + */ + public function getSerializableTrace() + { + return $this->serializableTrace; + } + + /** + * @return string + */ + public function __toString() + { + $string = PHPUnit_Framework_TestFailure::exceptionToString($this); + + if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; + } + + return $string; + } + + public function __sleep() + { + return array_keys(get_object_vars($this)); + } } diff --git a/core/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php b/core/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php new file mode 100644 index 000000000000..1105afa6ece8 --- /dev/null +++ b/core/vendor/phpunit/phpunit/src/Framework/ExceptionWrapper.php @@ -0,0 +1,128 @@ +. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * * Neither the name of Sebastian Bergmann nor the names of his + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * @package PHPUnit + * @subpackage Framework + * @author Sebastian Bergmann + * @copyright 2001-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://www.phpunit.de/ + * @since File available since Release 4.1.5 + */ + +/** + * Wraps Exceptions thrown by code under test. + * + * Re-instantiates Exceptions thrown by user-space code to retain their original + * class names, properties, and stack traces (but without arguments). + * + * Unlike PHPUnit_Framework_Exception, the complete stack of previous Exceptions + * is processed. + * + * @package PHPUnit + * @subpackage Framework + * @author Daniel F. Kudwien + * @copyright 2001-2014 Sebastian Bergmann + * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License + * @link http://www.phpunit.de/ + * @since Class available since Release 4.1.5 + */ +class PHPUnit_Framework_ExceptionWrapper extends PHPUnit_Framework_Exception +{ + /** + * @var string + */ + protected $classname; + + /** + * @var PHPUnit_Framework_ExceptionWrapper|null + */ + protected $previous; + + public function __construct(Exception $e) + { + // PDOException::getCode() is a string. + // @see http://php.net/manual/en/class.pdoexception.php#95812 + parent::__construct($e->getMessage(), (int) $e->getCode()); + + $this->classname = get_class($e); + $this->file = $e->getFile(); + $this->line = $e->getLine(); + + $this->serializableTrace = $e->getTrace(); + foreach ($this->serializableTrace as $i => $call) { + unset($this->serializableTrace[$i]['args']); + } + + if ($e->getPrevious()) { + $this->previous = new self($e->getPrevious()); + } + } + + /** + * @return string + */ + public function getClassname() + { + return $this->classname; + } + + /** + * @return PHPUnit_Framework_ExceptionWrapper + */ + public function getPreviousWrapped() + { + return $this->previous; + } + + /** + * @return string + */ + public function __toString() + { + $string = PHPUnit_Framework_TestFailure::exceptionToString($this); + + if ($trace = PHPUnit_Util_Filter::getFilteredStacktrace($this)) { + $string .= "\n" . $trace; + } + + if ($this->previous) { + $string .= "\nCaused by\n" . $this->previous; + } + + return $string; + } +} diff --git a/core/vendor/phpunit/phpunit/src/Framework/TestFailure.php b/core/vendor/phpunit/phpunit/src/Framework/TestFailure.php index 9da0b739efe4..835ea91cad5c 100644 --- a/core/vendor/phpunit/phpunit/src/Framework/TestFailure.php +++ b/core/vendor/phpunit/phpunit/src/Framework/TestFailure.php @@ -57,9 +57,9 @@ class PHPUnit_Framework_TestFailure { /** - * @var PHPUnit_Framework_Test + * @var string */ - protected $failedTest; + private $testName; /** * @var Exception @@ -74,7 +74,11 @@ class PHPUnit_Framework_TestFailure */ public function __construct(PHPUnit_Framework_Test $failedTest, Exception $thrownException) { - $this->failedTest = $failedTest; + if ($failedTest instanceof PHPUnit_Framework_SelfDescribing) { + $this->testName = $failedTest->toString(); + } else { + $this->testName = get_class($failedTest); + } $this->thrownException = $thrownException; } @@ -87,8 +91,7 @@ public function toString() { return sprintf( '%s: %s', - - $this->failedTest->toString(), + $this->testName, $this->thrownException->getMessage() ); } @@ -125,6 +128,8 @@ public static function exceptionToString(Exception $e) } } elseif ($e instanceof PHPUnit_Framework_Error) { $buffer = $e->getMessage() . "\n"; + } elseif ($e instanceof PHPUnit_Framework_ExceptionWrapper) { + $buffer = $e->getClassname() . ': ' . $e->getMessage() . "\n"; } else { $buffer = get_class($e) . ': ' . $e->getMessage() . "\n"; } @@ -133,13 +138,13 @@ public static function exceptionToString(Exception $e) } /** - * Gets the failed test. + * Returns the name of the failing test (including data set, if any). * - * @return PHPUnit_Framework_Test + * @return string */ - public function failedTest() + public function getTestName() { - return $this->failedTest; + return $this->testName; } /** diff --git a/core/vendor/phpunit/phpunit/src/Framework/TestResult.php b/core/vendor/phpunit/phpunit/src/Framework/TestResult.php index f373b082f83a..7a34bc1cb78a 100644 --- a/core/vendor/phpunit/phpunit/src/Framework/TestResult.php +++ b/core/vendor/phpunit/phpunit/src/Framework/TestResult.php @@ -695,7 +695,10 @@ public function run(PHPUnit_Framework_Test $test) } elseif ($e instanceof PHPUnit_Framework_SkippedTestError) { $skipped = true; } + } catch (PHPUnit_Framework_Exception $e) { + $error = true; } catch (Exception $e) { + $e = new PHPUnit_Framework_ExceptionWrapper($e); $error = true; } diff --git a/core/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php b/core/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php index 3a316c19bed1..adf82c2de3b1 100644 --- a/core/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php +++ b/core/vendor/phpunit/phpunit/src/TextUI/ResultPrinter.php @@ -260,20 +260,12 @@ protected function printDefect(PHPUnit_Framework_TestFailure $defect, $count) */ protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $count) { - $failedTest = $defect->failedTest(); - - if ($failedTest instanceof PHPUnit_Framework_SelfDescribing) { - $testName = $failedTest->toString(); - } else { - $testName = get_class($failedTest); - } - $this->write( sprintf( "\n%d) %s\n", $count, - $testName + $defect->getTestName() ) ); } @@ -283,26 +275,11 @@ protected function printDefectHeader(PHPUnit_Framework_TestFailure $defect, $cou */ protected function printDefectTrace(PHPUnit_Framework_TestFailure $defect) { - $this->write($defect->getExceptionAsString()); - - $trace = PHPUnit_Util_Filter::getFilteredStacktrace( - $defect->thrownException() - ); - - if (!empty($trace)) { - $this->write("\n" . $trace); - } - - $e = $defect->thrownException()->getPrevious(); - - while ($e) { - $this->write( - "\nCaused by\n" . - PHPUnit_Framework_TestFailure::exceptionToString($e). "\n" . - PHPUnit_Util_Filter::getFilteredStacktrace($e) - ); + $e = $defect->thrownException(); + $this->write((string) $e); - $e = $e->getPrevious(); + while ($e = $e->getPrevious()) { + $this->write("\nCaused by\n" . $e); } } diff --git a/core/vendor/phpunit/phpunit/src/Util/Filter.php b/core/vendor/phpunit/phpunit/src/Util/Filter.php index b36ce7d75e7f..5a0ae4feb191 100644 --- a/core/vendor/phpunit/phpunit/src/Util/Filter.php +++ b/core/vendor/phpunit/phpunit/src/Util/Filter.php @@ -82,12 +82,15 @@ public static function getFilteredStacktrace(Exception $e, $asString = true) $eTrace = $e->getSyntheticTrace(); $eFile = $e->getSyntheticFile(); $eLine = $e->getSyntheticLine(); + } elseif ($e instanceof PHPUnit_Framework_Exception) { + $eTrace = $e->getSerializableTrace(); + $eFile = $e->getFile(); + $eLine = $e->getLine(); } else { if ($e->getPrevious()) { - $eTrace = $e->getPrevious()->getTrace(); - } else { - $eTrace = $e->getTrace(); + $e = $e->getPrevious(); } + $eTrace = $e->getTrace(); $eFile = $e->getFile(); $eLine = $e->getLine(); } diff --git a/core/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist b/core/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist index 8d714b52a364..5daca63cd095 100644 --- a/core/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist +++ b/core/vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist @@ -1,4 +1,10 @@ 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