Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Generator bug: Duplicate method generation #98

Closed
wants to merge 3 commits into from

2 participants

Igor Pádraic Brady
Igor

I've run into a bug in the generator, where it would generate the on method twice. I've attached a failing test case that demonstrates the issue.

I suggest you refactor Generator::createClassMockCode into smaller sub-methods so that you can check only the code inside the class, instead of all of the other boilerplate crap that is not really helpful in this case. It might already be possible to achieve by making the methods non-static and mocking out the methods that provide the boilerplate code (like _getStandardMethods).

Pádraic Brady
Owner

Duplication verified using the PR rebased to master.

Pádraic Brady
Owner

This PR has been merged in local branch generator-fail for investigation

Dave Marshall davedevelopment referenced this pull request
Closed

1.0 Tidy Up #118

Dave Marshall davedevelopment referenced this pull request from a commit in davedevelopment/mockery
Dave Marshall davedevelopment Add test for #98 639c95f
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
43 library/Mockery/Generator.php
View
@@ -22,23 +22,10 @@
class Generator
{
-
- /**
- * Generates a Mock Object class with all Mockery methods whose
- * intent is basically to provide the mock object with the same
- * class type hierarchy as a typical instance of the class being
- * mocked.
- *
- * @param string $className
- * @param string $mockName
- * @param string $allowFinal
- * @return string Classname of the mock class created
- */
- public static function createClassMock($className, $mockName = null,
+ public static function createClassMockCode($className, $mockName,
$allowFinal = false, $block = array(), $makeInstanceMock = false,
$partialMethods = array())
{
- if (is_null($mockName)) $mockName = uniqid('Mockery_');
$definition = '';
$inheritance = '';
$interfaceInheritance = array();
@@ -80,13 +67,37 @@ public static function createClassMock($className, $mockName = null,
$result = self::applyMockeryTo($data['class'], $data['publicMethods'], $block, $partialMethods);
if ($result['callTypehinting']) $callTypehinting = true;
$definition .= $result['definition'];
+ $definition .= PHP_EOL;
$definition .= self::stubAbstractProtected($data['protectedMethods']);
} else {
$useStandardMethods = false;
}
}
if ($useStandardMethods) $definition .= self::_getStandardMethods($callTypehinting, $makeInstanceMock);
- $definition .= PHP_EOL . '}';
+ $definition .= PHP_EOL . '}'.PHP_EOL;
+
+ return $definition;
+ }
+
+ /**
+ * Generates a Mock Object class with all Mockery methods whose
+ * intent is basically to provide the mock object with the same
+ * class type hierarchy as a typical instance of the class being
+ * mocked.
+ *
+ * @param string $className
+ * @param string $mockName
+ * @param string $allowFinal
+ * @return string Classname of the mock class created
+ */
+ public static function createClassMock($className, $mockName = null,
+ $allowFinal = false, $block = array(), $makeInstanceMock = false,
+ $partialMethods = array())
+ {
+ if (is_null($mockName)) $mockName = uniqid('Mockery_');
+
+ $definition = static::createClassMockCode($className, $mockName, $allowFinal, $block, $makeInstanceMock, $partialMethods);
+
eval($definition);
return $mockName;
}
@@ -221,7 +232,7 @@ protected static function _replacePublicMethod(\ReflectionMethod $method)
}
$returnByRef = $method->returnsReference() ? ' & ' : '';
return $access . ' function ' . $returnByRef . $name . '(' . $paramDef . ')'
- . '{' . $body . '}';
+ . '{' . PHP_EOL . $body . PHP_EOL . '}' . PHP_EOL;
}
protected static function _renderPublicMethodParameters(\ReflectionMethod $method)
316 tests/Mockery/Fixtures/chatroulette_connection_mock.txt
View
@@ -0,0 +1,316 @@
+class Chatroulette_Connection_Mock extends Evenement_EventEmitter implements Chatroulette_ConnectionInterface, \Mockery\MockInterface
+{
+public function on($name,$callback){
+$stack = debug_backtrace();
+$args = array();
+if (isset($stack[0]['args'])) {
+ for($i=0; $i<count($stack[0]['args']); $i++) {
+ $args[$i] =& $stack[0]['args'][$i];
+ }
+}
+return $this->__call('on', $args);
+}
+
+public function pause(){
+$stack = debug_backtrace();
+$args = array();
+if (isset($stack[0]['args'])) {
+ for($i=0; $i<count($stack[0]['args']); $i++) {
+ $args[$i] =& $stack[0]['args'][$i];
+ }
+}
+return $this->__call('pause', $args);
+}
+public function close(){
+$stack = debug_backtrace();
+$args = array();
+if (isset($stack[0]['args'])) {
+ for($i=0; $i<count($stack[0]['args']); $i++) {
+ $args[$i] =& $stack[0]['args'][$i];
+ }
+}
+return $this->__call('close', $args);
+}
+public function write($data){
+$stack = debug_backtrace();
+$args = array();
+if (isset($stack[0]['args'])) {
+ for($i=0; $i<count($stack[0]['args']); $i++) {
+ $args[$i] =& $stack[0]['args'][$i];
+ }
+}
+return $this->__call('write', $args);
+}
+
+ protected static $_mockery_staticClassName = '';
+
+ protected $_mockery_expectations = array();
+
+ protected $_mockery_lastExpectation = null;
+
+ protected $_mockery_ignoreMissing = false;
+
+ protected $_mockery_deferMissing = false;
+
+ protected $_mockery_verified = false;
+
+ protected $_mockery_name = null;
+
+ protected $_mockery_allocatedOrder = 0;
+
+ protected $_mockery_currentOrder = 0;
+
+ protected $_mockery_groups = array();
+
+ protected $_mockery_container = null;
+
+ protected $_mockery_partial = null;
+
+ protected $_mockery_disableExpectationMatching = false;
+
+ protected $_mockery_mockableMethods = array();
+
+ protected $_mockery_mockableProperties = array();
+
+ public function mockery_init($name, \Mockery\Container $container = null, $partialObject = null)
+ {
+ $this->_mockery_name = $name;
+ if(is_null($container)) {
+ $container = new \Mockery\Container;
+ }
+ $this->_mockery_container = $container;
+ if (!is_null($partialObject)) {
+ $this->_mockery_partial = $partialObject;
+ }
+ if (!\Mockery::getConfiguration()->mockingNonExistentMethodsAllowed()) {
+ if (isset($this->_mockery_partial)) {
+ $reflected = new \ReflectionObject($this->_mockery_partial);
+ } else {
+ $reflected = new \ReflectionClass($this->_mockery_name);
+ }
+ $methods = $reflected->getMethods(\ReflectionMethod::IS_PUBLIC);
+ foreach ($methods as $method) {
+ if (!$method->isStatic()) $this->_mockery_mockableMethods[] = $method->getName();
+ }
+ }
+ }
+
+ public function shouldReceive()
+ {
+ $self = $this;
+ $lastExpectation = \Mockery::parseShouldReturnArgs(
+ $this, func_get_args(), function($method) use ($self) {
+ $director = $self->mockery_getExpectationsFor($method);
+ if (!$director) {
+ $director = new \Mockery\ExpectationDirector($method, $self);
+ $self->mockery_setExpectationsFor($method, $director);
+ }
+ $expectation = new \Mockery\Expectation($self, $method);
+ $director->addExpectation($expectation);
+ return $expectation;
+ }
+ );
+ return $lastExpectation;
+ }
+
+ public function shouldDeferMissing()
+ {
+ $this->_mockery_deferMissing = true;
+ return $this;
+ }
+
+ public function shouldIgnoreMissing()
+ {
+ $this->_mockery_ignoreMissing = true;
+ return $this;
+ }
+
+ public function shouldExpect(Closure $closure)
+ {
+ $recorder = new \Mockery\Recorder($this, $this->_mockery_partial);
+ $this->_mockery_disableExpectationMatching = true;
+ $closure($recorder);
+ $this->_mockery_disableExpectationMatching = false;
+ return $this;
+ }
+
+ public function byDefault()
+ {
+ foreach ($this->_mockery_expectations as $director) {
+ $exps = $director->getExpectations();
+ foreach ($exps as $exp) {
+ $exp->byDefault();
+ }
+ }
+ return $this;
+ }
+
+ public function __call($method, $args)
+ {
+ if (isset($this->_mockery_expectations[$method])
+ && !$this->_mockery_disableExpectationMatching) {
+ $handler = $this->_mockery_expectations[$method];
+ return $handler->call($args);
+ } elseif (!is_null($this->_mockery_partial) && method_exists($this->_mockery_partial, $method)) {
+ return call_user_func_array(array($this->_mockery_partial, $method), $args);
+ } elseif ($this->_mockery_deferMissing && is_callable("parent::$method")) {
+ return call_user_func_array("parent::$method", $args);
+ } elseif ($this->_mockery_ignoreMissing) {
+ $undef = new \Mockery\Undefined;
+ return call_user_func_array(array($undef, $method), $args);
+ }
+ throw new \BadMethodCallException(
+ 'Method ' . $this->_mockery_name . '::' . $method . '() does not exist on this mock object'
+ );
+ }
+
+ /**
+ * Forward calls to this magic method to the __call method
+ */
+ public function __toString()
+ {
+ return $this->__call('__toString', array());
+ }
+
+ public function mockery_verify()
+ {
+ if ($this->_mockery_verified) return true;
+ if (isset($this->_mockery_ignoreVerification)
+ && $this->_mockery_ignoreVerification == true) {
+ return true;
+ }
+ $this->_mockery_verified = true;
+ foreach($this->_mockery_expectations as $director) {
+ $director->verify();
+ }
+ }
+
+ public function mockery_teardown()
+ {
+
+ }
+
+ public function mockery_allocateOrder()
+ {
+ $this->_mockery_allocatedOrder += 1;
+ return $this->_mockery_allocatedOrder;
+ }
+
+ public function mockery_setGroup($group, $order)
+ {
+ $this->_mockery_groups[$group] = $order;
+ }
+
+ public function mockery_getGroups()
+ {
+ return $this->_mockery_groups;
+ }
+
+ public function mockery_setCurrentOrder($order)
+ {
+ $this->_mockery_currentOrder = $order;
+ return $this->_mockery_currentOrder;
+ }
+
+ public function mockery_getCurrentOrder()
+ {
+ return $this->_mockery_currentOrder;
+ }
+
+ public function mockery_validateOrder($method, $order)
+ {
+ if (isset($this->_mockery_ignoreVerification)
+ && $this->_mockery_ignoreVerification === false) {
+ return;
+ }
+ if ($order < $this->_mockery_currentOrder) {
+ throw new \Mockery\Exception(
+ 'Method ' . $this->_mockery_name . '::' . $method . '()'
+ . ' called out of order: expected order '
+ . $order . ', was ' . $this->_mockery_currentOrder
+ );
+ }
+ $this->mockery_setCurrentOrder($order);
+ }
+
+ public function mockery_getExpectationCount()
+ {
+ $count = 0;
+ foreach($this->_mockery_expectations as $director) {
+ $count += $director->getExpectationCount();
+ }
+ return $count;
+ }
+
+ public function mockery_setExpectationsFor($method, \Mockery\ExpectationDirector $director)
+ {
+ $this->_mockery_expectations[$method] = $director;
+ }
+
+ public function mockery_getExpectationsFor($method)
+ {
+ if (isset($this->_mockery_expectations[$method])) {
+ return $this->_mockery_expectations[$method];
+ }
+ }
+
+ public function mockery_findExpectation($method, array $args)
+ {
+ if (!isset($this->_mockery_expectations[$method])) {
+ return null;
+ }
+ $director = $this->_mockery_expectations[$method];
+ return $director->findExpectation($args);
+ }
+
+ public function mockery_getContainer()
+ {
+ return $this->_mockery_container;
+ }
+
+ public function mockery_getName()
+ {
+ return $this->_mockery_name;
+ }
+
+ public function mockery_getMockableMethods()
+ {
+ return $this->_mockery_mockableMethods;
+ }
+
+ public function mockery_getMockableProperties()
+ {
+ return $this->_mockery_mockableProperties;
+ }
+
+ //** Everything below this line is not copied from/needed for Mockery/Mock **//
+
+ public function __wakeup()
+ {
+ /**
+ * This does not add __wakeup method support. It's a blind method and any
+ * expected __wakeup work will NOT be performed. It merely cuts off
+ * annoying errors where a __wakeup exists but is not essential when
+ * mocking
+ */
+ }
+
+ public static function __callStatic($method, $args)
+ {
+ try {
+ $associatedRealObject = \Mockery::fetchMock(__CLASS__);
+ return $associatedRealObject->__call($method, $args);
+ } catch (\BadMethodCallException $e) {
+ throw new \BadMethodCallException(
+ 'Static method ' . $associatedRealObject->mockery_getName() . '::' . $method
+ . '() does not exist on this mock object'
+ );
+ }
+ }
+
+ public function mockery_getExpectations()
+ {
+ return $this->_mockery_expectations;
+ }
+
+}
68 tests/Mockery/GeneratorTest.php
View
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Mockery
+ *
+ * LICENSE
+ *
+ * This source file is subject to the new BSD license that is bundled
+ * with this package in the file LICENSE.txt.
+ * It is also available through the world-wide-web at this URL:
+ * http://github.com/padraic/mockery/master/LICENSE
+ * If you did not receive a copy of the license and are unable to
+ * obtain it through the world-wide-web, please send an email
+ * to padraic@php.net so we can send you a copy immediately.
+ *
+ * @category Mockery
+ * @package Mockery
+ * @subpackage UnitTests
+ * @copyright Copyright (c) 2010 Pádraic Brady (http://blog.astrumfutura.com)
+ * @license http://github.com/padraic/mockery/blob/master/LICENSE New BSD License
+ */
+
+use Mockery\Generator;
+
+class GeneratorTest extends PHPUnit_Framework_TestCase
+{
+ /** @test */
+ public function shouldNotDuplicateDoublyInheritedMethods()
+ {
+ $classes = array('Evenement_EventEmitter', 'Chatroulette_ConnectionInterface');
+ $definition = Generator::createClassMockCode($classes, 'Chatroulette_Connection_Mock');
+ $expected = file_get_contents(__DIR__.'/Fixtures/chatroulette_connection_mock.txt');
+
+ $this->assertSame($expected, $definition);
+ }
+}
+
+interface Evenement_EventEmitterInterface
+{
+ public function on($name, $callback);
+}
+
+class Evenement_EventEmitter implements Evenement_EventEmitterInterface
+{
+ public function on($name, $callback)
+ {
+ }
+}
+
+interface React_StreamInterface extends Evenement_EventEmitterInterface
+{
+ public function close();
+}
+
+interface React_ReadableStreamInterface extends React_StreamInterface
+{
+ public function pause();
+}
+
+interface React_WritableStreamInterface extends React_StreamInterface
+{
+ public function write($data);
+}
+
+interface Chatroulette_ConnectionInterface
+ extends React_ReadableStreamInterface,
+ React_WritableStreamInterface
+{
+}
Something went wrong with that request. Please try again.