diff --git a/.travis.yml b/.travis.yml index 8bf5226..a0b6549 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,14 +1,14 @@ language: php -php: ["5.3", "5.4", "5.5", "5.6", "hhvm"] +php: ["5.3", "5.4", "5.5", "5.6", "hhvm", "hhvm-nightly"] matrix: - allow_failures: [{"php": "5.6"}, {"php": "hhvm"}] + allow_failures: [{"php": "hhvm"}, {"php": "hhvm-nightly"}] fast_finish: true env: global: - - ARCHER_PUBLISH_VERSION=5.5 + - ARCHER_PUBLISH_VERSION=5.6 - secure: "O/QlJbj5sRzjQifZBowuwIlBk9g1Ig6kGULzAdx0oXHEGgurH47IZcHkDBHvmqaAABXtE4K3jrd3cuX6Vm/7GiRThA5dYYMt40qnJdLEt8HlhrnmmlSIJQioS6l6x3UbBC3xvaeQYZIXgkyUjFSWVWsACXjYJwqtXaCuXXAvyNQ=" install: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6570281..4772c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Isolator Changelog +### 3.0.0 (2014-10-08) + +* **[BC]** Removed `Isolator::getIsolator()` and `resetIsolator()` +* **[BC]** Removed `Isolator::className()` (the full class name is now always `Icecave\Isolator\Isolator`) +* **[FIXED]** Calling functions with variable arguments now works correctly in PHP 5.6 +* **[NEW]** Added `Isolator::set()` +* **[IMPROVED]** Code is now generated via a custom autoloader, and then cached, providing a massive performance improvement +* **[IMRPOVED]** Several micro-optimisations to invocation of function-like language constructs + +While this release contains several backwards compatibility breaks, the `Isolator` class itself still behaves as per the +examples given in the README file of v2.3.0. + ### 2.3.0 (2014-08-12) * **[NEW]** Added support for isolation of the `new` operator @@ -16,7 +28,7 @@ ### 2.1.2 (2013-04-04) -* **[FIX]** Return values of isolated `include/require[_once]` calls are now propagated correctly +* **[FIXED]** Return values of isolated `include/require[_once]` calls are now propagated correctly * **[NEW]** Integrated `icecave/archer` (previously `icecave/testing`) ### 2.1.1 (2013-01-13) diff --git a/README.md b/README.md index e200b66..a42179c 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ class MyDocument } ``` -MyDocument now takes an instance of `Isolator` in it's constructor. It would be a pain - and unnecessary - to specify -the `Isolator` instance every time you construct an object in your production code, so a shared instance is made +`MyDocument` now takes an instance of `Isolator` in it's constructor. It would be a pain - and unnecessary - to create a +new `Isolator` instance every time you construct an object in your production code, so a shared instance is made accessible using the `Isolator::get()` method. If a non-null value is passed to `Isolator::get()` it is returned unchanged, allowing you to replace the isolator when necessary. @@ -81,16 +81,14 @@ test suite below. for mocking. Phake provides a more flexible alternative to PHPUnit's built-in mock objects.* ```php -use Icecave\Isolator\Isolator; - class MyDocumentTest extends PHPUnit_Framework_TestCase { public function setUp() { // First a mocked isolator instance is created ... - $this->isolator = Phake::mock(Isolator::className()); + $this->isolator = Phake::mock('Icecave\Isolator\Isolator'); - // That isolator instance is provided to the MyDocument instance + // That isolator instance is given to the MyDocument instance // that is to be tested ... $this->myDocument = new MyDocument('foo.txt', $this->isolator); } @@ -118,9 +116,9 @@ class MyDocumentTest extends PHPUnit_Framework_TestCase The test verifies the behavior of the `MyDocument` class completely, without requiring any disk access. -Using an isolator is most helpful when testing code that uses global functions which maintain global state or utilize -external resources such as databases, filesystems, etc. It is usually unnecessary to mock out deterministic functions -such as `strlen()`, for example. +Using an isolator is most helpful when testing code that uses functions which maintain global state or utilize external +resources such as databases, filesystems, etc. It is usually unnecessary to mock out deterministic functions such as +`strlen()`, for example. ## Isolator Trait @@ -145,7 +143,6 @@ class MyDocument } protected $filename; - protected $isolator; } ``` @@ -163,9 +160,15 @@ class MyDocument Several of PHP's core global functions have some peculiarities and inconsitencies in the way they are defined. **Isolator** attempts to accomodate such inconsistencies when possible, but may have issues with some native C functions -for which parameter reflection information is non-standard or incorrect. +for which parameter reflection information is non-standard or incorrect. These issues seem to be largely rectified as of +PHP 5.6. + +## Contact us + +* Follow [@IcecaveStudios](https://twitter.com/IcecaveStudios) on Twitter +* Visit the [Icecave Studios website](http://icecave.com.au) -[Build Status]: http://img.shields.io/travis/IcecaveStudios/isolator/develop.svg -[Test Coverage]: http://img.shields.io/coveralls/IcecaveStudios/isolator/develop.svg -[SemVer]: http://img.shields.io/:semver-2.3.0-brightgreen.svg +[Build Status]: http://img.shields.io/travis/IcecaveStudios/isolator/develop.svg?style=flat-square +[Test Coverage]: http://img.shields.io/coveralls/IcecaveStudios/isolator/develop.svg?style=flat-square +[SemVer]: http://img.shields.io/:semver-3.0.0-brightgreen.svg?style=flat-square diff --git a/benchmark/benchmark.php b/benchmark/benchmark.php new file mode 100755 index 0000000..a168330 --- /dev/null +++ b/benchmark/benchmark.php @@ -0,0 +1,87 @@ +#!/usr/bin/env php +strlen('foo'); + } +); + +runBenchmark( + 'variable arguments', + function () use ($isolator) { + $isolator->sprintf('%d %d %d %d %d', 1, 2, 3, 4, 5); + } +); + +runBenchmark( + 'reference parameter', + function () use ($isolator) { + $matches = array(); + $isolator->preg_match('/./', 'a', $matches); + } +); + +runBenchmark( + 'echo', + function () use ($isolator) { + $isolator->echo('.'); + } +); + +runBenchmark( + 'new', + function () use ($isolator) { + $isolator->new('TestClass'); + } +); + +runBenchmark( + 'new with arguments', + function () use ($isolator) { + $isolator->new('TestClass', 'arg'); + } +); + +runBenchmark( + 'eval', + function () use ($isolator) { + $isolator->eval('return 1;'); + } +); diff --git a/composer.json b/composer.json index 5834fd2..e101cbc 100644 --- a/composer.json +++ b/composer.json @@ -1,7 +1,15 @@ { "name": "icecave/isolator", "description": "Dependency injection for global functions.", - "keywords": ["test", "phpunit", "phake", "unit", "mock", "fake"], + "keywords": [ + "test", + "phpunit", + "unit", + "mock", + "fake", + "double", + "stub" + ], "homepage": "https://github.com/IcecaveStudios/isolator", "license": "MIT", "authors": [ @@ -17,12 +25,17 @@ "require-dev": { "icecave/archer": "~1" }, - "suggest": { - "eloquent/asplode": "Drop-in exception-based error handling." - }, "autoload": { "psr-4": { "Icecave\\Isolator\\": "src" + }, + "files": [ + "src/register-autoloader.php" + ] + }, + "extra": { + "branch-alias": { + "dev-develop": "3.0.x-dev" } } } diff --git a/composer.lock b/composer.lock index f91e8c3..38d1d4b 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "cd3bd98a401c759263120c414cdb77c5", + "hash": "709e2c626a4055d53c6114c7e869b2a1", "packages": [], "packages-dev": [ { @@ -160,16 +160,16 @@ }, { "name": "icecave/archer", - "version": "1.2.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/IcecaveStudios/archer.git", - "reference": "49e90d8c0d0a63e537b26d90591367093ebf45be" + "reference": "5b1b4748d0fd6a8f977486d15d10e52e68349379" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/IcecaveStudios/archer/zipball/49e90d8c0d0a63e537b26d90591367093ebf45be", - "reference": "49e90d8c0d0a63e537b26d90591367093ebf45be", + "url": "https://api.github.com/repos/IcecaveStudios/archer/zipball/5b1b4748d0fd6a8f977486d15d10e52e68349379", + "reference": "5b1b4748d0fd6a8f977486d15d10e52e68349379", "shasum": "" }, "require": { @@ -182,6 +182,7 @@ }, "require-dev": { "eloquent/liberator": "~1", + "phpunit/phpunit": "~4", "symfony/event-dispatcher": "~2.1" }, "suggest": { @@ -192,6 +193,11 @@ "bin/woodhouse" ], "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "1.3.x-dev" + } + }, "autoload": { "psr-4": { "Icecave\\Archer\\": "src" @@ -228,7 +234,7 @@ "testing", "unit" ], - "time": "2014-06-23 00:33:17" + "time": "2014-09-18 03:05:28" }, { "name": "nikic/php-parser", @@ -536,17 +542,17 @@ }, { "name": "symfony/config", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Config", "source": { "type": "git", "url": "https://github.com/symfony/Config.git", - "reference": "8d044668c7ccb4ade684e368d910e3aadcff6f6c" + "reference": "0316364bfebc8b080077c731a99f189341476bd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Config/zipball/8d044668c7ccb4ade684e368d910e3aadcff6f6c", - "reference": "8d044668c7ccb4ade684e368d910e3aadcff6f6c", + "url": "https://api.github.com/repos/symfony/Config/zipball/0316364bfebc8b080077c731a99f189341476bd7", + "reference": "0316364bfebc8b080077c731a99f189341476bd7", "shasum": "" }, "require": { @@ -580,21 +586,21 @@ ], "description": "Symfony Config Component", "homepage": "http://symfony.com", - "time": "2014-08-05 09:00:40" + "time": "2014-09-23 05:25:11" }, { "name": "symfony/console", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Console", "source": { "type": "git", "url": "https://github.com/symfony/Console.git", - "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63" + "reference": "ca053eaa031c93afb68a71e4eb1f4168dfd4a661" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/cd2d1e4bac2206b337326b0140ff475fe9ad5f63", - "reference": "cd2d1e4bac2206b337326b0140ff475fe9ad5f63", + "url": "https://api.github.com/repos/symfony/Console/zipball/ca053eaa031c93afb68a71e4eb1f4168dfd4a661", + "reference": "ca053eaa031c93afb68a71e4eb1f4168dfd4a661", "shasum": "" }, "require": { @@ -635,21 +641,21 @@ ], "description": "Symfony Console Component", "homepage": "http://symfony.com", - "time": "2014-08-05 09:00:40" + "time": "2014-09-25 09:53:56" }, { "name": "symfony/event-dispatcher", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/EventDispatcher", "source": { "type": "git", "url": "https://github.com/symfony/EventDispatcher.git", - "reference": "8faf5cc7e80fde74a650a36e60d32ce3c3e0457b" + "reference": "f6281337bf5f985f585d1db6a83adb05ce531f46" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/8faf5cc7e80fde74a650a36e60d32ce3c3e0457b", - "reference": "8faf5cc7e80fde74a650a36e60d32ce3c3e0457b", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/f6281337bf5f985f585d1db6a83adb05ce531f46", + "reference": "f6281337bf5f985f585d1db6a83adb05ce531f46", "shasum": "" }, "require": { @@ -658,7 +664,7 @@ "require-dev": { "psr/log": "~1.0", "symfony/config": "~2.0", - "symfony/dependency-injection": "~2.0", + "symfony/dependency-injection": "~2.0,<2.6.0", "symfony/stopwatch": "~2.2" }, "suggest": { @@ -692,21 +698,21 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "http://symfony.com", - "time": "2014-07-28 13:20:46" + "time": "2014-09-28 15:56:11" }, { "name": "symfony/filesystem", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Filesystem", "source": { "type": "git", "url": "https://github.com/symfony/Filesystem.git", - "reference": "c1309b0ee195ad264a4314435bdaecdfacb8ae9c" + "reference": "4e62fab0060a826561c78b665925b37c870c45f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Filesystem/zipball/c1309b0ee195ad264a4314435bdaecdfacb8ae9c", - "reference": "c1309b0ee195ad264a4314435bdaecdfacb8ae9c", + "url": "https://api.github.com/repos/symfony/Filesystem/zipball/4e62fab0060a826561c78b665925b37c870c45f5", + "reference": "4e62fab0060a826561c78b665925b37c870c45f5", "shasum": "" }, "require": { @@ -739,21 +745,21 @@ ], "description": "Symfony Filesystem Component", "homepage": "http://symfony.com", - "time": "2014-07-09 09:05:48" + "time": "2014-09-22 09:14:18" }, { "name": "symfony/finder", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Finder", "source": { "type": "git", "url": "https://github.com/symfony/Finder.git", - "reference": "090fe4eaff414d8f2171c7a4748ea868d530775f" + "reference": "d5033742b9a6206ef6d06e813870bca18e9205df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Finder/zipball/090fe4eaff414d8f2171c7a4748ea868d530775f", - "reference": "090fe4eaff414d8f2171c7a4748ea868d530775f", + "url": "https://api.github.com/repos/symfony/Finder/zipball/d5033742b9a6206ef6d06e813870bca18e9205df", + "reference": "d5033742b9a6206ef6d06e813870bca18e9205df", "shasum": "" }, "require": { @@ -786,21 +792,21 @@ ], "description": "Symfony Finder Component", "homepage": "http://symfony.com", - "time": "2014-07-28 13:20:46" + "time": "2014-09-27 08:35:39" }, { "name": "symfony/process", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Process", "source": { "type": "git", "url": "https://github.com/symfony/Process.git", - "reference": "e0997d2a9a1a763484b34b989900b61322a9b056" + "reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Process/zipball/e0997d2a9a1a763484b34b989900b61322a9b056", - "reference": "e0997d2a9a1a763484b34b989900b61322a9b056", + "url": "https://api.github.com/repos/symfony/Process/zipball/8a1ec96c4e519cee0fb971ea48a1eb7369dda54b", + "reference": "8a1ec96c4e519cee0fb971ea48a1eb7369dda54b", "shasum": "" }, "require": { @@ -833,21 +839,21 @@ ], "description": "Symfony Process Component", "homepage": "http://symfony.com", - "time": "2014-08-05 09:00:40" + "time": "2014-09-23 05:25:11" }, { "name": "symfony/stopwatch", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Stopwatch", "source": { "type": "git", "url": "https://github.com/symfony/Stopwatch.git", - "reference": "086c8c98c3016f59f5e6e7b15b751c2384b311e5" + "reference": "9f8a33a24f2378c0ec5f372a8d50b2d43069c050" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/086c8c98c3016f59f5e6e7b15b751c2384b311e5", - "reference": "086c8c98c3016f59f5e6e7b15b751c2384b311e5", + "url": "https://api.github.com/repos/symfony/Stopwatch/zipball/9f8a33a24f2378c0ec5f372a8d50b2d43069c050", + "reference": "9f8a33a24f2378c0ec5f372a8d50b2d43069c050", "shasum": "" }, "require": { @@ -880,21 +886,21 @@ ], "description": "Symfony Stopwatch Component", "homepage": "http://symfony.com", - "time": "2014-08-06 06:44:37" + "time": "2014-09-22 09:14:18" }, { "name": "symfony/yaml", - "version": "v2.5.3", + "version": "v2.5.5", "target-dir": "Symfony/Component/Yaml", "source": { "type": "git", "url": "https://github.com/symfony/Yaml.git", - "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f" + "reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Yaml/zipball/5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f", - "reference": "5a75366ae9ca8b4792cd0083e4ca4dff9fe96f1f", + "url": "https://api.github.com/repos/symfony/Yaml/zipball/b1dbc53593b98c2d694ebf383660ac9134d30b96", + "reference": "b1dbc53593b98c2d694ebf383660ac9134d30b96", "shasum": "" }, "require": { @@ -927,7 +933,7 @@ ], "description": "Symfony Yaml Component", "homepage": "http://symfony.com", - "time": "2014-08-05 09:00:40" + "time": "2014-09-22 09:14:18" }, { "name": "twig/twig", @@ -990,6 +996,7 @@ "aliases": [], "minimum-stability": "stable", "stability-flags": [], + "prefer-stable": false, "platform": { "php": ">=5.3" }, diff --git a/src/Detail/AbstractIsolator.php b/src/Detail/AbstractIsolator.php new file mode 100644 index 0000000..bba587c --- /dev/null +++ b/src/Detail/AbstractIsolator.php @@ -0,0 +1,103 @@ +newInstanceArgs($arguments); + } + + if ($arguments) { + return \call_user_func_array($name, $arguments); + } + + return $name(); + } + + /** + * Fetch an isolator instance. + * + * This convenience method returns the global isolator instance, or $instance if provided. + * + * @param Isolator|null $isolator An existing isolator instance, if available. + * + * @return Isolator The global isolator instance, or $isolator if provided. + */ + public static function get(Isolator $isolator = null) + { + if (null !== $isolator) { + return $isolator; + } elseif (!self::$instance) { + self::$instance = new Isolator(); + } + + return self::$instance; + } + + /** + * Set the default isolator instance. + * + * @param Isolator $isolator The isolator instance. + */ + public static function set(Isolator $isolator = null) + { + self::$instance = $isolator; + } + + private static $instance; +} diff --git a/src/Detail/Autoloader.php b/src/Detail/Autoloader.php new file mode 100644 index 0000000..66a5c2a --- /dev/null +++ b/src/Detail/Autoloader.php @@ -0,0 +1,106 @@ +path = $path; + $this->codeGenerator = $codeGenerator; + } + + /** + * Load an isolator class with the given name. + * + * @param string $className The name to give to the isolator class. + */ + public function load($className) + { + $functions = get_defined_functions(); + $functions = $functions['internal']; + + $hash = $this->computeHash($className, $functions); + + $fileName = $this->path . DIRECTORY_SEPARATOR . 'Isolator' . $hash . '.php'; + + if (!file_exists($fileName)) { + $this->generateIsolatorClass( + $fileName, + $className, + $functions + ); + } + + require_once $fileName; + } + + /** + * Compute a unique hash used to identify an isolator implementation that + * proxies the given functions. + * + * @param string $className The name to give to the isolator class. + * @param array $functions An array of global function names. + * + * @return string + */ + public function computeHash($className, array $functions) + { + $hash = hash_init('sha1'); + + hash_update($hash, $className); + + foreach ($functions as $function) { + hash_update($hash, $function); + } + + // Include the PHP version in the hash in case function signatures + // change between versions. + hash_update($hash, phpversion()); + + // Include the Isolator version in the hash in case code generation + // changes between versions. + hash_update($hash, PackageInfo::VERSION); + + return hash_final($hash); + } + + /** + * Generate and store an isolator implementation. + * + * @param string $className The name to give to the isolator class. + * @param array $functions An array of global function names. + */ + public function generateIsolatorClass( + $fileName, + $className, + array $functions + ) { + $dirName = dirname($fileName); + + if (!file_exists($dirName)) { + mkdir($dirName, 0777, true); + } + + $code = $this + ->codeGenerator + ->generate( + $className, + $functions + ); + + file_put_contents($fileName, $code); + } + + private $path; + private $codeGenerator; +} diff --git a/src/Detail/CodeGenerator.php b/src/Detail/CodeGenerator.php new file mode 100644 index 0000000..72bfef8 --- /dev/null +++ b/src/Detail/CodeGenerator.php @@ -0,0 +1,168 @@ +ellipsisExpansion = $ellipsisExpansion; + } + + /** + * Generate an isolator implementation. + */ + public function generate($className, array $functions) + { + $pos = strrpos($className, '\\'); + + if ($pos) { + $namespace = substr($className, 0, $pos); + $shortName = substr($className, $pos + 1); + } else { + $namespace = null; + $shortName = $className; + } + + $code = 'generateMethod( + new ReflectionFunction($function) + ); + } + + $code .= '}' . PHP_EOL; + + return $code; + } + + private function generateMethod(ReflectionFunction $reflector) + { + $name = $reflector->getName(); + + list($minArity, $maxArity, $refIndices) = $this->inspectParameters($reflector); + + $signature = $this->generateSignature( + $name, + $reflector->returnsReference(), + $minArity, + $maxArity, + $refIndices + ); + + $code = ' ' . $signature . PHP_EOL; + $code .= ' {' . PHP_EOL; + $code .= $this->generateSwitch($name, $minArity, $maxArity); + $code .= ' return \call_user_func_array(' . var_export($reflector->getName(), true) . ', \func_get_args());' . PHP_EOL; + $code .= ' }' . PHP_EOL; + + return $code; + } + + private function generateSignature( + $name, + $returnsReference, + $minArity, + $maxArity, + $refIndices + ) { + $parameters = array(); + + for ($index = 0; $index < $maxArity; ++$index) { + $param = '$p' . $index; + + if ($refIndices[$index]) { + $param = '&' . $param; + } + + if ($index >= $minArity) { + $param .= ' = null'; + } + + $parameters[] = $param; + } + + return sprintf( + 'public function %s%s(%s)', + $returnsReference ? '&' : '', + str_replace('\\', '_', $name), + implode(', ', $parameters) + ); + } + + private function generateSwitch($name, $minArity, $maxArity) + { + $code = ' switch (\func_num_args()) {' . PHP_EOL; + + for ($arity = $minArity; $arity <= $maxArity; ++$arity) { + $code .= sprintf( + ' case %d: %s', + $arity, + $this->generateReturn($name, $arity) + ); + } + + $code .= ' }' . PHP_EOL; + + return $code; + } + + private function generateReturn($name, $arity) + { + $arguments = array(); + for ($index = 0; $index < $arity; ++$index) { + $arguments[] = '$p' . $index; + } + + return sprintf( + 'return \%s(%s);' . PHP_EOL, + $name, + implode(', ', $arguments) + ); + } + + private function inspectParameters(ReflectionFunction $reflector) + { + $minArity = 0; + $maxArity = 0; + $refIndices = array(); + + foreach ($reflector->getParameters() as $parameter) { + // PHP versions < 5.6 showed a parameter named '...' to indicate + // that a function took an arbitrary number of arguments. + // + // @codeCoverageIgnoreStart + if ($parameter->getName() === '...') { + break; + } + // @codeCoverageIgnoreEnd + + $refIndices[$maxArity++] = $parameter->isPassedByReference(); + + if (!$parameter->isOptional()) { + ++$minArity; + } + } + + return array($minArity, $maxArity, $refIndices); + } + + private $ellipsisExpansion; +} diff --git a/src/Generator.php b/src/Generator.php deleted file mode 100644 index e5867aa..0000000 --- a/src/Generator.php +++ /dev/null @@ -1,159 +0,0 @@ -ellipsisExpansion = $ellipsisExpansion; - $this->isolator = $isolator ?: new Isolator; // Note, Isolator::getIsolator is not used to avoid recursion. - } - - public function generateClass(array $functionReflectors, $className = null, $baseClassName = 'Isolator') - { - if ($className === null) { - $className = 'Isolator' . self::$count++; - } - - $code = 'namespace ' . __NAMESPACE__ . ' {' . PHP_EOL; - $code .= 'class ' . $className . ' extends ' . $baseClassName . ' {' . PHP_EOL; - $code .= PHP_EOL; - - foreach ($functionReflectors as $reflector) { - if ($this->requiresIsolatorProxy($reflector)) { - $code .= $this->generateProxyMethod($reflector); - $code .= PHP_EOL; - } - } - - $code .= '} // End class.' . PHP_EOL; - $code .= '} // End namespace.' . PHP_EOL; - - $this->isolator->eval($code); - - return new ReflectionClass(__NAMESPACE__ . '\\' . $className); - } - - public function requiresIsolatorProxy(ReflectionFunction $reflector) - { - if ($reflector->isDisabled()) { - return false; - } elseif ($reflector->returnsReference()) { - return true; - } - - foreach ($reflector->getParameters() as $parameter) { - if ($parameter->isPassedByReference()) { - return true; - } - } - - return false; - } - - public function inspect(ReflectionFunction $reflector) - { - $minArity = 0; - $maxArity = 0; - $refIndices = array(); - - foreach ($reflector->getParameters() as $parameter) { - $refIndices[$maxArity++] = $parameter->isPassedByReference(); - - if (!$parameter->isOptional()) { - ++$minArity; - } - - if ($parameter->getName() === '...') { - $ref = $parameter->isPassedByReference(); - for ($count = 1; $count < $this->ellipsisExpansion; ++$count) { - $refIndices[$maxArity++] = $ref; - } - break; - } - } - - return array($minArity, $maxArity, $refIndices); - } - - public function generateProxyMethod(ReflectionFunction $reflector) - { - list($minArity, $maxArity, $refIndices) = $this->inspect($reflector); - $name = $reflector->getName(); - $code = $this->generateSignature($name, $reflector->returnsReference(), $minArity, $maxArity, $refIndices) . ' {' . PHP_EOL; - $code .= $this->generateSwitch($name, $minArity, $maxArity); - $code .= $this->generateReturn($name, $maxArity); - $code .= '} // End function ' . $name . '.' . PHP_EOL; - $code .= PHP_EOL; - - return $code; - } - - protected function generateSignature($name, $returnsReference, $minArity, $maxArity, $refIndices) - { - $parameters = array(); - for ($index = 0; $index < $maxArity; ++$index) { - $param = ''; - if ($refIndices[$index]) { - $param .= '&'; - } - $param .= '$_' . $index; - if ($index >= $minArity) { - $param .= ' = null'; - } - $parameters[] = $param; - } - - return sprintf( - 'public function %s%s(%s)' - , $returnsReference ? '&' : '' - , str_replace('\\', '_', $name) - , implode(', ', $parameters) - ); - } - - protected function generateSwitch($name, $minArity, $maxArity) - { - if ($minArity === $maxArity) { - return ''; - } - - $code = 'switch (func_num_args()) {' . PHP_EOL; - - for ($arity = $minArity; $arity < $maxArity; ++$arity) { - $code .= sprintf( - 'case %d: %s' - , $arity - , $this->generateReturn($name, $arity) - ); - } - - $code .= '} // End switch.' . PHP_EOL; - - return $code; - } - - protected function generateReturn($name, $arity) - { - $arguments = array(); - for ($index = 0; $index < $arity; ++$index) { - $arguments[] = '$_' . $index; - } - - return sprintf( - 'return \%s(%s);' . PHP_EOL - , $name - , implode(', ', $arguments) - ); - } - - private static $count = 0; - protected $ellipsisExpansionDepth; - protected $isolator; -} diff --git a/src/Isolator.php b/src/Isolator.php deleted file mode 100644 index dc19364..0000000 --- a/src/Isolator.php +++ /dev/null @@ -1,144 +0,0 @@ -newInstanceArgs($arguments); - } - - return call_user_func_array($name, $arguments); - } - - /** - * Fetch an isolator instance. - * - * This convenience method returns the global isolator instance, or $instance if provided. - * - * @param Isolator|null $instance An existing isolator instance, if available. - * - * @return Isolator The global isolator instance, or $instance if provided. - */ - public static function get(Isolator $instance = null) - { - if ($instance) { - return $instance; - } - - return static::getIsolator(); - } - - /** - * Fetch the isolator class name. - * - * @return string The concrete class name for the global isolator instance. - */ - public static function className() - { - return get_class(static::get()); - } - - /** - * Fetch the default isolator instance, constructing it if necessary. - * - * @param boolean $handleReferences Indicates whether or not the isolator should account for functions with reference parameters and return types. - * @param Generator|null $generator The Generator instance to use to construct the concreate isolator class, or null to use the default. - * @param Isolator|null $isolator The isolator used to access the global list of functions, or null to use the default. - */ - public static function getIsolator($handleReferences = true, Generator $generator = null, Isolator $isolator = null) - { - // Global instance already initialized ... - if (self::$instance !== null) { - return self::$instance; - } - - // No need to handle references, rely on default Isolator::__call() method ... - if (!$handleReferences) { - return self::$instance = new self; - } - - // Construct an isolator generator to create the concreate isolator class ... - if ($generator === null) { - $generator = new Generator; - } - - // Get a basic isolator to use for reflection ... - if ($isolator === null) { - $isolator = new self; - } - - // Create reflectors for each of the globally defined functions ... - $functionReflectors = array(); - foreach ($isolator->get_defined_functions() as $functions) { - foreach ($functions as $name) { - $functionReflectors[] = new ReflectionFunction($name); - } - } - - // Generate the concrete isolator class and install it as the global instance ... - $classReflector = $generator->generateClass($functionReflectors); - - return self::$instance = $classReflector->newInstance(); - } - - /** - * Reset the default isolator instance. - */ - public static function resetIsolator() - { - self::$instance = null; - } - - private static $instance; -} diff --git a/src/IsolatorTrait.php b/src/IsolatorTrait.php index fc0b196..2bc4178 100644 --- a/src/IsolatorTrait.php +++ b/src/IsolatorTrait.php @@ -13,7 +13,11 @@ trait IsolatorTrait */ public function isolator() { - return Isolator::get($this->isolator); + if ($this->isolator) { + return $this->isolator; + } + + return Isolator::get(); } /** diff --git a/src/PackageInfo.php b/src/PackageInfo.php index 30f10ed..45ce146 100644 --- a/src/PackageInfo.php +++ b/src/PackageInfo.php @@ -4,5 +4,5 @@ class PackageInfo { const NAME = 'Isolator'; - const VERSION = '2.3.0'; + const VERSION = '3.0.0'; } diff --git a/src/register-autoloader.php b/src/register-autoloader.php new file mode 100644 index 0000000..d17ec03 --- /dev/null +++ b/src/register-autoloader.php @@ -0,0 +1,19 @@ +load($className); + } +); diff --git a/test/src/function.php b/test/src/function.php new file mode 100644 index 0000000..76a1684 --- /dev/null +++ b/test/src/function.php @@ -0,0 +1,7 @@ +path = tempnam(sys_get_temp_dir(), 'isolator-'); + $this->codeGenerator = Phake::mock(__NAMESPACE__ . '\CodeGenerator'); + + $this->autoloader = new Autoloader( + $this->path, + $this->codeGenerator + ); + + unlink($this->path); + + Phake::when($this->codeGenerator) + ->generate(Phake::anyParameters()) + ->thenReturn('expectOutputString('Included!'); + + $this->autoloader->load('Foo'); + + $functions = get_defined_functions(); + + Phake::verify($this->codeGenerator)->generate( + 'Foo', + $functions['internal'] + ); + + // Additional load should not produce additional output ... + $this->autoloader->load('Foo'); + + Phake::verify($this->codeGenerator, Phake::times(1))->generate(Phake::anyParameters()); + } + +} diff --git a/test/suite/Detail/CodeGeneratorTest.php b/test/suite/Detail/CodeGeneratorTest.php new file mode 100644 index 0000000..ea80565 --- /dev/null +++ b/test/suite/Detail/CodeGeneratorTest.php @@ -0,0 +1,140 @@ +generator = new CodeGenerator(); + } + + public function testGenerateClass() + { + $code = $this->generator->generate( + 'Foo', + array() + ); + + $expectedCode = 'assertSame( + $expectedCode, + $code + ); + } + + public function testGenerateClassInNamespace() + { + $code = $this->generator->generate( + 'Foo\Bar\Spam', + array() + ); + + $expectedCode = 'assertSame( + $expectedCode, + $code + ); + } + + public function testGenerateMethod() + { + $code = $this->generator->generate( + 'Foo', + array('strlen') + ); + + $expectedCode = 'assertSame( + $expectedCode, + $code + ); + } + + public function testGenerateMethodWithReferenceParameter() + { + $code = $this->generator->generate( + 'Foo', + array('ereg') + ); + + $expectedCode = 'assertSame( + $expectedCode, + $code + ); + } + + public function testGenerateMethodWithVarArgs() + { + $code = $this->generator->generate( + 'Foo', + array('sprintf') + ); + + $expectedCode = 'assertSame( + $expectedCode, + $code + ); + } +} diff --git a/test/suite/GeneratorTest.php b/test/suite/GeneratorTest.php deleted file mode 100644 index 5460eca..0000000 --- a/test/suite/GeneratorTest.php +++ /dev/null @@ -1,390 +0,0 @@ -isolator = Phake::mock('Icecave\Isolator\Isolator'); - $this->generator = Phake::partialMock( - 'Icecave\Isolator\Generator', - 5, - $this->isolator - ); - } - - public function testCallWithReference() - { - $isolator = Isolator::get(); - - $matches = array(); - - $isolator->preg_match('/.*/', 'foo', $matches); - - $this->assertSame(array('foo'), $matches); - } - - public function testGenerateClass() - { - $reflector = Phake::mock('ReflectionFunction'); - $reflectors = array($reflector); - - Phake::when($this->generator) - ->requiresIsolatorProxy(Phake::anyParameters()) - ->thenReturn(true); - - Phake::when($this->generator) - ->generateProxyMethod(Phake::anyParameters()) - ->thenReturn('/* method goes here */'); - - $this->setExpectedException('ReflectionException', 'Class Icecave\Isolator\TestIsolatorClass does not exist'); - try { - $this->generator->generateClass($reflectors, 'TestIsolatorClass'); - } catch (ReflectionException $e) { - $expectedCode = 'namespace Icecave\Isolator {' . PHP_EOL; - $expectedCode .= 'class TestIsolatorClass extends Isolator {' . PHP_EOL; - $expectedCode .= PHP_EOL; - $expectedCode .= '/* method goes here */' . PHP_EOL; - $expectedCode .= '} // End class.' . PHP_EOL; - $expectedCode .= '} // End namespace.' . PHP_EOL; - - Phake::inOrder( - Phake::verify($this->generator)->requiresIsolatorProxy($reflector), - Phake::verify($this->generator)->generateProxyMethod($reflector), - Phake::verify($this->isolator)->eval($expectedCode) - ); - throw $e; - } - } - - public function testInspect() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(true); - - Phake::when($parameter1) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->isOptional() - ->thenReturn(true); - - $expected = array( - 1, - 2, - array(false, true), - ); - - $this->assertSame($expected, $this->generator->inspect($reflector)); - - Phake::inOrder( - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter1)->isOptional(), - Phake::verify($parameter1)->getName(), - Phake::verify($parameter2)->isPassedByReference(), - Phake::verify($parameter2)->isOptional(), - Phake::verify($parameter2)->getName() - ); - } - - public function testInspectEllipsis() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter1) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->getName() - ->thenReturn('...'); - - $expected = array( - 2, - 6, - array( - false, - false, - false, - false, - false, - false, - ), - ); - - $this->assertSame($expected, $this->generator->inspect($reflector)); - - $parameter2Verifier = Phake::verify($parameter2, Phake::times(2)); - - Phake::inOrder( - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter1)->isOptional(), - Phake::verify($parameter1)->getName(), - $parameter2Verifier->isPassedByReference(), - Phake::verify($parameter2)->isOptional(), - Phake::verify($parameter2)->getName(), - $parameter2Verifier->isPassedByReference() - ); - } - - public function testInspectEllipsisOptional() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter1) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->isOptional() - ->thenReturn(true); - - Phake::when($parameter2) - ->getName() - ->thenReturn('...'); - - $expected = array( - 1, - 6, - array( - false, - false, - false, - false, - false, - false, - ), - ); - - $this->assertSame($expected, $this->generator->inspect($reflector)); - - $parameter2Verifier = Phake::verify($parameter2, Phake::times(2)); - - Phake::inOrder( - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter1)->isOptional(), - Phake::verify($parameter1)->getName(), - $parameter2Verifier->isPassedByReference(), - Phake::verify($parameter2)->isOptional(), - Phake::verify($parameter2)->getName(), - $parameter2Verifier->isPassedByReference() - ); - } - - public function testInspectEllipsisReference() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(true); - - Phake::when($parameter1) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->isOptional() - ->thenReturn(false); - - Phake::when($parameter2) - ->getName() - ->thenReturn('...'); - - $expected = array( - 2, - 6, - array( - false, - true, - true, - true, - true, - true, - ), - ); - - $this->assertSame($expected, $this->generator->inspect($reflector)); - - $parameter2Verifier = Phake::verify($parameter2, Phake::times(2)); - - Phake::inOrder( - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter1)->isOptional(), - Phake::verify($parameter1)->getName(), - $parameter2Verifier->isPassedByReference(), - Phake::verify($parameter2)->isOptional(), - Phake::verify($parameter2)->getName(), - $parameter2Verifier->isPassedByReference() - ); - } - - public function testRequiresIsolatorProxyDisabled() - { - $reflector = Phake::mock('ReflectionFunction'); - Phake::when($reflector) - ->isDisabled() - ->thenReturn(true); - - $this->assertFalse($this->generator->requiresIsolatorProxy($reflector)); - - Phake::verify($reflector)->isDisabled(); - } - - public function testRequiresIsolatorProxyReturnsReference() - { - $reflector = Phake::mock('ReflectionFunction'); - Phake::when($reflector) - ->isDisabled() - ->thenReturn(false); - - Phake::when($reflector) - ->returnsReference() - ->thenReturn(true); - - $this->assertTrue($this->generator->requiresIsolatorProxy($reflector)); - - Phake::inOrder( - Phake::verify($reflector)->isDisabled(), - Phake::verify($reflector)->returnsReference() - ); - } - - public function testRequiresIsolatorProxyReferenceParameter() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->isDisabled() - ->thenReturn(false); - - Phake::when($reflector) - ->returnsReference() - ->thenReturn(false); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(true); - - $this->assertTrue($this->generator->requiresIsolatorProxy($reflector)); - - Phake::inOrder( - Phake::verify($reflector)->isDisabled(), - Phake::verify($reflector)->returnsReference(), - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter2)->isPassedByReference() - ); - } - - public function testRequiresIsolatorProxyNoReferenceParameters() - { - $reflector = Phake::mock('ReflectionFunction'); - $parameter1 = Phake::mock('ReflectionParameter'); - $parameter2 = Phake::mock('ReflectionParameter'); - - Phake::when($reflector) - ->isDisabled() - ->thenReturn(false); - - Phake::when($reflector) - ->returnsReference() - ->thenReturn(false); - - Phake::when($reflector) - ->getParameters() - ->thenReturn(array($parameter1, $parameter2)); - - Phake::when($parameter1) - ->isPassedByReference() - ->thenReturn(false); - - Phake::when($parameter2) - ->isPassedByReference() - ->thenReturn(false); - - $this->assertFalse($this->generator->requiresIsolatorProxy($reflector)); - - Phake::inOrder( - Phake::verify($reflector)->isDisabled(), - Phake::verify($reflector)->returnsReference(), - Phake::verify($reflector)->getParameters(), - Phake::verify($parameter1)->isPassedByReference(), - Phake::verify($parameter2)->isPassedByReference() - ); - } -} diff --git a/test/suite/IsolatorTest.php b/test/suite/IsolatorTest.php index b27c2a8..fde13c1 100644 --- a/test/suite/IsolatorTest.php +++ b/test/suite/IsolatorTest.php @@ -2,174 +2,197 @@ namespace Icecave\Isolator; use DateTime; -use Phake; use PHPUnit_Framework_TestCase; -use ReflectionClass; -use ReflectionFunction; use SplObjectStorage; +/** + * @covers Icecave\Isolator\Isolator + * @covers Icecave\Isolator\Detail\AbstractIsolator + */ class IsolatorTest extends PHPUnit_Framework_TestCase { + public function setUp() + { + $this->isolator = new Isolator(); + } + public function tearDown() { - parent::tearDown(); - Isolator::resetIsolator(); + Isolator::set(null); } public function testCall() { - $isolator = new Isolator; - $this->assertSame(3, $isolator->strlen('foo')); + $this->assertSame( + 3, + $this->isolator->strlen('foo') + ); + } + + public function testCallWithReference() + { + $matches = array(); + + $this->isolator->preg_match( + '/.*/', + 'foo', + $matches + ); + + $this->assertSame( + array('foo'), + $matches + ); + } + + public function testCallWithVarArgs() + { + $this->assertSame( + 'a b c', + $this->isolator->sprintf( + '%s %s %s', + 'a', + 'b', + 'c' + ) + ); + } + + public function testCallFunctionDefinedAfterIsolatorCreated() + { + if (function_exists('icecaveIsolatorPostGeneration')) { + $this->markTestSkipped('This test can only be executed once.'); + } + + require __DIR__ . '/../src/function.php'; + + $this->assertNull( + $this->isolator->icecaveIsolatorPostGeneration() + ); + + $this->assertSame( + $this->isolator->icecaveIsolatorPostGeneration(123), + 123 + ); } public function testEcho() { - $isolator = new Isolator; $this->expectOutputString('Echo works!'); - $isolator->echo('Echo works!'); + + $this->isolator->echo('Echo works!'); } public function testEval() { - $isolator = new Isolator; - $this->assertSame(3, $isolator->eval('return strlen("foo");')); + $this->assertSame( + 3, + $this->isolator->eval('return strlen("foo");') + ); } public function testInclude() { - $isolator = new Isolator; - $this->assertFalse(class_exists('Icecave\Isolator\ClassA', false)); + $this->assertFalse( + class_exists('Icecave\Isolator\ClassA', false) + ); $this->assertSame( 'returnValueA', - $isolator->include(__DIR__ . '/../src/ClassA.php') + $this->isolator->include(__DIR__ . '/../src/ClassA.php') + ); + + $this->assertTrue( + class_exists('Icecave\Isolator\ClassA', false) ); - $this->assertTrue(class_exists('Icecave\Isolator\ClassA', false)); } public function testIncludeOnce() { - $isolator = new Isolator; - $this->assertFalse(class_exists('Icecave\Isolator\ClassB', false)); + $this->assertFalse( + class_exists('Icecave\Isolator\ClassB', false) + ); $this->assertSame( 'returnValueB', - $isolator->include_once(__DIR__ . '/../src/ClassB.php') + $this->isolator->include_once(__DIR__ . '/../src/ClassB.php') + ); + + $this->assertTrue( + class_exists('Icecave\Isolator\ClassB', false) ); - $this->assertTrue(class_exists('Icecave\Isolator\ClassB', false)); } public function testRequire() { - $isolator = new Isolator; - $this->assertFalse(class_exists('Icecave\Isolator\ClassC', false)); + $this->assertFalse( + class_exists('Icecave\Isolator\ClassC', false) + ); $this->assertSame( 'returnValueC', - $isolator->require(__DIR__ . '/../src/ClassC.php') + $this->isolator->require(__DIR__ . '/../src/ClassC.php') + ); + + $this->assertTrue( + class_exists('Icecave\Isolator\ClassC', false) ); - $this->assertTrue(class_exists('Icecave\Isolator\ClassC', false)); } public function testRequireOnce() { - $isolator = new Isolator; - $this->assertFalse(class_exists('Icecave\Isolator\ClassD', false)); + $this->assertFalse( + class_exists('Icecave\Isolator\ClassD', false) + ); $this->assertSame( 'returnValueD', - $isolator->require_once(__DIR__ . '/../src/ClassD.php') + $this->isolator->require_once(__DIR__ . '/../src/ClassD.php') + ); + + $this->assertTrue( + class_exists('Icecave\Isolator\ClassD', false) ); - $this->assertTrue(class_exists('Icecave\Isolator\ClassD', false)); } public function testNew() { - $isolator = new Isolator; - $this->assertEquals( - new SplObjectStorage, - $isolator->new('SplObjectStorage') + new SplObjectStorage(), + $this->isolator->new('SplObjectStorage') ); } public function testNewWithConstructorArguments() { - $isolator = new Isolator; - $this->assertEquals( new DateTime('2014-01-01 01:02:03 GMT'), - $isolator->new('DateTime', '2014-01-01 01:02:03 GMT') + $this->isolator->new('DateTime', '2014-01-01 01:02:03 GMT') ); } public function testGet() { - $isolator = new Isolator; - $this->assertSame($isolator, Isolator::get($isolator)); - $this->assertInstanceOf('Icecave\Isolator\Isolator', Isolator::get(null)); - } - - public function testGetIsolator() - { - $isolator = Isolator::getIsolator(); - $this->assertInstanceOf('Icecave\Isolator\Isolator', $isolator); - $this->assertSame($isolator, Isolator::getIsolator()); - } - - public function testGetIsolatorNoReferences() - { - $isolator = Isolator::getIsolator(false); - $this->assertSame('Icecave\Isolator\Isolator', get_class($isolator)); - } + $this->assertSame( + $this->isolator, + Isolator::get($this->isolator) + ); - public function testGetIsolatorExistingInstance() - { - $isolator = Isolator::getIsolator(false); - $this->assertInstanceOf('Icecave\Isolator\Isolator', $isolator); - $this->assertSame($isolator, Isolator::getIsolator(false)); - } + $globalIsolator = Isolator::get(); - public function testGetIsolatorNewInstance() - { - $generator = Phake::mock('Icecave\Isolator\Generator'); - Phake::when($generator) - ->generateClass(Phake::anyParameters()) - ->thenReturn(new ReflectionClass('Icecave\Isolator\Isolator')); - - $functions = array( - 'internal' => array( - 'strlen' - ) + $this->assertInstanceOf( + 'Icecave\Isolator\Isolator', + $globalIsolator ); - $reflectors = array( - new ReflectionFunction('strlen') + $this->assertNotSame( + $this->isolator, + $globalIsolator ); - $internalIsolator = Phake::mock('Icecave\Isolator\Isolator'); - Phake::when($internalIsolator) - ->get_defined_functions() - ->thenReturn($functions); - - $isolator = Isolator::getIsolator(true, $generator, $internalIsolator); - $this->assertInstanceOf('Icecave\Isolator\Isolator', $isolator); - - Phake::inOrder( - Phake::verify($internalIsolator)->get_defined_functions() - , Phake::verify($generator)->generateClass($reflectors) + $this->assertSame( + $globalIsolator, + Isolator::get() ); - - // Invoking a second time should not produce any additional calls to the generator or isolator ... - Phake::verifyNoFurtherInteraction($generator); - Phake::verifyNoFurtherInteraction($internalIsolator); - $this->assertSame($isolator, Isolator::getIsolator(true, $generator, $isolator)); - } - - public function testClassName() - { - $isolator = Isolator::getIsolator(); - - $this->assertSame(get_class($isolator), Isolator::className()); } } diff --git a/test/suite/IsolatorTraitTest.php b/test/suite/IsolatorTraitTest.php index 81f7bda..183dc91 100644 --- a/test/suite/IsolatorTraitTest.php +++ b/test/suite/IsolatorTraitTest.php @@ -12,7 +12,7 @@ class IsolatorTraitTest extends PHPUnit_Framework_TestCase { public function testIsolator() { - $object = new TraitUsage; + $object = new TraitUsage(); $instance = Isolator::get();