From d69563f7c9366748fdaaae6794799b076f9b4dc6 Mon Sep 17 00:00:00 2001 From: Blaine Schmeisser Date: Wed, 19 Dec 2012 21:49:29 -0600 Subject: [PATCH 1/6] Add documentation for the mocker class --- test/Mocker.php | 45 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/test/Mocker.php b/test/Mocker.php index 3a67b7f9e4..5ba6fc2b9c 100644 --- a/test/Mocker.php +++ b/test/Mocker.php @@ -14,7 +14,50 @@ use Reflection; /** - * The Mocker class aids in the creation of Mocks on the fly. + * The Mocker class aids in the creation of Mocks on the fly, allowing you to + * use Lithium filters on most methods in the class. + * + * To enable the autoloading of mocks you simply need to make a simple method + * call. + * {{{ + * use lithium\core\Environment; + * use lithium\test\Mocker; + * if(!Environment::is('production')) { + * Mocker::register(); + * } + * }}} + * + * You can also enable autoloading inside the setup of a unit test class. This + * method can be called redundantly. + * {{{ + * use lithium\test\Mocker; + * class MockerTest extends \lithium\test\Unit { + * public function setUp() { + * Mocker::register(); + * } + * } + * }}} + * + * Using Mocker is the fun magical part, it's autoloaded so simply call the + * class you want to mock with the '\Mock' at the end. The autoloader will + * detect you want to autoload it, and create it for you. Now you can filter + * any method. + * {{{ + * use lithium\console\dispatcher\Mock as DispatcherMock; + * $dispatcher = new DispatcherMock(); + * $dispatcher->applyFilter('config', function($self, $params, $chain) { + * return array(); + * }); + * $results = $dispatcher->config(); + * }}} + * {{{ + * use lithium\analysis\parser\Mock as ParserMock; + * $code = 'echo "foobar";'; + * ParserMock::applyFilter('config', function($self, $params, $chain) { + * return array(); + * }); + * $tokens = ParserMock::tokenize($code, array('wrap' => true)); + * }}} */ class Mocker { From 614f732f7f554ddf8e6186576036e1925793aae3 Mon Sep 17 00:00:00 2001 From: Blaine Schmeisser Date: Thu, 20 Dec 2012 16:44:17 -0600 Subject: [PATCH 2/6] Delete README.md and leave readme.md - Fixes #759 --- README.md | 38 -------------------------------------- 1 file changed, 38 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 44ba1f1052..0000000000 --- a/README.md +++ /dev/null @@ -1,38 +0,0 @@ -You asked for a better framework. Here it is. ---------------------------------------------- - -Lithium is the fast, flexible and most RAD development framework for PHP 5.3 and up. - -A framework of firsts ---------------------- - -Lithium is the first and only major PHP framework built from the ground up for PHP 5.3+, and the first to break ground into major new technologies, including bridging the gap between relational and non-relational databases through a single, unified API. - -Promiscuously opinionated -------------------------- - -Some frameworks give you a solid set of classes, but little or no default project organization, leaving you to fend for yourself on each project you create, and spend time wiring up framework classes that should just work together. Others provide you with great organizational conventions, but no way to break out of those conventions if you need to, and too often, no way to override or replace core framework classes. - -Lithium is the first framework to give you the best of both worlds, without compromising either. In fact, Lithium's API is intentionally designed to allow you to "grow out of" the framework and into your own custom code over the course of your application's lifecycle, if your needs require. - -Technology ----------- - -Lithium takes full advantage of the latest PHP 5.3 features, including namespaces, late static binding and closures. Lithium's innovative [method filter system](http://lithify.me/docs/lithium/util/collection/Filters) makes extensive use of closures and anonymous functions to allow application developers to "wrap" framework method calls, intercepting parameters before, and return values after. - -Lithium also complies with the PHP 5.3 namespacing standard, allowing you to easily integrate other PHP 5.3 standard libraries and frameworks with Lithium applications, and vice-versa. - -Lithium integrates the latest storage technologies, including MongoDB, CouchDB and Redis, with plugin support for Cassandra, ElasticSearch and others. - -Flexibility ------------ - -Lithium gives you full control over your application, from filters to dynamically modify framework internals, to dynamic dependencies to extend and replace core classes with application or plugin classes, to heavy use of adapter-oriented configurations, to make it seamless to move between different technologies and options. - -Every component of the Lithium framework stack is replaceable through the robust plugin architecture. Swap out the default ORM / ODM implementation for [Doctrine 2](https://github.com/mariano/li3_doctrine2/) or [PHP ActiveRecord](http://dev.lithify.me/li3_activerecord). Don't like the templating? Use [ Twig](http://dev.lithify.me/li3_twig), [ Mustache](https://github.com/bobthecow/mustache.php), or roll your own. - -If you don't even need to write a full application, build a micro-app in a single file using the routing system, without giving up the maintainability of the framework's structure. - -Build status ------------ -[![Build Status](https://secure.travis-ci.org/UnionOfRAD/lithium.png?branch=master)](http://travis-ci.org/UnionOfRAD/lithium) From 0803c888fe7337d342e0f281baf53cfbdcb55cc0 Mon Sep 17 00:00:00 2001 From: Blaine Schmeisser Date: Wed, 19 Dec 2012 22:43:00 -0600 Subject: [PATCH 3/6] Fix bug with how results are rendered. The issue was we were rendering results BEFORE the filter, passing it in and returning it. This is bad since the results may modify the existing class and being a filter you may choose never to get that far in your chain. The fix is a bit complex but covers my bases. You create a class 'Mock' which extends the class you wish to mock, this will be directly accessed by the consumer, but it doesnt interact with itself, it sends all its responsibilities to another class we create 'MockDelegate'. MockDelegate which, if called from the Mock class will pass it along to the real mocked class, or if called from the extended class, will submit it back out through the client Mock class. Now all methods are filtered. A known issue is not something I can really fix. ~~~ php ~~~ If you are Mocking this class the `doSomething` methods NEVER goes to the extending 'MockDelegate' to be re-filtered so this method would NOT be filtered even if you have set a filter for this method. --- test/Mocker.php | 168 +++++++++++++++++++++++++------- tests/cases/test/MockerTest.php | 26 +++++ 2 files changed, 159 insertions(+), 35 deletions(-) diff --git a/test/Mocker.php b/test/Mocker.php index 5ba6fc2b9c..0ab719d335 100644 --- a/test/Mocker.php +++ b/test/Mocker.php @@ -62,34 +62,119 @@ class Mocker { /** - * A list of code to be generated based on the type. + * A list of code to be generated for the delegator. + * + * The MockDelgate directly extends the mocker and makes all methods + * publically available to other classes but should not be accessed directly + * by any other application. This should be called only by the mocker and + * the mockee and never by the consumer. * * @var array */ - protected static $_dynamicCode = array( + protected static $_mockDelegateIngredients = array( 'startClass' => array( 'namespace {:namespace};', - 'class {:mockee} extends \{:mocker} {' + 'class MockDelegate extends \{:mocker} {' + ), + 'constructor' => array( + '{:modifiers} function __construct({:args}) {', + ' $args = func_get_args();', + ' $this->parent = array_pop($args);', + ' $this->parent->mocker = $this;', + ' call_user_func_array("parent::__construct", $args);', + '}', + ), + 'method' => array( + '{:modifiers} function {:method}({:args}) {', + ' $args = func_get_args();', + ' $token = spl_object_hash($this);', + ' $id = count($args) - 1;', + ' if (!isset($args[$id]) || $args[$id] !== $token) {', + ' $method = array($this->parent, "{:method}");', + ' return call_user_func_array($method, $args);', + ' }', + ' array_pop($args);', + ' return call_user_func_array("parent::{:method}", $args);', + '}', + ), + 'staticMethod' => array( + '{:modifiers} function {:method}({:args}) {', + ' $args = func_get_args();', + ' $token = "1f3870be274f6c49b3e31a0c6728957f";', + ' $id = count($args) - 1;', + ' if (!isset($args[$id]) || $args[$id] !== $token) {', + ' $method = \'{:namespace}\Mock::{:method}\';', + ' return call_user_func_array($method, $args);', + ' }', + ' array_pop($args);', + ' return call_user_func_array("parent::{:method}", $args);', + '}', + ), + 'endClass' => array( + '}', + ), + ); + + /** + * A list of code to be generated for the mocker. + * + * The Mock class directly extends the mock class but only directly + * interacts with the MockDelegate directly. This class is the actual + * interface for consumers, instantiation or static method calls, and can + * have most of its methods filtered. + * + * @var array + */ + protected static $_mockIngredients = array( + 'startClass' => array( + 'namespace {:namespace};', + 'class Mock extends \{:mocker} {', + ' public $mocker;', + ' protected $_safeVars = array(', + ' "_classes",', + ' "_methodFilters",', + ' "mocker",', + ' "_safeVars"', + ' );', + ), + 'get' => array( + 'public function __get($key) {', + ' return $this->mocker->$key;', + '}', + ), + 'constructor' => array( + '{:modifiers} function __construct({:args}) {', + ' $args = array_values(get_defined_vars());', + ' array_push($args, $this);', + ' foreach ($this as $key => $value) {', + ' if (!in_array($key, $this->_safeVars)) {', + ' unset($this->$key);', + ' }', + ' }', + ' $class = new \ReflectionClass(\'{:namespace}\MockDelegate\');', + ' $class->newInstanceArgs($args);', + '}', + ), + 'destructor' => array( + 'public function __destruct() {}', ), 'staticMethod' => array( - '{:modifiers} function {:name}({:params}) {', - ' $params = func_get_args();', - ' list($class, $method) = explode(\'::\', __METHOD__, 2);', - ' $parent = \'parent::\' . $method;', - ' $result = call_user_func_array($parent, $params);', - ' return self::_filter($method, $params, function($self, $params) use(&$result) {', - ' return $result;', + '{:modifiers} function {:method}({:args}) {', + ' $args = func_get_args();', + ' array_push($args, "1f3870be274f6c49b3e31a0c6728957f");', + ' $method = \'{:namespace}\MockDelegate::{:method}\';', + ' return self::_filter("{:method}", $args, function($self, $args) use(&$method) {', + ' return call_user_func_array($method, $args);', ' });', '}', ), 'method' => array( - '{:modifiers} function {:name}({:params}) {', - ' $params = func_get_args();', - ' list($class, $method) = explode(\'::\', __METHOD__, 2);', - ' $parent = \'parent::\' . $method;', - ' $result = call_user_func_array($parent, $params);', - ' return $this->_filter($parent, $params, function($self, $params) use(&$result) {', - ' return $result;', + '{:modifiers} function {:method}({:args}) {', + ' $args = func_get_args();', + ' array_push($args, spl_object_hash($this->mocker));', + ' $method = array($this->mocker, "{:method}");', + ' return $this->_filter(__METHOD__, $args, function($self, $args) use(&$method) {', + ' return call_user_func_array($method, $args);', ' });', '}', ), @@ -104,12 +189,11 @@ class Mocker { * @var array */ protected static $_blackList = array( - '__construct', '__destruct', '__call', '__callStatic', + '__destruct', '__call', '__callStatic', '_parents', '__get', '__set', '__isset', '__unset', '__sleep', '__wakeup', '__toString', '__clone', '__invoke', - '__construct', '_init', 'applyFilter', 'invokeMethod', - '__set_state', '_instance', '_filter', '_parents', - '_stop', + '_stop', '_init', 'applyFilter', 'invokeMethod', + '__set_state', '_instance', '_filter', ); /** @@ -134,29 +218,38 @@ public static function create($mockee) { $mocker = self::_mocker($mockee); - $code = self::_dynamicCode('startClass', array( + $tokens = array( 'namespace' => self::_namespace($mockee), 'mocker' => $mocker, - 'mockee' => 'Mock', - )); + 'mockee' => 'MockDelegate', + ); + $mockDelegate = self::_dynamicCode('mockDelegate', 'startClass', $tokens); + $mock = self::_dynamicCode('mock', 'startClass', $tokens); $reflectedClass = new ReflectionClass($mocker); $reflecedMethods = $reflectedClass->getMethods(); foreach ($reflecedMethods as $method) { if (!in_array($method->name, self::$_blackList)) { $key = $method->isStatic() ? 'staticMethod' : 'method'; - $code .= self::_dynamicCode($key, array( - 'name' => $method->name, + $key = $method->name === '__construct' ? 'constructor' : $key; + $tokens = array( + 'namespace' => self::_namespace($mockee), + 'method' => $method->name, 'modifiers' => self::_methodModifiers($method), - 'params' => self::_methodParams($method), - 'visibility' => 'public', - )); + 'args' => self::_methodParams($method), + 'mocker' => $mocker, + ); + $mockDelegate .= self::_dynamicCode('mockDelegate', $key, $tokens); + $mock .= self::_dynamicCode('mock', $key, $tokens); } } - $code .= self::_dynamicCode('endClass'); + $mockDelegate .= self::_dynamicCode('mockDelegate', 'endClass'); + $mock .= self::_dynamicCode('mock', 'get'); + $mock .= self::_dynamicCode('mock', 'destructor'); + $mock .= self::_dynamicCode('mock', 'endClass'); - eval($code); + eval($mockDelegate . $mock); } /** @@ -170,7 +263,8 @@ public static function create($mockee) { protected static function _methodModifiers(ReflectionMethod $method) { $modifierKey = $method->getModifiers(); $modifierArray = Reflection::getModifierNames($modifierKey); - return implode(' ', $modifierArray); + $modifiers = implode(' ', $modifierArray); + return str_replace(array('private', 'protected'), 'public', $modifiers); } /** @@ -195,12 +289,16 @@ protected static function _methodParams(ReflectionMethod $method) { /** * Will generate the code you are wanting. * - * @param string $key The key from self::$_dynamicCode + * This pulls from $_mockDelegateIngredients and $_mockIngredients. + * + * @param string $type The name of the array of ingredients to use + * @param string $key The key from the array of ingredients * @param array $tokens Tokens, if any, that should be inserted * @return string */ - protected static function _dynamicCode($key, $tokens = array()) { - $code = implode("\n", self::$_dynamicCode[$key]); + protected static function _dynamicCode($type, $key, $tokens = array()) { + $name = '_' . $type . 'Ingredients'; + $code = implode("\n", self::${$name}[$key]); return String::insert($code, $tokens) . "\n"; } diff --git a/tests/cases/test/MockerTest.php b/tests/cases/test/MockerTest.php index a516b410af..0a19be573b 100644 --- a/tests/cases/test/MockerTest.php +++ b/tests/cases/test/MockerTest.php @@ -113,6 +113,32 @@ public function testFilteringStaticClassCanReturnOriginal() { $this->assertEqual($filteredResult, $originalResult); } + public function testOriginalMethodNotCalled() { + $http = new \lithium\tests\mocks\security\auth\adapter\mockHttp\Mock; + + $this->assertEqual(0, count($http->headers)); + + $http->_writeHeader('Content-type: text/html'); + + $this->assertEqual(1, count($http->headers)); + + $http->applyFilter('_writeHeader', function($self, $params, $chain) { + return false; + }); + + $http->_writeHeader('Content-type: application/pdf'); + + $this->assertEqual(1, count($http->headers)); + } + + public function testFilteringAFilteredMethod() { + $adapt = 'lithium\core\adaptable\Mock'; + $adapt::applyFilter('_initAdapter', function($self, $params, $chain) { + return false; + }); + $this->assertFalse($adapt::_initAdapter('foo', array())); + } + } ?> \ No newline at end of file From c189f747314192c380908d7d53e054fc0787844d Mon Sep 17 00:00:00 2001 From: Simon JAILLET Date: Fri, 21 Dec 2012 16:03:02 +0100 Subject: [PATCH 4/6] Some refactoring of `data\model\Query::alias()`. --- data/model/Query.php | 65 ++++++------------------ data/source/Database.php | 8 ++- tests/cases/data/model/QueryTest.php | 59 ++++++++------------- tests/cases/data/source/DatabaseTest.php | 4 +- 4 files changed, 42 insertions(+), 94 deletions(-) diff --git a/data/model/Query.php b/data/model/Query.php index 74fe66812d..95b4bfde52 100644 --- a/data/model/Query.php +++ b/data/model/Query.php @@ -11,6 +11,7 @@ use lithium\util\Set; use lithium\data\Source; use lithium\core\ConfigException; +use InvalidArgumentException; /** * The `Query` class acts as a container for all information necessary to perform a particular @@ -106,15 +107,6 @@ class Query extends \lithium\core\Object { */ protected $_paths = array(); - /** - * Map beetween relation paths and their corresponding fieldname paths - * - * @see lithium\data\model\Query::alias() - * - * @var array - */ - protected $_relationNames = array(); - /** * Map beetween generated aliases and corresponding models. * @@ -518,11 +510,15 @@ public function data($data = array()) { * @param array $config the config array to set. * @return mixed The relationships array or a relationship array if `$relpath` is set. Returns * `null` if a join doesn't exist. + * @throws InvalidArgumentException */ public function relationships($relpath = null, $config = null) { if ($config) { if (!$relpath) { - throw new ConfigException("The relation dotted path is empty."); + throw new InvalidArgumentException("The relation dotted path is empty."); + } + if (isset($config['model']) && isset($config['alias'])) { + $this->_models[$config['alias']] = $config['model']; } $this->_config['relationships'][$relpath] = $config; return $this; @@ -699,26 +695,20 @@ public function alias($alias = true, $relpath = null) { return $return ?: null; } - if ($relpath) { - $oldAlias = array_search($relpath, $this->_paths); - } else { - $oldAlias = array_search('', $this->_paths); + if ($relpath === null) { + $this->_config['alias'] = $alias; } - unset($this->_models[$oldAlias]); - unset($this->_paths[$oldAlias]); - - $model = $this->_config['model']; - if (!$relpath) { - $this->_alias[$alias] = 1; + if ($relpath === null && ($model = $this->_config['model'])) { $this->_models[$alias] = $model; - $this->_paths[$alias] = ''; - return $this->_config['alias'] = $alias; } - $paths = explode('.', $relpath); - if (!$alias) { - $alias = end($paths); + $relpath = (string) $relpath; + unset($this->_paths[array_search($relpath, $this->_paths)]); + + if (!$alias && $relpath) { + $last = strrpos($relpath, '.'); + $alias = $last ? substr($relpath, $last + 1) : $relpath; } if (isset($this->_alias[$alias])) { @@ -729,34 +719,9 @@ public function alias($alias = true, $relpath = null) { } $this->_paths[$alias] = $relpath; - $fieldname = array(); - foreach ($paths as $path) { - if (!$relation = $model::relations($path)) { - $model = null; - break; - } - $fieldname[] = $relation->fieldName(); - $model = $relation->to(); - } - $this->_models[$alias] = $model; - $this->_relationNames[$relpath] = join('.', $fieldname); return $alias; } - /** - * Return the relation paths mapped to their corredponding fieldname paths. - * - * @param object $source Instance of the data source (`lithium\data\Source`) to use for - * conversion. - * @return array Map between relation paths and their corresponding fieldname paths. - */ - public function relationNames(Source $source = null) { - if ($source) { - $this->applyStrategy($source); - } - return $this->_relationNames; - } - /** * Return the generated aliases mapped to their relation path * diff --git a/data/source/Database.php b/data/source/Database.php index b185a1182f..68537431de 100644 --- a/data/source/Database.php +++ b/data/source/Database.php @@ -213,8 +213,7 @@ public function __construct(array $config = array()) { 'type' => $rel->type(), 'model' => $rel->to(), 'fieldName' => $rel->fieldName(), - 'fromAlias' => $from, - 'toAlias' => $to + 'alias' => $to )); $self->join($context, $rel, $from, $to, $constraints); } @@ -473,11 +472,10 @@ public function read($query, array $options = array()) { case 'array': $columns = $args['schema'] ?: $self->schema($query, $result); - if (!isset($columns['']) || !is_array($columns[''])) { + if (!is_array(reset($columns))) { $columns = array('' => $columns); } - $relationNames = is_object($query) ? $query->relationNames($self) : array(); $i = 0; $records = array(); foreach ($result as $data) { @@ -487,7 +485,7 @@ public function read($query, array $options = array()) { $len = count($cols); $values = array_combine($cols, array_slice($data, $offset, $len)); if ($path) { - $records[$i][$relationNames[$path]] = $values; + $records[$i][$path] = $values; } else { $records[$i] += $values; } diff --git a/tests/cases/data/model/QueryTest.php b/tests/cases/data/model/QueryTest.php index c1952c08c2..c35915eaba 100644 --- a/tests/cases/data/model/QueryTest.php +++ b/tests/cases/data/model/QueryTest.php @@ -38,6 +38,11 @@ public function setUp() { MockQueryComment::$connection = $this->db; } + public function tearDown() { + MockQueryPost::reset(); + MockQueryComment::reset(); + } + /** * Tests that configuration settings are delegating to matching method names */ @@ -391,9 +396,7 @@ public function testJoins() { public function testWithAssociation() { $model = $this->_model; $model::meta('source', 'foo'); - $model::bind('hasMany', 'MockQueryComment', array( - 'class' => 'lithium\tests\mocks\data\model\MockQueryComment' - )); + $model::bind('hasMany', 'MockQueryComment'); $query = new Query(compact('model') + array('with' => 'MockQueryComment')); $export = $query->export(new MockDatabase()); @@ -402,8 +405,7 @@ public function testWithAssociation() { 'type' => 'hasMany', 'model' => 'lithium\tests\mocks\data\model\MockQueryComment', 'fieldName' => 'mock_query_comments', - 'fromAlias' => 'MockQueryPost', - 'toAlias' => 'MockQueryComment' + 'alias' => 'MockQueryComment' )); $keyExists = isset($export['relationships']); $this->assertTrue($keyExists); @@ -612,8 +614,10 @@ public function testAliasAndPaths() { public function testModels() { $model = 'lithium\tests\mocks\data\model\MockQueryPost'; - $query = new Query(compact('model')); - $query->alias(null, 'MockQueryComment'); + $query = new Query(array( + 'model' => $model, + 'with' => 'MockQueryComment' + )); $expected = array( 'MockQueryPost' => 'lithium\tests\mocks\data\model\MockQueryPost', @@ -621,9 +625,14 @@ public function testModels() { ); $this->assertEqual($expected, $query->models($this->db)); - $query->alias('Post'); - $query->alias('Comment', 'MockQueryComment'); - $query->alias('Post2', 'MockQueryComment.MockQueryPost'); + $query = new Query(array( + 'model' => $model, + 'alias' => 'Post', + 'with' => array( + 'MockQueryComment' => array('alias' => 'Comment'), + 'MockQueryComment.MockQueryPost' => array('alias' => 'Post2'), + ) + )); $expected = array( 'Post' => 'lithium\tests\mocks\data\model\MockQueryPost', @@ -633,27 +642,6 @@ public function testModels() { $this->assertEqual($expected, $query->models($this->db)); } - public function testRelationNames() { - $model = 'lithium\tests\mocks\data\model\MockQueryPost'; - $query = new Query(compact('model')); - $query->alias(null, 'MockQueryComment'); - - $expected = array( - 'MockQueryComment' => 'mock_query_comments' - ); - $this->assertEqual($expected, $query->relationNames($this->db)); - - $query->alias('Post'); - $query->alias('Comment', 'MockQueryComment'); - $query->alias('Post2', 'MockQueryComment.MockQueryPost'); - - $expected = array( - 'MockQueryComment' => 'mock_query_comments', - 'MockQueryComment.MockQueryPost' => 'mock_query_comments.mock_query_post' - ); - $this->assertEqual($expected, $query->relationNames($this->db)); - } - public function testExportWithJoinedStrategy() { $query = new Query(array( 'alias' => 'MyAlias', @@ -702,22 +690,19 @@ public function testExportWithJoinedStrategy() { 'type' => 'hasMany', 'model' => 'lithium\tests\mocks\data\model\MockImage', 'fieldName' => 'images', - 'fromAlias' => 'MyAlias', - 'toAlias' => 'Image' + 'alias' => 'Image' ), 'Image.ImageTag' => array( 'type' => 'hasMany', 'model' => 'lithium\tests\mocks\data\model\MockImageTag', 'fieldName' => 'image_tags', - 'fromAlias' => 'Image', - 'toAlias' => 'ImageTag' + 'alias' => 'ImageTag' ), 'Image.ImageTag.Tag' => array( 'type' => 'belongsTo', 'model' => 'lithium\tests\mocks\data\model\MockTag', 'fieldName' => 'tag', - 'fromAlias' => 'ImageTag', - 'toAlias' => 'Tag' + 'alias' => 'Tag' ) ) ); diff --git a/tests/cases/data/source/DatabaseTest.php b/tests/cases/data/source/DatabaseTest.php index edd5f58132..a626322554 100644 --- a/tests/cases/data/source/DatabaseTest.php +++ b/tests/cases/data/source/DatabaseTest.php @@ -1584,13 +1584,13 @@ public function testReturnArrayOnReadWithQuery() { 'author_id' => '2', 'title' => 'Post title', 'created' => '2012-12-17 17:04:00', - 'mock_database_comments' => array( + 'MockDatabaseComment' => array( 'id' => '3', 'post_id' => '1', 'author_id' => '2', 'body' => 'Very good post', 'created' => '2012-12-17 17:05:00', - 'mock_database_post' => array( + 'MockDatabasePost' => array( 'id' => '1', 'author_id' => '2', 'title' => 'Post title', From 386e5ea865a5436a37ee89b4f6f5e16e7d3face7 Mon Sep 17 00:00:00 2001 From: John Coggeshall Date: Wed, 26 Dec 2012 13:39:04 -0500 Subject: [PATCH 5/6] Making Unit return the value of the asserted Expresion consistently --- test/Unit.php | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/test/Unit.php b/test/Unit.php index 84c3aab73c..1ed1d436bb 100644 --- a/test/Unit.php +++ b/test/Unit.php @@ -270,7 +270,7 @@ protected function _normalizeLineEndings($expected, $result) { public function assertEqual($expected, $result, $message = false) { list($expected, $result) = $this->_normalizeLineEndings($expected, $result); $data = ($expected != $result) ? $this->_compare('equal', $expected, $result) : null; - $this->assert($expected == $result, $message, $data); + return $this->assert($expected == $result, $message, $data); } /** @@ -282,7 +282,7 @@ public function assertEqual($expected, $result, $message = false) { */ public function assertNotEqual($expected, $result, $message = false) { list($expected, $result) = $this->_normalizeLineEndings($expected, $result); - $this->assert($result != $expected, $message, compact('expected', 'result')); + return $this->assert($result != $expected, $message, compact('expected', 'result')); } /** @@ -294,7 +294,7 @@ public function assertNotEqual($expected, $result, $message = false) { */ public function assertIdentical($expected, $result, $message = false) { $data = ($expected !== $result) ? $this->_compare('identical', $expected, $result) : null; - $this->assert($expected === $result, $message, $data); + return $this->assert($expected === $result, $message, $data); } /** @@ -317,7 +317,7 @@ public function assertIdentical($expected, $result, $message = false) { */ public function assertTrue($result, $message = '{:message}') { $expected = true; - $this->assert(!empty($result), $message, compact('expected', 'result')); + return $this->assert(!empty($result), $message, compact('expected', 'result')); } /** @@ -342,7 +342,7 @@ public function assertTrue($result, $message = '{:message}') { */ public function assertFalse($result, $message = '{:message}') { $expected = false; - $this->assert(empty($result), $message, compact('expected', 'result')); + return $this->assert(empty($result), $message, compact('expected', 'result')); } /** @@ -353,7 +353,7 @@ public function assertFalse($result, $message = '{:message}') { */ public function assertNull($result, $message = '{:message}') { $expected = null; - $this->assert($result === null, $message, compact('expected', 'result')); + return $this->assert($result === null, $message, compact('expected', 'result')); } /** @@ -365,7 +365,7 @@ public function assertNull($result, $message = '{:message}') { */ public function assertNoPattern($expected, $result, $message = '{:message}') { list($expected, $result) = $this->_normalizeLineEndings($expected, $result); - $this->assert(!preg_match($expected, $result), $message, compact('expected', 'result')); + return $this->assert(!preg_match($expected, $result), $message, compact('expected', 'result')); } /** @@ -377,7 +377,7 @@ public function assertNoPattern($expected, $result, $message = '{:message}') { */ public function assertPattern($expected, $result, $message = '{:message}') { list($expected, $result) = $this->_normalizeLineEndings($expected, $result); - $this->assert(!!preg_match($expected, $result), $message, compact('expected', 'result')); + return $this->assert(!!preg_match($expected, $result), $message, compact('expected', 'result')); } /** @@ -600,8 +600,7 @@ public function assertCookie($expected, $headers = null) { $matched = $this->_cookieMatch($expected, $headers); if (!$matched['match']) { $message = sprintf('%s - Cookie not found in headers.', $matched['pattern']); - $this->assert(false, $message, compact('expected', 'result')); - return false; + return $this->assert(false, $message, compact('expected', 'result')); } return $this->assert(true, '%s'); } @@ -625,8 +624,7 @@ public function assertNoCookie($expected, $headers = null) { $matched = $this->_cookieMatch($expected, $headers); if ($matched['match']) { $message = sprintf('%s - Cookie found in headers.', $matched['pattern']); - $this->assert(false, $message, compact('expected', 'result')); - return false; + return $this->assert(false, $message, compact('expected', 'result')); } return $this->assert(true, '%s'); } From d7d8da304ade3d0b7ce8e469edb7ec3c22e26075 Mon Sep 17 00:00:00 2001 From: Blaine Schmeisser Date: Thu, 27 Dec 2012 20:26:18 -0600 Subject: [PATCH 6/6] Update parser to recognize newlines when PHP does not provide a line For instance the content '}' has no PHP token, so if it is the only thing on a line it'll inherit the line of it's parent line which would be incorrect. --- analysis/Parser.php | 4 ++++ tests/cases/analysis/ParserTest.php | 25 +++++++++++++++++++++---- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/analysis/Parser.php b/analysis/Parser.php index 08207072bc..54b8f219cb 100644 --- a/analysis/Parser.php +++ b/analysis/Parser.php @@ -74,6 +74,10 @@ public static function tokenize($code, array $options = array()) { } } $tokens[] = array('id' => $id, 'name' => $name, 'content' => $content, 'line' => $line); + + if ($id === T_WHITESPACE) { + $line += count(preg_split('/\r\n|\r|\n/', $content)) - 1; + } } if ($options['wrap'] && empty($options['include'])) { diff --git a/tests/cases/analysis/ParserTest.php b/tests/cases/analysis/ParserTest.php index b6b71c325c..4f2b701300 100644 --- a/tests/cases/analysis/ParserTest.php +++ b/tests/cases/analysis/ParserTest.php @@ -56,7 +56,12 @@ public function testFullTokenization() { $result = Parser::tokenize('$foo = function() {};'); $this->assertEqual(11, count($result)); - $expected = array('id' => 309, 'name' => 'T_VARIABLE', 'content' => '$foo', 'line' => 1); + $expected = array( + 'id' => T_VARIABLE, + 'name' => 'T_VARIABLE', + 'content' => '$foo', + 'line' => 1 + ); $this->assertEqual($expected, $result[0]); $expected = array('id' => null, 'name' => ';', 'content' => ';', 'line' => 1); @@ -99,9 +104,9 @@ public function testFilteredTokenization() { $result = Parser::tokenize($code, array('include' => array('T_IF', 'T_WHILE', 'T_CATCH'))); $expected = array( - array('id' => 318, 'name' => 'T_WHILE', 'content' => 'while', 'line' => 1), - array('id' => 301, 'name' => 'T_IF', 'content' => 'if', 'line' => 1), - array('id' => 338, 'name' => 'T_CATCH', 'content' => 'catch', 'line' => 3) + array('id' => T_WHILE, 'name' => 'T_WHILE', 'content' => 'while', 'line' => 1), + array('id' => T_IF, 'name' => 'T_IF', 'content' => 'if', 'line' => 1), + array('id' => T_CATCH, 'name' => 'T_CATCH', 'content' => 'catch', 'line' => 3) ); $this->assertEqual($expected, $result); } @@ -136,6 +141,18 @@ function ($i) { return join('', $i); }, $expected = array('ClassName', 'method'); $this->assertEqual($expected, $results); } + + public function testParserGuessesLineBleed() { + $code = <<assertIdentical('}', $tokens[13]['content']); + $this->assertIdentical(3, $tokens[13]['line']); + } + } ?> \ No newline at end of file