diff --git a/.travis.yml b/.travis.yml index f088192a1..2aa812ed6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -68,13 +68,7 @@ install: - ln -s ${TRAVIS_BUILD_DIR}/.temp ${TRAVIS_BUILD_DIR}/cphalcon/.temp - ( cd cphalcon; zephir fullclean && zephir generate $ZEND_BACKEND ) - ( cd cphalcon/ext; export CFLAGS="-g3 -O1 -std=gnu90 -Wall -DZEPHIR_RELEASE=1"; /usr/bin/phpize &> /dev/null && ./configure --silent --enable-phalcon &> /dev/null && make --silent -j3 &> /dev/null && make --silent install ) - - phpenv config-add tests/_ci/phalcon.ini - - phpenv config-add tests/_ci/redis.ini - - if [[ "${PHP_MAJOR:0:1}" != "7" ]]; then phpenv config-add tests/_ci/mongo.ini; fi; - - phpenv config-add tests/_ci/mongodb.ini - - phpenv config-add tests/_ci/memcached.ini - - pecl channel-update pecl.php.net - - if [[ "${PHP_MAJOR:0:1}" = "7" ]]; then printf "\n" | pecl install yaml-2.0.0 > /dev/null 2>&1; else printf "\n" | pecl install yaml > /dev/null 2>&1; fi; + - ( bash tests/_ci/install_prereqs_$PHP_MAJOR.sh ) before_script: - stty cols 160 diff --git a/Library/Phalcon/Annotations/Extended/AbstractAdapter.php b/Library/Phalcon/Annotations/Extended/AbstractAdapter.php new file mode 100644 index 000000000..ba7afcf23 --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/AbstractAdapter.php @@ -0,0 +1,92 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Annotations\Extended; + +use Phalcon\Annotations\Adapter; +use Phalcon\Annotations\Exception; +use Phalcon\Annotations\Reflection; +use Phalcon\Traits\ConfigurableTrait; + +/** + * Phalcon\Annotations\Extended\AbstractAdapter + * + * This is the base class for Phalcon\Annotations\Extended adapters + * + * @package Phalcon\Annotations\Extended + */ +abstract class AbstractAdapter extends Adapter implements AdapterInterface +{ + use ConfigurableTrait; + + /** + * Configurable properties. + * @var array + */ + protected $configurable = []; + + /** + * AbstractAdapter constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + $this->setParameters($options); + } + + /** + * Returns prefixed identifier. + * + * @param string $id + * @return string + */ + abstract protected function getPrefixedIdentifier($id); + + /** + * Check and cast returned result. + * + * @param mixed $result + * @return bool + */ + protected function castResult($result) + { + if ($result instanceof Reflection) { + return $result; + } + + return false; + } + + /** + * Check annotation key. + * + * @param string $key + * + * @throws Exception + */ + protected function checkKey($key) + { + if (!is_string($key)) { + throw new Exception( + sprintf('Invalid key type key to retrieve annotations. Expected string but got %s.', gettype($key)) + ); + } + } +} diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Apc.php b/Library/Phalcon/Annotations/Extended/Adapter/Apc.php new file mode 100644 index 000000000..49982066f --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/Adapter/Apc.php @@ -0,0 +1,201 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Annotations\Extended\Adapter; + +use Phalcon\Annotations\Exception; +use Phalcon\Annotations\Reflection; +use Phalcon\Annotations\Extended\AbstractAdapter; + +/** + * Phalcon\Annotations\Extended\Adapter\Apc + * + * Extended Apc adapter for storing annotations in the APC(u). + * This adapter is suitable for production. + * + * + * use Phalcon\Annotations\Extended\Adapter\Apc; + * + * $annotations = new Apc( + * [ + * 'prefix' => 'app-annotations', // Optional + * 'lifetime' => 8600, // Optional + * 'statsKey' => '_PHAN', // Optional + * ] + * ); + * + * + * @package Phalcon\Annotations\Extended\Adapter + */ +class Apc extends AbstractAdapter +{ + /** + * The storage key prefix. + * @var string + */ + protected $prefix = ''; + + /** + * The storage lifetime. + * @var int + */ + protected $lifetime = 8600; + + /** + * Storage stats key + * @var string + */ + protected $statsKey = '_PHAN'; + + /** + * Configurable properties. + * @var array + */ + protected $configurable = [ + 'prefix', + 'lifetime', + 'statsKey', + ]; + + /** + * Reads parsed annotations from APC(u). + * + * @param string $key + * @return Reflection|bool + * + * @throws Exception + */ + public function read($key) + { + $this->checkKey($key); + + $prefixedKey = $this->getPrefixedIdentifier($key); + + if (function_exists('apcu_fetch')) { + $result = apcu_fetch($prefixedKey); + } else { + $result = apc_fetch($prefixedKey); + } + + return $this->castResult($result); + } + + /** + * Writes parsed annotations to APC(u) + * + * @param string $key + * @param Reflection $data + * @return bool + * + * @throws Exception + */ + public function write($key, Reflection $data) + { + $this->checkKey($key); + + $prefixedKey = $this->getPrefixedIdentifier($key); + + if (function_exists('apcu_store')) { + return apcu_store($prefixedKey, $data, $this->lifetime); + } + + return apc_store($prefixedKey, $data, $this->lifetime); + } + + /** + * {@inheritdoc} + * + * + * use Phalcon\Annotations\Extended\Apc; + * + * $annotations = new Apc(['prefix' => 'app-annotations']); + * $annotations->flush(); + * + * + * @return bool + */ + public function flush() + { + $prefixPattern = '#^_PHAN' . preg_quote("{$this->prefix}", '#') . '#'; + + if (class_exists('\APCuIterator')) { + foreach (new \APCuIterator($prefixPattern) as $item) { + apcu_delete($item['key']); + } + + return true; + } + + foreach (new \APCIterator('user', $prefixPattern) as $item) { + apc_delete($item['key']); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @param string $id + * @return string + */ + protected function getPrefixedIdentifier($id) + { + return $this->statsKey . $this->prefix . $id; + } + + /** + * Sets the storage key prefix. + * + * @param string $prefix The storage key prefix. + * @return $this + */ + protected function setPrefix($prefix) + { + $this->prefix = (string) $prefix; + + return $this; + } + + /** + * Sets the storage lifetime. + * + * @param int $lifetime The storage lifetime. + * @return $this + */ + protected function setLifetime($lifetime) + { + $this->lifetime = (int) $lifetime; + + return $this; + } + + /** + * Sets the storage stats key. + * + * @param string $statsKey The storage key prefix. + * @return $this + */ + protected function setStatsKey($statsKey) + { + $this->statsKey = (string) $statsKey; + + return $this; + } +} diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Files.php b/Library/Phalcon/Annotations/Extended/Adapter/Files.php new file mode 100644 index 000000000..a22d0ceef --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/Adapter/Files.php @@ -0,0 +1,167 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Annotations\Extended\Adapter; + +use Phalcon\Annotations\Exception; +use Phalcon\Annotations\Reflection; +use Phalcon\Annotations\Extended\AbstractAdapter; + +/** + * Phalcon\Annotations\Extended\Adapter\Files + * + * Stores the parsed annotations in files. + * This adapter is suitable for production. + * + * + * use Phalcon\Annotations\Adapter\Files; + * + * $annotations = new Files( + * [ + * "annotationsDir" => "app/cache/annotations/", + * ] + * ); + * + * + * @package Phalcon\Annotations\Extended\Adapter + */ +class Files extends AbstractAdapter +{ + protected $annotationsDir = './'; + + /** + * Configurable properties. + * @var array + */ + protected $configurable = [ + 'annotationsDir', + ]; + + /** + * Files adapter constructor. + * + * @param array $options + */ + public function __construct(array $options = []) + { + if (!isset($options['annotationsDir'])) { + $options['annotationsDir'] =sys_get_temp_dir(); + } + + parent::__construct($options); + } + + /** + * Sets the annotations dir. + * + * @param string $annotationsDir The storage key prefix. + * @return $this + */ + protected function setAnnotationsDir($annotationsDir) + { + $annotationsDir = (string) $annotationsDir; + $this->annotationsDir = rtrim($annotationsDir, '\\/') . DIRECTORY_SEPARATOR; + + return $this; + } + + /** + * Reads parsed annotations from memory. + * + * @param string $key + * @return Reflection|bool + * + * @throws Exception + */ + public function read($key) + { + $this->checkKey($key); + + $result = null; + $path = $this->getPrefixedIdentifier($key); + + if (file_exists($path)) { + /** @noinspection PhpIncludeInspection */ + $result = require $path; + } + + return $this->castResult($result); + } + + /** + * Writes parsed annotations to files. + * + * @param string $key + * @param Reflection $reflection + * @return bool + * + * @throws Exception + */ + public function write($key, Reflection $reflection) + { + $this->checkKey($key); + + $path = $this->getPrefixedIdentifier($key); + + if (file_put_contents($path, ' + * use Phalcon\Annotations\Extended\Files; + * + * $annotations = new Files(['annotationsDir' => BASE_DIR . '/cache/']); + * $annotations->flush(); + * + * + * @return bool + */ + public function flush() + { + $iterator = new \DirectoryIterator($this->annotationsDir); + foreach ($iterator as $item) { + if ($item->isDot() || !$item->isFile() || $item->getExtension() !== 'php') { + continue; + } + + unlink($item->getPathname()); + } + + return true; + } + + /** + * {@inheritdoc} + * + * @param string $id + * @return string + */ + protected function getPrefixedIdentifier($key) + { + $key = strtolower(str_replace(['\\', '/', ':'], '_', $key)); + + return $this->annotationsDir . preg_replace('#_{2,}#', '_', $key) . '.php'; + } +} diff --git a/Library/Phalcon/Annotations/Extended/Adapter/Memory.php b/Library/Phalcon/Annotations/Extended/Adapter/Memory.php new file mode 100644 index 000000000..63bd07ba9 --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/Adapter/Memory.php @@ -0,0 +1,109 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Annotations\Extended\Adapter; + +use Phalcon\Annotations\Exception; +use Phalcon\Annotations\Reflection; +use Phalcon\Annotations\Extended\AbstractAdapter; + +/** + * Phalcon\Annotations\Extended\Adapter\Memory + * + * Extended Memory adapter for storing annotations in memory. + * This adapter is the suitable development/testing. + * + * + * use Phalcon\Annotations\Extended\Adapter\Memory; + * + * $annotations = new Memory(); + * + * + * @package Phalcon\Annotations\Extended\Adapter + */ +class Memory extends AbstractAdapter +{ + protected $data = []; + + /** + * Reads parsed annotations from memory. + * + * @param string $key + * @return Reflection|bool + * + * @throws Exception + */ + public function read($key) + { + $this->checkKey($key); + + $result = null; + $prefixedKey = $this->getPrefixedIdentifier($key); + + if (isset($this->data[$prefixedKey])) { + $result = $this->data[$prefixedKey]; + } + + return $this->castResult($result); + } + + /** + * Writes parsed annotations to memory. + * + * @param string $key + * @param Reflection $reflection + * @return bool + * + * @throws Exception + */ + public function write($key, Reflection $reflection) + { + $this->checkKey($key); + + $prefixedKey = $this->getPrefixedIdentifier($key); + + $this->data[$prefixedKey] = $reflection; + + + return true; + } + + /** + * Immediately invalidates all existing items. + * + * @return bool + */ + public function flush() + { + $this->data = []; + + return true; + } + + /** + * {@inheritdoc} + * + * @param string $id + * @return string + */ + protected function getPrefixedIdentifier($key) + { + return strtolower($key); + } +} diff --git a/Library/Phalcon/Annotations/Extended/AdapterInterface.php b/Library/Phalcon/Annotations/Extended/AdapterInterface.php new file mode 100644 index 000000000..b1d37ea26 --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/AdapterInterface.php @@ -0,0 +1,57 @@ + | + +------------------------------------------------------------------------+ +*/ + +namespace Phalcon\Annotations\Extended; + +use Phalcon\Annotations\Reflection; +use Phalcon\Annotations\AdapterInterface as BaseInterface; + +/** + * Phalcon\Annotations\Extended\AdapterInterface + * + * This interface must be implemented by adapters in Phalcon\Annotations\Extended + * + * @package Phalcon\Annotations\Extended + */ +interface AdapterInterface extends BaseInterface +{ + /** + * Reads parsed annotations from the current storage. + * + * @param string $key + * @return Reflection|bool + */ + public function read($key); + + /** + * Writes parsed annotations to the current storage. + * + * @param string $key + * @param Reflection $reflection + * @return bool + */ + public function write($key, Reflection $reflection); + + /** + * Immediately invalidates all existing items. + * + * @return bool + */ + public function flush(); +} diff --git a/Library/Phalcon/Annotations/Extended/README.md b/Library/Phalcon/Annotations/Extended/README.md new file mode 100644 index 000000000..ac7a64f20 --- /dev/null +++ b/Library/Phalcon/Annotations/Extended/README.md @@ -0,0 +1,54 @@ +# Phalcon\Annotations\Extended\Adapter + +The main goals of this package: + +* Extended `AdapterInterface` with methods `read`, `write` and `flush` +* Work only with `Phalcon\Annotations\Reflection` (`read` and `write`) +* Ability to set custom `statsKey` +* Work separately from current `Phalcon\Cache\BackendInterface` + +In the future a set of these adapters will be part of the Phalcon Framework. +Usage examples of the adapters available here: + +## Apc + +Stores the parsed annotations in the [Alternative PHP Cache (APC)](http://php.net/manual/en/intro.apcu.php) +using either _APCu_ or _APC_ extension. This adapter is suitable for production. + +```php +use Phalcon\Annotations\Extended\Adapter\Apc; + +$di->set('annotations', function () { + return new Apc([ + 'lifetime' => 8600, // Optional + 'statsSey' => '_PHAN', // Optional + 'prefix' => 'app-annotations-', // Optional + ]); +}); +``` + +## Memory + +Stores the parsed annotations in the memory. This adapter is the suitable development/testing. + +```php +use Phalcon\Annotations\Extended\Adapter\Memory; + +$di->set('annotations', function () { + return new Memory(); +}); +``` + +## Files + +Stores the parsed annotations in files. This adapter is suitable for production. + +```php +use Phalcon\Annotations\Adapter\Files; + +$annotations = new Files( + [ + "annotationsDir" => "app/cache/annotations/", + ] +); +``` diff --git a/README.md b/README.md index 37f8827a5..98188212f 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,9 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) * [Phalcon\Annotations\Adapter\Memcached](Library/Phalcon/Annotations/Adapter) - Memcached adapter for storing annotations (@igusev) * [Phalcon\Annotations\Adapter\Redis](Library/Phalcon/Annotations/Adapter) - Redis adapter for storing annotations (@sergeyklay) * [Phalcon\Annotations\Adapter\Aerospike](Library/Phalcon/Annotations/Adapter) - Aerospike adapter for storing annotations (@sergeyklay) +* [Phalcon\Annotations\Extended\Adapter\Apc](Library/Phalcon/Annotations/Extended/Adapter) - Extended Apc adapter for storing annotations in the APC(u) (@sergeyklay) +* [Phalcon\Annotations\Extended\Adapter\Memory](Library/Phalcon/Annotations/Extended/Adapter) - Extended Memory adapter for storing annotations in the memory (@sergeyklay) +* [Phalcon\Annotations\Extended\Adapter\Files](Library/Phalcon/Annotations/Extended/Adapter) - Extended Files adapter for storing annotations in files (@sergeyklay) ### Behaviors * [Phalcon\Mvc\Model\Behavior\Blameable](Library/Phalcon/Mvc/Model/Behavior) - logs with every created or updated row in your database who created and who updated it (@phalcon) diff --git a/tests/_ci/apc_bc.ini b/tests/_ci/apc_bc.ini new file mode 100644 index 000000000..6f0dcf505 --- /dev/null +++ b/tests/_ci/apc_bc.ini @@ -0,0 +1,15 @@ +; See https://pear.php.net/bugs/bug.php?id=21007 + +[APCu] +extension=apcu.so + +[APC] +extension=apc.so +apc.enabled=1 +apc.enable_cli=1 + +; If the system is flooded with i/o and some update procedures are taking longer than 2 seconds, +; this setting should be increased to enable the protection on those slower update operations. +apc.file_update_protection=0 + +; apc.cache_by_default=0 diff --git a/tests/_ci/install_prereqs_5.5.sh b/tests/_ci/install_prereqs_5.5.sh new file mode 100755 index 000000000..7976782e9 --- /dev/null +++ b/tests/_ci/install_prereqs_5.5.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Phalcon Framework +# +# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com) +# +# This source file is subject to the New BSD License that is bundled +# with this package in the file LICENSE.txt. +# +# 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 license@phalconphp.com so we can send you a copy immediately. + +CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}" + +pecl channel-update pecl.php.net + +printf "\n" | pecl install apcu-4.0.11 &> /dev/null + +echo "apc.enable_cli=On" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + +printf "\n" | pecl install yaml > /dev/null 2>&1 + +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongo.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini diff --git a/tests/_ci/install_prereqs_5.6.sh b/tests/_ci/install_prereqs_5.6.sh new file mode 100755 index 000000000..7976782e9 --- /dev/null +++ b/tests/_ci/install_prereqs_5.6.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +# +# Phalcon Framework +# +# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com) +# +# This source file is subject to the New BSD License that is bundled +# with this package in the file LICENSE.txt. +# +# 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 license@phalconphp.com so we can send you a copy immediately. + +CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}" + +pecl channel-update pecl.php.net + +printf "\n" | pecl install apcu-4.0.11 &> /dev/null + +echo "apc.enable_cli=On" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + +printf "\n" | pecl install yaml > /dev/null 2>&1 + +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongo.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini diff --git a/tests/_ci/install_prereqs_7.0.sh b/tests/_ci/install_prereqs_7.0.sh new file mode 100755 index 000000000..d604e41a1 --- /dev/null +++ b/tests/_ci/install_prereqs_7.0.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# +# Phalcon Framework +# +# Copyright (c) 2011-2016 Phalcon Team (https://www.phalconphp.com) +# +# This source file is subject to the New BSD License that is bundled +# with this package in the file LICENSE.txt. +# +# 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 license@phalconphp.com so we can send you a copy immediately. + +CURRENT_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +TRAVIS_BUILD_DIR="${TRAVIS_BUILD_DIR:-$(dirname $(dirname $CURRENT_DIR))}" + +CFLAGS="-O2 -g3 -fno-strict-aliasing -std=gnu90"; + +pecl channel-update pecl.php.net + +install_apcu() { + # See https://github.com/krakjoe/apcu/issues/203 + git clone -q https://github.com/krakjoe/apcu -b v5.1.7 /tmp/apcu + cd /tmp/apcu + + phpize &> /dev/null + ./configure &> /dev/null + + make --silent -j4 &> /dev/null + make --silent install +} + +install_apcu_bc() { + git clone -q https://github.com/krakjoe/apcu-bc /tmp/apcu-bc + cd /tmp/apcu-bc + + phpize &> /dev/null + ./configure &> /dev/null + + make --silent -j4 &> /dev/null + make --silent install +} + +install_apcu +install_apcu_bc + +phpenv config-add "${TRAVIS_BUILD_DIR}/tests/_ci/apc_bc.ini" + +printf "\n" | pecl install yaml-2.0.0 >/dev/null 2>&1 + +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/phalcon.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/redis.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/mongodb.ini +phpenv config-add ${TRAVIS_BUILD_DIR}/tests/_ci/memcached.ini diff --git a/tests/unit.suite.5.yml b/tests/unit.suite.5.yml index a74416b10..07d369f7f 100644 --- a/tests/unit.suite.5.yml +++ b/tests/unit.suite.5.yml @@ -7,9 +7,11 @@ modules: # enabled modules and helpers enabled: - Helper\Unit + - Filesystem - Aerospike - Asserts - Mockery + - Apc - Db config: Db: diff --git a/tests/unit.suite.yml b/tests/unit.suite.yml index ba1047bf5..60320e213 100644 --- a/tests/unit.suite.yml +++ b/tests/unit.suite.yml @@ -7,8 +7,10 @@ modules: # enabled modules and helpers enabled: - Helper\Unit + - Filesystem - Asserts - Mockery + - Apc - Db config: Db: diff --git a/tests/unit/Annotations/Extended/Adapter/ApcTest.php b/tests/unit/Annotations/Extended/Adapter/ApcTest.php new file mode 100644 index 000000000..c0b3bb056 --- /dev/null +++ b/tests/unit/Annotations/Extended/Adapter/ApcTest.php @@ -0,0 +1,176 @@ +markTestSkipped('Warning: apc extension is not loaded'); + } + + if (!ini_get('apc.enabled') || (PHP_SAPI === 'cli' && !ini_get('apc.enable_cli'))) { + $this->markTestSkipped('Warning: apc.enable_cli must be set to "On"'); + } + + if (extension_loaded('apcu') && version_compare(phpversion('apcu'), '5.1.6', '=')) { + $this->markTestSkipped('Warning: APCu v5.1.6 was broken. See: https://github.com/krakjoe/apcu/issues/203'); + } + } + + /** @test */ + public function shouldReadFromApcWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Apc(); + + $this->tester->haveInApc('_PHAN' . 'read-1', $reflection); + $this->assertEquals($reflection, $annotations->read('read-1')); + } + + /** @test */ + public function shouldReadFromApcWithPrefix() + { + $reflection = $this->getReflection(); + $annotations = new Apc(['prefix' => 'prefix-']); + + $this->tester->haveInApc('_PHAN' . 'prefix-read-2', $reflection); + $this->assertEquals($reflection, $annotations->read('read-2')); + } + + /** @test */ + public function shouldWriteToTheApcWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Apc(); + + $this->assertTrue($annotations->write('write-1', $reflection)); + $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'write-1')); + } + + /** @test */ + public function shouldWriteToTheApcWithPrefix() + { + $reflection = $this->getReflection(); + $annotations = new Apc(['prefix' => 'prefix-']); + + $this->assertTrue($annotations->write('write-2', $reflection)); + $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'prefix-write-2')); + } + + /** @test */ + public function shouldFlushTheApcStorageWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Apc(); + + $this->tester->haveInApc('_PHAN' . 'flush-1', $reflection); + $this->tester->haveInApc('_ANOTHER' . 'flush-1', $reflection); + + $this->assertTrue($annotations->flush()); + $this->tester->dontSeeInApc('_PHAN' . 'flush-1'); + $this->tester->seeInApc('_ANOTHER' . 'flush-1', $reflection); + } + + /** @test */ + public function shouldFlushTheApcStorageWithPrefix() + { + $reflection = $this->getReflection(); + $annotations = new Apc(['prefix' => 'prefix-']); + + $this->tester->haveInApc('_PHAN' . 'prefix-flush-2', $reflection); + $this->tester->haveInApc('_ANOTHER' . 'prefix-flush-2', $reflection); + + $this->assertTrue($annotations->flush()); + $this->tester->dontSeeInApc('_PHAN' . 'prefix-flush-2'); + $this->tester->seeInApc('_ANOTHER' . 'prefix-flush-2', $reflection); + } + + /** @test */ + public function shouldReadAndWriteFromApcWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Apc(); + + $this->assertTrue($annotations->write('read-write-1', $reflection)); + $this->assertEquals($reflection, $annotations->read('read-write-1')); + $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'read-write-1')); + } + + /** @test */ + public function shouldReadAndWriteFromApcWithPrefix() + { + $reflection = $this->getReflection(); + $annotations = new Apc(['prefix' => 'prefix-']); + + $this->assertTrue($annotations->write('read-write-2', $reflection)); + $this->assertEquals($reflection, $annotations->read('read-write-2')); + $this->assertEquals($reflection, $this->tester->grabValueFromApc('_PHAN' . 'prefix-read-write-2')); + } + + /** + * @test + * @dataProvider providerKey + * @param mixed $key + * @param string $prefix + * @param string|null $statsKey + * @param string $expected + */ + public function shouldGetValueFromApcByUsingPrefixedIdentifier($key, $prefix, $statsKey, $expected) + { + if ($statsKey === null) { + $options = ['prefix' => $prefix]; + } else { + $options = ['prefix' => $prefix, 'statsKey' => $statsKey]; + } + + $annotations = new Apc($options); + $reflectedMethod = new ReflectionMethod(get_class($annotations), 'getPrefixedIdentifier'); + $reflectedMethod->setAccessible(true); + + $this->assertEquals($expected, $reflectedMethod->invoke($annotations, $key)); + } + + public function providerKey() + { + return [ + ['key1', '', null, '_PHANkey1' ], + ['key1', '-some-', '_PHAN', '_PHAN-some-key1'], + [1, '', null, '_PHAN1' ], + [1, 2, '_PHAN', '_PHAN21' ], + ['_key1', '', null, '_PHAN_key1' ], + ['_key1', '/', '_PHAN', '_PHAN/_key1' ], + ['key1', '#', null, '_PHAN#key1' ], + ['key1', '', '', 'key1' ], + ['key1', '', '_XXX', '_XXXkey1' ], + ['key1', 'xxx-', '', 'xxx-key1' ], + ]; + } + + protected function getReflection() + { + return Reflection::__set_state([ + '_reflectionData' => [ + 'class' => [], + 'methods' => [], + 'properties' => [], + ] + ]); + } +} diff --git a/tests/unit/Annotations/Extended/Adapter/FilesTest.php b/tests/unit/Annotations/Extended/Adapter/FilesTest.php new file mode 100644 index 000000000..8195a790e --- /dev/null +++ b/tests/unit/Annotations/Extended/Adapter/FilesTest.php @@ -0,0 +1,147 @@ +getReflection(); + $annotations = new Files(); + + $this->tester->amInPath(sys_get_temp_dir()); + $this->tester->writeToFile('read-1.php', 'assertEquals($reflection, $annotations->read('read-1')); + + $this->tester->deleteFile('read-1.php'); + } + + /** @test */ + public function shouldReadFromFileDirectoryWithAnnotationsDir() + { + $reflection = $this->getReflection(); + $annotations = new Files(['annotationsDir' => codecept_output_dir()]); + + $this->tester->amInPath(codecept_output_dir()); + $this->tester->writeToFile('read-2.php', 'assertEquals($reflection, $annotations->read('read-2')); + + $this->tester->deleteFile('read-2.php'); + } + + /** @test */ + public function shouldWriteToTheFileDirectoryWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Files(); + + $this->assertTrue($annotations->write('write-1', $reflection)); + + $this->tester->amInPath(sys_get_temp_dir()); + $this->tester->seeFileFound('write-1.php'); + $this->tester->seeFileContentsEqual('tester->deleteFile('write-1.php'); + } + + /** @test */ + public function shouldWriteToTheFileDirectoryWithAnnotationsDir() + { + $reflection = $this->getReflection(); + $annotations = new Files(['annotationsDir' => codecept_output_dir()]); + + $this->assertTrue($annotations->write('write-2', $reflection)); + + $this->tester->amInPath(codecept_output_dir()); + $this->tester->seeFileFound('write-2.php'); + $this->tester->seeFileContentsEqual('tester->deleteFile('write-2.php'); + } + + /** @test */ + public function shouldFlushTheFileDirectoryStorageWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Files(); + + $this->tester->amInPath(sys_get_temp_dir()); + $this->tester->writeToFile('flush-1.php', 'tester->writeToFile('flush-2.php', 'assertTrue($annotations->flush()); + + $this->tester->dontSeeFileFound('flush-1.php'); + $this->tester->dontSeeFileFound('flush-2.php'); + } + + /** @test */ + public function shouldFlushTheFileDirectoryStorageWithAnnotationsDir() + { + $reflection = $this->getReflection(); + $annotations = new Files(['annotationsDir' => codecept_output_dir()]); + + $this->tester->amInPath(codecept_output_dir()); + $this->tester->writeToFile('flush-3.php', 'tester->writeToFile('flush-4.php', 'assertTrue($annotations->flush()); + + $this->tester->dontSeeFileFound('flush-3.php'); + $this->tester->dontSeeFileFound('flush-4.php'); + } + + /** @test */ + public function shouldReadAndWriteFromFileDirectoryWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Files(); + + $this->assertTrue($annotations->write('read-write-1', $reflection)); + $this->assertEquals($reflection, $annotations->read('read-write-1')); + + $this->tester->amInPath(sys_get_temp_dir()); + $this->tester->seeFileContentsEqual('tester->deleteFile('read-write-1.php'); + } + + /** @test */ + public function shouldReadAndWriteFromFileDirectoryWithAnnotationsDir() + { + $reflection = $this->getReflection(); + $annotations = new Files(['annotationsDir' => codecept_output_dir()]); + + $this->assertTrue($annotations->write('read-write-2', $reflection)); + $this->assertEquals($reflection, $annotations->read('read-write-2')); + + $this->tester->amInPath(codecept_output_dir()); + $this->tester->seeFileContentsEqual('tester->deleteFile('read-write-2.php'); + } + + + protected function getReflection() + { + return Reflection::__set_state([ + '_reflectionData' => [ + 'class' => [], + 'methods' => [], + 'properties' => [], + ] + ]); + } +} diff --git a/tests/unit/Annotations/Extended/Adapter/MemoryTest.php b/tests/unit/Annotations/Extended/Adapter/MemoryTest.php new file mode 100644 index 000000000..e247c72cb --- /dev/null +++ b/tests/unit/Annotations/Extended/Adapter/MemoryTest.php @@ -0,0 +1,133 @@ +getReflection(); + $annotations = new Memory(); + + $this->haveInMemory($annotations, 'read-1', $reflection); + $this->assertEquals($reflection, $annotations->read('read-1')); + } + + /** @test */ + public function shouldWriteToTheMemoryWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Memory(); + + $this->assertTrue($annotations->write('write-1', $reflection)); + $this->assertEquals($reflection, $this->grabValueFromMemory($annotations, 'write-1')); + } + + /** @test */ + public function shouldFlushTheMemoryStorageWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Memory(); + + $this->haveInMemory($annotations, 'flush-1', $reflection); + + $this->assertTrue($annotations->flush()); + $this->dontSeeInMemory($annotations, 'flush-1'); + } + + /** @test */ + public function shouldReadAndWriteFromMemoryWithoutAnyAdditionalParameter() + { + $reflection = $this->getReflection(); + $annotations = new Memory(); + + $this->assertTrue($annotations->write('read-write-1', $reflection)); + $this->assertEquals($reflection, $annotations->read('read-write-1')); + $this->assertEquals($reflection, $this->grabValueFromMemory($annotations, 'read-write-1')); + } + + /** + * @test + * @dataProvider providerKey + * @param mixed $key + * @param string $expected + */ + public function shouldGetValueFromMemoryByUsingPrefixedIdentifier($key, $expected) + { + $annotations = new Memory(); + $reflectedMethod = new ReflectionMethod(get_class($annotations), 'getPrefixedIdentifier'); + $reflectedMethod->setAccessible(true); + + $this->assertEquals($expected, $reflectedMethod->invoke($annotations, $key)); + } + + public function providerKey() + { + return [ + ['Key1', 'key1'], + ['KEY', 'key' ], + [1, '1' ], + ['____', '____'], + ]; + } + + protected function getReflection() + { + return Reflection::__set_state([ + '_reflectionData' => [ + 'class' => [], + 'methods' => [], + 'properties' => [], + ] + ]); + } + + protected function haveInMemory($object, $key, $value) + { + $reflectedProperty = new ReflectionProperty(get_class($object), 'data'); + $reflectedProperty->setAccessible(true); + + $data = $reflectedProperty->getValue($object); + $data[$key] = $value; + + $reflectedProperty->setValue($object, $data); + } + + protected function grabValueFromMemory($object, $key) + { + $reflectedProperty = new ReflectionProperty(get_class($object), 'data'); + $reflectedProperty->setAccessible(true); + + $data = $reflectedProperty->getValue($object); + + return $data[$key]; + } + + protected function dontSeeInMemory($object, $key, $value = false) + { + $reflectedProperty = new ReflectionProperty(get_class($object), 'data'); + $reflectedProperty->setAccessible(true); + + $data = $reflectedProperty->getValue($object); + + if ($value === false) { + $this->assertArrayNotHasKey($key, $data); + } else { + $this->assertSame($value, $data[$key]); + } + } +}