Home » Testing » PHPUnit2 » Bug #7780
Exception in dtor causes fatal error
Details
| Submitted | 2006-06-01 14:02 UTC |
|---|---|
| From | elias at adaptiveinstance dot com |
| Assigned | sebastian |
| Status | Closed |
| Package | PHPUnit2 |
| PHP Version | 5_2 CVS-2006-06-01 |
| OS | linux |
| Roadmaps | (Not assigned) |
Comments
[2006-06-01 14:02 UTC] elias at adaptiveinstance dot com
Description:
------------
Exception in dtor causes fatal error
Test script:
---------------
class FooTest extends PHPUnit2_Framework_TestCase
{
public function test()
{
$mock = $this->getMock('stdClass');
$mock->expects($this->exactly(1));
$this->assertTrue(null);
}
}
Expected result:
----------------
PHPUnit 3.0.0alpha3 by Sebastian Bergmann.
..................E
Time: 00:00
There was 1 error:
1) test(IpExtensionTest)
No method matcher is set
<STACKTRACE_HERE>
FAILURES!
Tests: 19, Errors: 1.
Actual result:
--------------
PHPUnit 3.0.0alpha3 by Sebastian Bergmann.
..................
Fatal error: Ignoring exception from Mock_stdClass::__destruct() while an exception is already active (Uncaught InvalidArgumentException in /usr/share/pear/PHPUnit2/Framework/Assert.php on line 352) in FooTest.php on line 0
[2006-06-02 08:39 UTC] elias at adaptiveinstance dot com
the verification of a mock in their own destructor's wont work, because you cannot prevent them to throw an exception if another is active (as long there's no way to detect an active exception).
the only solutions i see are:
1. verify the mock by hand in every test method
2. save the mocks inside the testcase and verify them right after test method exceution
with the latter you're able to control the order of error output. i.e. if the mock isn't runnable due to missing ->method('foo') you can prefer this error before any assertion failures.
[2006-06-02 11:42 UTC] sebastian at php dot net
The Zend Engine only has limited support for throwing exceptions inside a destructor.
The patch below moves the verify() call out of the Mock Object's destructor to the runBar() method of PHPUnit2_Framework_TestCase. The only downside to this is that now Mock Objects only work (at least out-of-the-box) when the test case class inherits from PHPUnit2_Framework_TestCase.
--
Index: Extensions/MockObject/Mock.php
===================================================================
RCS file: /repository/pear/PHPUnit2/Extensions/MockObject/Mock.php,v
retrieving revision 1.25
diff -u -B -r1.25 Mock.php
--- Extensions/MockObject/Mock.php 31 May 2006 11:31:21 -0000 1.25
+++ Extensions/MockObject/Mock.php 2 Jun 2006 11:37:55 -0000
@@ -243,7 +243,6 @@
" private \$invocationMocker;\n\n" .
"%s" .
"%s" .
- "%s" .
" public function getInvocationMocker() {\n" .
" return \$this->invocationMocker;\n" .
" }\n\n" .
@@ -255,7 +254,6 @@
" }\n",
$this->generateConstructorCode($class),
- $this->generateDestructorCode($class),
$this->generateCloneCode($class)
);
}
@@ -282,20 +280,6 @@
}
}
- protected function generateDestructorCode(ReflectionClass $class)
- {
- if ($class->hasMethod('__destruct')) {
- return " public function __destruct() {\n" .
- " parent::__destruct();" .
- " \$this->invocationMocker->verify();\n" .
- " }\n\n";
- } else {
- return " public function __destruct() {\n" .
- " \$this->invocationMocker->verify();\n" .
- " }\n\n";
- }
- }
-
protected function generateCloneCode(ReflectionClass $class)
{
if ($class->hasMethod('__clone')) {
Index: Framework/Assert.php
===================================================================
RCS file: /repository/pear/PHPUnit2/Framework/Assert.php,v
retrieving revision 1.83
diff -u -B -r1.83 Assert.php
--- Framework/Assert.php 2 Jun 2006 05:10:19 -0000 1.83
+++ Framework/Assert.php 2 Jun 2006 11:37:55 -0000
@@ -643,73 +643,6 @@
/**
*
*
- * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function never()
- {
- return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount(0);
- }
-
- /**
- *
- *
- * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedAtLeastOnce
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function atLeastOnce()
- {
- return new PHPUnit2_Extensions_MockObject_Matcher_InvokedAtLeastOnce;
- }
-
- /**
- *
- *
- * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function once()
- {
- return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount(1);
- }
-
- /**
- *
- *
- * @param integer $count
- * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function exactly($count)
- {
- return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount($count);
- }
-
- /**
- *
- *
- * @param integer $index
- * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedAtIndex
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function at($index)
- {
- return new PHPUnit2_Extensions_MockObject_Matcher_InvokedAtIndex($index);
- }
-
- /**
- *
- *
* @return PHPUnit2_Framework_Constraint_IsAnything
* @access public
* @since Method available since Release 3.0.0
@@ -888,35 +821,6 @@
return new PHPUnit2_Framework_Constraint_StringContains($string, $case);
}
- /**
- *
- *
- * @param mixed $value
- * @return PHPUnit2_Extensions_MockObject_Stub_Return
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function returnValue($value)
- {
- return new PHPUnit2_Extensions_MockObject_Stub_Return($value);
- }
-
- /**
- *
- *
- * @param mixed $value, ...
- * @return PHPUnit2_Extensions_MockObject_Stub_ConsecutiveCalls
- * @access public
- * @since Method available since Release 3.0.0
- * @static
- */
- public static function onConsecutiveCalls()
- {
- $args = func_get_args();
-
- return new PHPUnit2_Extensions_MockObject_Stub_ConsecutiveCalls($args);
- }
/**
* Fails a test with the given message.
@@ -959,47 +863,6 @@
}
/**
- * Returns a mock object for the specified class.
- *
- * @param string $className
- * @param array $methods
- * @param array $arguments
- * @param string $mockClassName
- * @return object
- * @access public
- * @static
- * @since Method available since Release 3.0.0
- */
- public static function getMock($className, Array $methods = array(), Array $arguments = array(), $mockClassName = '')
- {
- if (!is_string($className) || !is_string($mockClassName)) {
- throw new InvalidArgumentException;
- }
-
- $mock = PHPUnit2_Extensions_MockObject_Mock::generate($className, $methods, $mockClassName);
- $mockClass = new ReflectionClass($mock->mockClassName);
-
- return $mockClass->newInstanceArgs($arguments);
- }
-
- /**
- * Returns a stub object for the specified class.
- *
- * @param string $className
- * @param array $methods
- * @param array $arguments
- * @param string $stubClassName
- * @return object
- * @access public
- * @static
- * @since Method available since Release 3.0.0
- * @todo Implement PHPUnit2_Framework_Assert::getStub()
- */
- public static function getStub($className, Array $methods = array(), Array $arguments = array(), $stubClassName = '')
- {
- }
-
- /**
* Returns the value of an object's property that is declared
* protected or private.
*
Index: Framework/TestCase.php
===================================================================
RCS file: /repository/pear/PHPUnit2/Framework/TestCase.php,v
retrieving revision 1.49
diff -u -B -r1.49 TestCase.php
--- Framework/TestCase.php 2 Jun 2006 11:21:22 -0000 1.49
+++ Framework/TestCase.php 2 Jun 2006 11:37:55 -0000
@@ -41,7 +41,7 @@
* @author Sebastian Bergmann <sb@sebastian-bergmann.de>
* @copyright 2002-2006 Sebastian Bergmann <sb@sebastian-bergmann.de>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
- * @version CVS: $Id: TestCase.php,v 1.49 2006/06/02 11:21:22 sebastian Exp $
+ * @version CVS: $Id: TestCase.php,v 1.48 2006/05/28 13:17:21 sebastian Exp $
* @link http://pear.php.net/package/PHPUnit2
* @since File available since Release 2.0.0
*/
@@ -126,6 +126,12 @@
private $iniSettings = Array();
/**
+ * @var Array
+ * @access private
+ */
+ private $mockObjects = Array();
+
+ /**
* Constructs a test case with the given name.
*
* @param string
@@ -205,20 +211,32 @@
*/
public function runBare()
{
+ // Workaround for missing "finally".
$catchedException = NULL;
+ // Set up the fixture.
$this->setUp();
+ // Run the test.
try {
$this->runTest();
+
+ // Verify Mock Object conditions.
+ foreach ($this->mockObjects as $mockObject) {
+ $mockObject->verify();
+ }
+
+ $this->mockObjects = Array();
}
catch (Exception $e) {
$catchedException = $e;
}
+ // Tear down the fixture.
$this->tearDown();
+ // Clean up INI settings.
foreach ($this->iniSettings as $varName => $oldValue) {
ini_set($varName, $oldValue);
}
@@ -273,16 +291,14 @@
* resets the modified php.ini setting to its original value after the
* test is run.
*
- * @param string $key
- * @param string $value
- * @param string $message
+ * @param string $varName
+ * @param string $newValue
* @throws InvalidArgumentException
* @throws RuntimeException
- * @access public
- * @static
+ * @access protected
* @since Method available since Release 3.0.0
*/
- public function iniSet($varName, $newValue)
+ protected function iniSet($varName, $newValue)
{
if (!is_string($varName) || !is_string($newValue)) {
throw new InvalidArgumentException;
@@ -298,6 +314,122 @@
}
/**
+ * Returns a mock object for the specified class.
+ *
+ * @param string $className
+ * @param array $methods
+ * @param array $arguments
+ * @param string $mockClassName
+ * @return object
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function getMock($className, Array $methods = array(), Array $arguments = array(), $mockClassName = '')
+ {
+ if (!is_string($className) || !is_string($mockClassName)) {
+ throw new InvalidArgumentException;
+ }
+
+ $mock = PHPUnit2_Extensions_MockObject_Mock::generate($className, $methods, $mockClassName);
+ $mockClass = new ReflectionClass($mock->mockClassName);
+ $mockObject = $mockClass->newInstanceArgs($arguments);
+
+ $this->mockObjects[] = $mockObject;
+
+ return $mockObject;
+ }
+
+ /**
+ *
+ *
+ * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function never()
+ {
+ return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount(0);
+ }
+
+ /**
+ *
+ *
+ * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedAtLeastOnce
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function atLeastOnce()
+ {
+ return new PHPUnit2_Extensions_MockObject_Matcher_InvokedAtLeastOnce;
+ }
+
+ /**
+ *
+ *
+ * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function once()
+ {
+ return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount(1);
+ }
+
+ /**
+ *
+ *
+ * @param integer $count
+ * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedCount
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function exactly($count)
+ {
+ return new PHPUnit2_Extensions_MockObject_Matcher_InvokedCount($count);
+ }
+
+ /**
+ *
+ *
+ * @param integer $index
+ * @return PHPUnit2_Extensions_MockObject_Matcher_InvokedAtIndex
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function at($index)
+ {
+ return new PHPUnit2_Extensions_MockObject_Matcher_InvokedAtIndex($index);
+ }
+
+ /**
+ *
+ *
+ * @param mixed $value
+ * @return PHPUnit2_Extensions_MockObject_Stub_Return
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function returnValue($value)
+ {
+ return new PHPUnit2_Extensions_MockObject_Stub_Return($value);
+ }
+
+ /**
+ *
+ *
+ * @param mixed $value, ...
+ * @return PHPUnit2_Extensions_MockObject_Stub_ConsecutiveCalls
+ * @access protected
+ * @since Method available since Release 3.0.0
+ */
+ protected function onConsecutiveCalls()
+ {
+ $args = func_get_args();
+
+ return new PHPUnit2_Extensions_MockObject_Stub_ConsecutiveCalls($args);
+ }
+
+ /**
* Creates a default TestResult object.
*
* @return PHPUnit2_Framework_TestResult
[2006-06-02 12:39 UTC] sebastian at php dot net
This bug has been fixed in CVS.
If this was a documentation problem, the fix will appear on pear.php.net by the end of next Sunday (CET).
If this was a problem with the pear.php.net website, the change should be live shortly.
Otherwise, the fix will appear in the package's next release.
Thank you for the report and for helping us make PEAR better.