diff --git a/composer.json b/composer.json index af0f43a..643c3f7 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "squizlabs/php_codesniffer": "2.*" }, "scripts": { - "cs": "phpcs --standard=PSR2 -n src", + "cs": "phpcs --standard=PSR2 -n src tests", "cbf": "phpcbf --standard=PSR2 -n src", "api": "PATH=$HOME/bin:$PATH sami.phar --ansi update sami-config.php", "sami-install": "mkdir -p $HOME/bin && curl --output $HOME/bin/sami.phar http://get.sensiolabs.org/sami.phar && chmod +x $HOME/bin/sami.phar", diff --git a/src/APCuL1.php b/src/APCuL1.php index cd49267..45afe7c 100644 --- a/src/APCuL1.php +++ b/src/APCuL1.php @@ -7,24 +7,12 @@ class APCuL1 extends L1 /** @var string */ private $localKeyPrefix; - /** @var string */ - private $statusKeyHits; - - /** @var string */ - private $statusKeyMisses; - - /** @var string */ - private $statusKeyLastAppliedEventId; - - public function __construct($pool = null) + public function __construct($pool, StateL1Interface $state) { - parent::__construct($pool); + parent::__construct($pool, $state); // Using designated variables to speed up key generation during runtime. - $this->localKeyPrefix = 'lcache' . $this->pool . ':'; - $this->statusKeyHits = 'lcache_status:' . $this->pool . ':hits'; - $this->statusKeyMisses = 'lcache_status:' . $this->pool . ':misses'; - $this->statusKeyLastAppliedEventId = 'lcache_status:' . $this->pool . ':last_applied_event_id'; + $this->localKeyPrefix = 'lcache:' . $pool . ':'; } protected function getLocalKey($address) @@ -38,9 +26,10 @@ public function getKeyOverhead(Address $address) // decrementing the overhead by existing hits when an item is set. This // would make hits cheaper but writes more expensive. + $success = null; $apcu_key = $this->getLocalKey($address); $overhead = apcu_fetch($apcu_key . ':overhead', $success); - if ($success) { + if (true === $success) { return $overhead; } return 0; @@ -67,9 +56,10 @@ public function setWithExpiration($event_id, Address $address, $value, $created, // If not setting a negative cache entry, increment the key's overhead. if (!is_null($value)) { + $overhead_success = null; $apcu_key_overhead = $apcu_key . ':overhead'; apcu_inc($apcu_key_overhead, 1, $overhead_success); - if (!$overhead_success) { + if (false === $overhead_success) { // @codeCoverageIgnoreStart apcu_store($apcu_key_overhead, 1); // @codeCoverageIgnoreEnd @@ -81,9 +71,10 @@ public function setWithExpiration($event_id, Address $address, $value, $created, public function isNegativeCache(Address $address) { + $success = null; $apcu_key = $this->getLocalKey($address); $entry = apcu_fetch($apcu_key, $success); - return ($success && is_null($entry->value)); + return (true === $success && is_null($entry->value)); } public function getEntry(Address $address) @@ -92,16 +83,18 @@ public function getEntry(Address $address) $apcu_key_overhead = $apcu_key . ':overhead'; // Decrement the key's overhead. + $overhead_success = null; apcu_dec($apcu_key_overhead, 1, $overhead_success); - if (!$overhead_success) { + if (false === $overhead_success) { // @codeCoverageIgnoreStart apcu_store($apcu_key_overhead, -1); // @codeCoverageIgnoreEnd } + $success = null; $entry = apcu_fetch($apcu_key, $success); // Handle failed reads. - if (!$success) { + if (false === $success) { $this->recordMiss(); return null; } @@ -125,13 +118,11 @@ protected function getIterator($pattern, $format = APC_ITER_ALL) public function delete($event_id, Address $address) { - if ($address->isEntireCache()) { - // @TODO: Consider flushing only LCache L1 storage by using an iterator. - return apcu_clear_cache(); - } - if ($address->isEntireBin()) { - $prefix = $this->getLocalKey($address); - $pattern = '/^' . preg_quote($prefix) . '.*/'; + $localKey = $this->getLocalKey($address); + + if ($address->isEntireCache() || $address->isEntireBin()) { + $localKey = $this->getLocalKey($address); + $pattern = '/^' . preg_quote($localKey) . '.*/'; $matching = $this->getIterator($pattern, APC_ITER_KEY); assert(!is_null($matching), 'Iterator instantiation failed.'); foreach ($matching as $match) { @@ -139,66 +130,15 @@ public function delete($event_id, Address $address) // deleted in another process using the same APCu. apcu_delete($match['key']); } + if ($address->isEntireCache()) { + $this->state->clear(); + } return true; } - $apcu_key = $this->getLocalKey($address); - // Ignore failures of delete because the key may have been // deleted in another process using the same APCu. - apcu_delete($apcu_key); + apcu_delete($localKey); return true; } - - protected function recordHit() - { - apcu_inc($this->statusKeyHits, 1, $success); - if (!$success) { - // @TODO: Remove this fallback when we drop APCu 4.x support. - // @codeCoverageIgnoreStart - // Ignore coverage because (1) it's tested with other code and - // (2) APCu 5.x does not use it. - apcu_store($this->statusKeyHits, 1); - // @codeCoverageIgnoreEnd - } - } - - protected function recordMiss() - { - apcu_inc($this->statusKeyMisses, 1, $success); - if (!$success) { - // @TODO: Remove this fallback when we drop APCu 4.x support. - // @codeCoverageIgnoreStart - // Ignore coverage because (1) it's tested with other code and - // (2) APCu 5.x does not use it. - apcu_store($this->statusKeyMisses, 1); - // @codeCoverageIgnoreEnd - } - } - - public function getHits() - { - $value = apcu_fetch($this->statusKeyHits); - return $value ? $value : 0; - } - - public function getMisses() - { - $value = apcu_fetch($this->statusKeyMisses); - return $value ? $value : 0; - } - - public function getLastAppliedEventID() - { - $value = apcu_fetch($this->statusKeyLastAppliedEventId); - if ($value === false) { - $value = null; - } - return $value; - } - - public function setLastAppliedEventID($eid) - { - return apcu_store($this->statusKeyLastAppliedEventId, $eid); - } } diff --git a/src/Address.php b/src/Address.php index efe5d94..c57d16c 100644 --- a/src/Address.php +++ b/src/Address.php @@ -121,7 +121,7 @@ public function unserialize($serialized) } // @TODO: Remove check against false for PHP 7+ - if ($this->key === false || $this->key === '') { + if (false === $this->key || '' === $this->key) { $this->key = null; } } diff --git a/src/DatabaseL2.php b/src/DatabaseL2.php index c6473c6..d28b8c8 100644 --- a/src/DatabaseL2.php +++ b/src/DatabaseL2.php @@ -162,7 +162,7 @@ public function getEntry(Address $address) //$last_matching_entry = $sth->fetchObject('LCacheEntry'); $last_matching_entry = $sth->fetchObject(); - if ($last_matching_entry === false) { + if (false === $last_matching_entry) { $this->misses++; return null; } @@ -176,7 +176,7 @@ public function getEntry(Address $address) $unserialized_value = @unserialize($last_matching_entry->value); // If unserialization failed, raise an exception. - if ($unserialized_value === false && $last_matching_entry->value !== serialize(false)) { + if (false === $unserialized_value && serialize(false) !== $last_matching_entry->value) { throw new UnserializationException($address, $last_matching_entry->value); } @@ -364,7 +364,7 @@ public function applyEvents(L1 $l1) return null; } $last_event = $sth->fetchObject(); - if ($last_event === false) { + if (false === $last_event) { $l1->setLastAppliedEventID(0); } else { $l1->setLastAppliedEventID($last_event->event_id); @@ -391,7 +391,7 @@ public function applyEvents(L1 $l1) $l1->delete($event->event_id, $address); } else { $unserialized_value = @unserialize($event->value); - if ($unserialized_value === false && $event->value !== serialize(false)) { + if (false === $unserialized_value && serialize(false) !== $event->value) { // Delete the L1 entry, if any, when we fail to unserialize. $l1->delete($event->event_id, $address); } else { diff --git a/src/L1.php b/src/L1.php index e856b19..3029fe6 100644 --- a/src/L1.php +++ b/src/L1.php @@ -6,25 +6,33 @@ abstract class L1 extends LX { protected $pool; - public function __construct($pool = null) + /** @var StateL1Interface */ + protected $state; + + /** + * Constructor for all the L1 implementations. + * + * @param string $pool + * Pool ID to group the cache data in. + * @param \LCache\StateL1Interface $state + * State manager class. Used to collect hit/miss statistics as well as + * the ID of the last cache mutation event. + */ + public function __construct($pool, StateL1Interface $state) { - if (!is_null($pool)) { - $this->pool = $pool; - } elseif (isset($_SERVER['SERVER_ADDR']) && isset($_SERVER['SERVER_PORT'])) { - $this->pool = $_SERVER['SERVER_ADDR'] . '-' . $_SERVER['SERVER_PORT']; - } else { - $this->pool = $this->generateUniqueID(); - } + $this->pool = $pool; + $this->state = $state; } - protected function generateUniqueID() + public function getLastAppliedEventID() { - // @TODO: Replace with a persistent but machine-local (and unique) method. - return uniqid('', true) . '-' . mt_rand(); + return $this->state->getLastAppliedEventID(); } - abstract public function getLastAppliedEventID(); - abstract public function setLastAppliedEventID($event_id); + public function setLastAppliedEventID($event_id) + { + return $this->state->setLastAppliedEventID($event_id); + } public function getPool() { @@ -40,4 +48,24 @@ abstract public function isNegativeCache(Address $address); abstract public function getKeyOverhead(Address $address); abstract public function setWithExpiration($event_id, Address $address, $value, $created, $expiration = null); abstract public function delete($event_id, Address $address); + + public function getHits() + { + return $this->state->getHits(); + } + + public function getMisses() + { + return $this->state->getMisses(); + } + + protected function recordHit() + { + $this->state->recordHit(); + } + + protected function recordMiss() + { + $this->state->recordMiss(); + } } diff --git a/src/L1CacheFactory.php b/src/L1CacheFactory.php new file mode 100644 index 0000000..251da20 --- /dev/null +++ b/src/L1CacheFactory.php @@ -0,0 +1,129 @@ +getPool($customPool); + $driver = mb_convert_case($driverName, MB_CASE_LOWER); + + $factoryName = 'create' . $driver; + if (!method_exists($this, $factoryName)) { + // TODO: Decide on better fallback (if needed). + $factoryName = 'createStatic'; + } + + $l1CacheInstance = call_user_func([$this, $factoryName], $pool); + return $l1CacheInstance; + } + + /** + * Factory method for the L1 APCu driver. + * + * @param string $pool + * @return \LCache\APCuL1 + */ + protected function createAPCu($pool) + { + return new APCuL1($pool, new StateL1APCu($pool)); + } + + /** + * Factory method for the L1 NULL driver. + * + * @param string $pool + * @return \LCache\NullL1 + */ + protected function createNull($pool) + { + return new NullL1($pool, new StateL1Static()); + } + + /** + * Factory method for the L1 static driver. + * + * @param string $pool + * @return \LCache\StaticL1 + */ + protected function createStatic($pool) + { + return new StaticL1($pool, new StateL1Static()); + } + + /** + * Factory method for the L1 SQLite driver. + * + * @param string $pool + * @return \LCache\SQLiteL1 + */ + protected function createSQLite($pool) + { + $hasApcu = function_exists('apcu_fetch'); + // TODO: Maybe implement StateL1SQLite class instead of NULL one. + $state = $hasApcu ? new StateL1APCu("sqlite-$pool") : new StateL1Static(); + $cache = new SQLiteL1($pool, $state); + return $cache; + } + + /** + * Pool generator utility. + * + * @param string $pool + * Custom pool to use. Defaults to NULL. If the default is uesed, it will + * atempt to generate a pool value for use. + * + * @return string + * Pool value based on input and/or environment variables / state. + */ + protected function getPool($pool = null) + { + if (!is_null($pool)) { + $result = (string) $pool; + } elseif (isset($_SERVER['SERVER_ADDR']) && isset($_SERVER['SERVER_PORT'])) { + $result = $_SERVER['SERVER_ADDR'] . '-' . $_SERVER['SERVER_PORT']; + } else { + $result = $this->generateUniqueID(); + } + return $result; + } + + /** + * Pool generation utility. + * + * @see L1CacheFactory::getPool() + * + * @return string + */ + protected function generateUniqueID() + { + // @TODO: Replace with a persistent but machine-local (and unique) method. + return uniqid('', true) . '-' . mt_rand(); + } +} diff --git a/src/NullL1.php b/src/NullL1.php index 922d682..74688f7 100644 --- a/src/NullL1.php +++ b/src/NullL1.php @@ -6,14 +6,14 @@ class NullL1 extends StaticL1 { public function setWithExpiration($event_id, Address $address, $value, $created, $expiration = null) { - // Store nothing; always succeed. + // Store nothing and always succeed. return true; } public function getLastAppliedEventID() { - // Because we store nothing locally, behave as if all events - // are applied. + // Because we store nothing locally. + // Behave as if all events are applied. return PHP_INT_MAX; } } diff --git a/src/SQLiteL1.php b/src/SQLiteL1.php index 0ce4ee0..45a7867 100644 --- a/src/SQLiteL1.php +++ b/src/SQLiteL1.php @@ -7,15 +7,6 @@ class SQLiteL1 extends L1 /** @var \PDO */ private $dbh; - /** @var string */ - private $statusKeyHits; - - /** @var string */ - private $statusKeyMisses; - - /** @var string */ - private $statusKeyLastAppliedEventId; - protected static function tableExists(\PDO $dbh, $table_name) { try { @@ -35,14 +26,27 @@ protected static function tableExists(\PDO $dbh, $table_name) protected static function initializeSchema(\PDO $dbh) { if (!self::tableExists($dbh, 'entries')) { - $dbh->exec('CREATE TABLE IF NOT EXISTS entries("address" TEXT PRIMARY KEY, "value" BLOB, "expiration" INTEGER, "created" INTEGER, "event_id" INTEGER NOT NULL DEFAULT 0, "reads" INTEGER NOT NULL DEFAULT 0, "writes" INTEGER NOT NULL DEFAULT 0)'); - $dbh->exec('CREATE INDEX IF NOT EXISTS expiration ON entries ("expiration")'); + foreach (self::schemaStatements() as $query) { + $dbh->exec($query); + } } } + protected static function schemaStatements() + { + return [ + // Table creation. + 'CREATE TABLE IF NOT EXISTS entries("address" TEXT PRIMARY KEY, "value" BLOB, "expiration" INTEGER, "created" INTEGER, "event_id" INTEGER NOT NULL DEFAULT 0, "reads" INTEGER NOT NULL DEFAULT 0, "writes" INTEGER NOT NULL DEFAULT 0)', + + // Index creation. + 'CREATE INDEX IF NOT EXISTS expiration ON entries ("expiration")', + ]; + } + protected static function getDatabaseHandle($pool) { - $path = join(DIRECTORY_SEPARATOR, array(sys_get_temp_dir(), 'lcache-' . $pool)); + $schemaId = substr(hash('sha512', implode(';', self::schemaStatements())), 0, 20); + $path = join(DIRECTORY_SEPARATOR, array(sys_get_temp_dir(), "lcache-$pool-$schemaId")); $dbh = new \PDO('sqlite:' . $path . '.sqlite3'); $dbh->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $dbh->exec('PRAGMA synchronous = OFF'); @@ -51,14 +55,10 @@ protected static function getDatabaseHandle($pool) return $dbh; } - public function __construct($pool = null) + public function __construct($pool, StateL1Interface $state) { - parent::__construct($pool); - $this->dbh = self::getDatabaseHandle($this->pool); - - $this->statusKeyHits = 'lcache_status:' . $this->pool . ':hits'; - $this->statusKeyMisses = 'lcache_status:' . $this->pool . ':misses'; - $this->statusKeyLastAppliedEventId = 'lcache_status:' . $this->pool . ':last_applied_event_id'; + parent::__construct($pool, $state); + $this->dbh = self::getDatabaseHandle($pool); } protected function pruneExpiredEntries() @@ -95,7 +95,7 @@ public function getKeyOverhead(Address $address) $sth->execute(); $result = $sth->fetchObject(); - if ($result === false) { + if (false === $result) { return 0; } @@ -188,7 +188,7 @@ public function getEntry(Address $address) // do this to simultaneously track useful overhead data but not unnecessarily // record reads after they massively outweigh writes for an address. // @TODO: Make this adapt to overhead thresholds. - if ($entry === false || $entry->reads < 10 * $entry->writes || $entry->reads < 10) { + if (false === $entry || $entry->reads < 10 * $entry->writes || $entry->reads < 10) { $sth = $this->dbh->prepare('UPDATE entries SET "reads" = "reads" + 1 WHERE "address" = :address'); $sth->bindValue(':address', $address->serialize(), \PDO::PARAM_STR); $sth->execute(); @@ -201,7 +201,7 @@ public function getEntry(Address $address) } } - if ($entry === false) { + if (false === $entry) { $this->recordMiss(); return null; } @@ -222,8 +222,8 @@ public function delete($event_id, Address $address) $sth = $this->dbh->prepare('DELETE FROM entries WHERE "address" LIKE :pattern'); $sth->bindValue('pattern', $pattern, \PDO::PARAM_STR); $sth->execute(); - apcu_store($this->statusKeyHits, 0); - apcu_store($this->statusKeyMisses, 0); + + $this->state->clear(); return true; } @@ -233,64 +233,4 @@ public function delete($event_id, Address $address) $sth->execute(); return true; } - - // @TODO: Update hit/miss functions to either record nothing or fall back - // to SQLite storage if APCu is not available. - - protected function recordHit() - { - $success = false; // Make Scrutinizer happy. - apcu_inc($this->statusKeyHits, 1, $success); - if (!$success) { - // @TODO: Remove this fallback when we drop APCu 4.x support. - // @codeCoverageIgnoreStart - // Ignore coverage because (1) it's tested with other code and - // (2) APCu 5.x does not use it. - apcu_store($this->statusKeyHits, 1); - // @codeCoverageIgnoreEnd - } - } - - protected function recordMiss() - { - $success = false; // Make Scrutinizer happy. - apcu_inc($this->statusKeyMisses, 1, $success); - if (!$success) { - // @TODO: Remove this fallback when we drop APCu 4.x support. - // @codeCoverageIgnoreStart - // Ignore coverage because (1) it's tested with other code and - // (2) APCu 5.x does not use it. - apcu_store($this->statusKeyMisses, 1); - // @codeCoverageIgnoreEnd - } - } - - public function getHits() - { - $value = apcu_fetch($this->statusKeyHits); - return $value ? $value : 0; - } - - public function getMisses() - { - $value = apcu_fetch($this->statusKeyMisses); - return $value ? $value : 0; - } - - // @TODO: Update event ID tracking to either record fall back - // to SQLite storage if APCu is not available. - - public function getLastAppliedEventID() - { - $value = apcu_fetch($this->statusKeyLastAppliedEventId); - if ($value === false) { - $value = null; - } - return $value; - } - - public function setLastAppliedEventID($eid) - { - return apcu_store($this->statusKeyLastAppliedEventId, $eid); - } } diff --git a/src/StateL1APCu.php b/src/StateL1APCu.php new file mode 100644 index 0000000..f2da36f --- /dev/null +++ b/src/StateL1APCu.php @@ -0,0 +1,131 @@ +pool = $pool; + + // Using designated variables to speed up key generation during runtime. + $this->statusKeyHits = 'lcache_status:' . $this->pool . ':hits'; + $this->statusKeyMisses = 'lcache_status:' . $this->pool . ':misses'; + $this->statusKeyLastAppliedEventId = 'lcache_status:' . $this->pool . ':last_applied_event_id'; + } + + /** + * {inheritdoc} + */ + public function recordHit() + { + return $this->recordEvent($this->statusKeyHits); + } + + /** + * {inheritdoc} + */ + public function recordMiss() + { + return $this->recordEvent($this->statusKeyMisses); + } + + /** + * Utility method to reduce code duplication. + * + * @param string $key + * Key to store the evet counters in. + * + * @return bool + * True on success, false otherwise. + */ + private function recordEvent($key) + { + $success = null; + apcu_inc($key, 1, $success); + if ($success !== null && !$success) { + // @TODO: Remove this fallback when we drop APCu 4.x support. + // @codeCoverageIgnoreStart + // Ignore coverage because (1) it's tested with other code and + // (2) APCu 5.x does not use it. + $success = apcu_store($key, 1); + // @codeCoverageIgnoreEnd + } + return $success; + } + + /** + * {inheritdoc} + */ + public function getHits() + { + $value = apcu_fetch($this->statusKeyHits); + return $value ? $value : 0; + } + + /** + * {inheritdoc} + */ + public function getMisses() + { + $value = apcu_fetch($this->statusKeyMisses); + return $value ? $value : 0; + } + + /** + * {inheritdoc} + */ + public function getLastAppliedEventID() + { + $value = apcu_fetch($this->statusKeyLastAppliedEventId); + if (false === $value) { + $value = null; + } + return $value; + } + + /** + * {inheritdoc} + */ + public function setLastAppliedEventID($eventId) + { + return apcu_store($this->statusKeyLastAppliedEventId, $eventId); + } + + /** + * {inheritdoc} + */ + public function clear() + { + apcu_store($this->statusKeyHits, 0); + apcu_store($this->statusKeyMisses, 0); + } +} diff --git a/src/StateL1Interface.php b/src/StateL1Interface.php new file mode 100644 index 0000000..dd2dd6d --- /dev/null +++ b/src/StateL1Interface.php @@ -0,0 +1,76 @@ +last_applied_event_id = null; + $this->clear(); + } + + /** + * {inheritdoc} + */ + public function recordHit() + { + $this->hits++; + } + + /** + * {inheritdoc} + */ + public function recordMiss() + { + $this->misses++; + } + + /** + * {inheritdoc} + */ + public function getHits() + { + return $this->hits; + } + + /** + * {inheritdoc} + */ + public function getMisses() + { + return $this->misses; + } + + /** + * {inheritdoc} + */ + public function getLastAppliedEventID() + { + return $this->last_applied_event_id; + } + + /** + * {inheritdoc} + */ + public function setLastAppliedEventID($eventId) + { + $this->last_applied_event_id = $eventId; + return true; + } + + /** + * {inheritdoc} + */ + public function clear() + { + $this->hits = $this->misses = 0; + } +} diff --git a/src/StaticL1.php b/src/StaticL1.php index a7a2470..c340608 100644 --- a/src/StaticL1.php +++ b/src/StaticL1.php @@ -4,21 +4,23 @@ class StaticL1 extends L1 { - protected $hits; - protected $misses; + private static $cacheData = []; + protected $key_overhead; + + /** @var array Reference to the data array for the instance data pool. */ protected $storage; - protected $last_applied_event_id; - public function __construct($pool = null) + public function __construct($pool, StateL1Interface $state) { - parent::__construct($pool); + parent::__construct($pool, $state); - $this->hits = 0; - $this->misses = 0; $this->key_overhead = []; - $this->storage = array(); - $this->last_applied_event_id = null; + + if (!isset(self::$cacheData[$this->pool])) { + self::$cacheData[$this->pool] = []; + } + $this->storage = &self::$cacheData[$this->pool]; } public function getKeyOverhead(Address $address) @@ -70,17 +72,18 @@ public function getEntry(Address $address) } if (!array_key_exists($local_key, $this->storage)) { - $this->misses++; + $this->recordMiss(); return null; } $entry = $this->storage[$local_key]; if (!is_null($entry->expiration) && $entry->expiration < $_SERVER['REQUEST_TIME']) { unset($this->storage[$local_key]); - $this->misses++; + $this->recordMiss(); return null; } - $this->hits++; + $this->recordHit(); + return $entry; } @@ -89,8 +92,7 @@ public function delete($event_id, Address $address) $local_key = $address->serialize(); if ($address->isEntireCache()) { $this->storage = array(); - $this->hits = 0; - $this->misses = 0; + $this->state->clear(); return true; } elseif ($address->isEntireBin()) { foreach ($this->storage as $index => $value) { @@ -105,25 +107,4 @@ public function delete($event_id, Address $address) unset($this->storage[$local_key]); return true; } - - public function getHits() - { - return $this->hits; - } - - public function getMisses() - { - return $this->misses; - } - - public function getLastAppliedEventID() - { - return $this->last_applied_event_id; - } - - public function setLastAppliedEventID($eid) - { - $this->last_applied_event_id = $eid; - return true; - } } diff --git a/src/StaticL2.php b/src/StaticL2.php index 845cf51..ffb7566 100644 --- a/src/StaticL2.php +++ b/src/StaticL2.php @@ -47,16 +47,17 @@ public function collectGarbage($item_limit = null) // Returns an LCache\Entry public function getEntry(Address $address) { + $events = array_filter($this->events, function (Entry $entry) use ($address) { + return $entry->getAddress()->isMatch($address); + }); $last_matching_entry = null; - foreach ($this->events as $event_id => $entry) { - if ($entry->getAddress()->isMatch($address)) { - if ($entry->getAddress()->isEntireCache() || $entry->getAddress()->isEntireBin()) { - $last_matching_entry = null; - } elseif (!is_null($entry->expiration) && $entry->expiration < $_SERVER['REQUEST_TIME']) { - $last_matching_entry = null; - } else { - $last_matching_entry = clone $entry; - } + foreach ($events as $entry) { + if ($entry->getAddress()->isEntireCache() || $entry->getAddress()->isEntireBin()) { + $last_matching_entry = null; + } elseif (!is_null($entry->expiration) && $entry->expiration < $_SERVER['REQUEST_TIME']) { + $last_matching_entry = null; + } else { + $last_matching_entry = clone $entry; } } // Last event was a deletion, so miss. @@ -68,7 +69,7 @@ public function getEntry(Address $address) $unserialized_value = @unserialize($last_matching_entry->value); // If unserialization failed, miss. - if ($unserialized_value === false && $last_matching_entry->value !== serialize(false)) { + if (false === $unserialized_value && serialize(false) !== $last_matching_entry->value) { throw new UnserializationException($address, $last_matching_entry->value); } @@ -151,20 +152,22 @@ public function applyEvents(L1 $l1) $applied = 0; foreach ($this->events as $event_id => $event) { // Skip events that are too old or were created by the local L1. - if ($event_id > $last_applied_event_id && $event->pool !== $l1->getPool()) { - if (is_null($event->value)) { + if ($event_id <= $last_applied_event_id || $event->pool === $l1->getPool()) { + continue; + } + + if (is_null($event->value)) { + $l1->delete($event->event_id, $event->getAddress()); + } else { + $unserialized_value = @unserialize($event->value); + if (false === $unserialized_value && serialize(false) !== $event->value) { + // Delete the L1 entry, if any, when we fail to unserialize. $l1->delete($event->event_id, $event->getAddress()); } else { - $unserialized_value = @unserialize($event->value); - if ($unserialized_value === false && $event->value !== serialize(false)) { - // Delete the L1 entry, if any, when we fail to unserialize. - $l1->delete($event->event_id, $event->getAddress()); - } else { - $l1->setWithExpiration($event->event_id, $event->getAddress(), $unserialized_value, $event->created, $event->expiration); - } + $l1->setWithExpiration($event->event_id, $event->getAddress(), $unserialized_value, $event->created, $event->expiration); } - $applied++; } + $applied++; } // Just in case there were skipped events, set the high water mark. diff --git a/tests/L1/APCuTest.php b/tests/L1/APCuTest.php new file mode 100644 index 0000000..e872be3 --- /dev/null +++ b/tests/L1/APCuTest.php @@ -0,0 +1,39 @@ +markTestSkipped('The APCu extension is not installed, enabled (for the CLI), or functional.'); + } + } + + /** + * {@inheritDoc} + */ + protected function driverName() + { + return 'apcu'; + } +} diff --git a/tests/L1/NullTest.php b/tests/L1/NullTest.php new file mode 100644 index 0000000..ffaa22f --- /dev/null +++ b/tests/L1/NullTest.php @@ -0,0 +1,63 @@ +createL1(); + $myaddr = new Address('mybin', 'mykey'); + + $cache->set(1, $myaddr, 'myvalue'); + + $this->assertNull($cache->get($myaddr)); + $this->assertEquals(0, $cache->getHits()); + $this->assertEquals(1, $cache->getMisses()); + } + + public function testStateStorage() + { + $lastEventId = $this->createL1()->getLastAppliedEventID(); + $this->assertEquals(PHP_INT_MAX, $lastEventId); + } + + public function testSetGetDelete() + { + // Not relevant for NullL1 class. + } + + public function testPreventRollback() + { + // Not relevant for NullL1 class. + } + + public function testExists() + { + // Not relevant for NullL1 class. + } + + public function testPoolSharing() + { + // Not relevant for NullL1 class. + } +} diff --git a/tests/L1/SQLiteTest.php b/tests/L1/SQLiteTest.php new file mode 100644 index 0000000..e905683 --- /dev/null +++ b/tests/L1/SQLiteTest.php @@ -0,0 +1,24 @@ +create($this->driverName(), $pool); + } + + public function testSetGetDelete() + { + $event_id = 1; + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + + // Validate emptyness. + $this->assertEquals(0, $l1->getHits()); + $this->assertEquals(0, $l1->getMisses()); + + // Try to get an entry from an empty L1. + $entry = $l1->get($myaddr); + $this->assertNull($entry); + $this->assertEquals(0, $l1->getHits()); + $this->assertEquals(1, $l1->getMisses()); + + // Set and get an entry. + $l1->set($event_id++, $myaddr, 'myvalue'); + $entry = $l1->get($myaddr); + $this->assertEquals('myvalue', $entry); + $this->assertEquals(1, $l1->getHits()); + $this->assertEquals(1, $l1->getMisses()); + + // Delete the entry and try to get it again. + $l1->delete($event_id++, $myaddr); + $entry = $l1->get($myaddr); + $this->assertNull($entry); + $this->assertEquals(1, $l1->getHits()); + $this->assertEquals(2, $l1->getMisses()); + + // Clear everything and try to read. + $l1->delete($event_id++, new Address()); + $entry = $l1->get($myaddr); + $this->assertNull($entry); + $this->assertEquals(0, $l1->getHits()); + $this->assertEquals(1, $l1->getMisses()); + + // This is a no-op for most L1 implementations, but it should not + // return false, regardless. + $this->assertTrue(false !== $l1->collectGarbage()); + + // Test complex values that need serialization. + $myarray = [1, 2, 3]; + $l1->set($event_id++, $myaddr, $myarray); + $entry = $l1->get($myaddr); + $this->assertEquals($myarray, $entry); + + // Test creation tracking. + $l1->setWithExpiration($event_id++, $myaddr, 'myvalue', 42); + $entry = $l1->getEntry($myaddr); + $this->assertEquals(42, $entry->created); + } + + public function testPreventRollback() + { + $l1 = $this->createL1(); + + $myaddr = new Address('mybin', 'mykey'); + $current_event_id = $l1->getLastAppliedEventID(); + if (is_null($current_event_id)) { + $current_event_id = 1; + } + // Write something in the cache. + $l1->set($current_event_id++, $myaddr, 'myvalue'); + $this->assertEquals('myvalue', $l1->get($myaddr)); + + // Atempt to write somthing with older event id. + $l1->set($current_event_id - 2, $myaddr, 'myoldvalue'); + $this->assertEquals('myvalue', $l1->get($myaddr)); + } + + public function testFullDelete() + { + $event_id = 1; + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + + // Set an entry and clear the storage. + $l1->set($event_id++, $myaddr, 'myvalue'); + $l1->delete($event_id++, new Address()); + $this->assertEquals(null, $l1->get($myaddr)); + $this->assertEquals(0, $l1->getHits()); + $this->assertEquals(1, $l1->getMisses()); + } + + public function testExpiration() + { + $event_id = 1; + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + + // Set and get an entry. + $l1->set($event_id++, $myaddr, 'myvalue', -1); + $this->assertNull($l1->get($myaddr)); + $this->assertEquals(0, $l1->getHits()); + $this->assertEquals(1, $l1->getMisses()); + } + + public function testExists() + { + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + + $l1->set(1, $myaddr, 'myvalue'); + $this->assertTrue($l1->exists($myaddr)); + $l1->delete(2, $myaddr); + $this->assertFalse($l1->exists($myaddr)); + } + + public function testPoolIDs() + { + // Test unique ID generation. + $this->assertNotNull($this->createL1()->getPool()); + + // Test host-based generation. + $_SERVER['SERVER_ADDR'] = 'localhost'; + $_SERVER['SERVER_PORT'] = '80'; + $this->assertEquals('localhost-80', $this->createL1()->getPool()); + } + + public function testPoolSharing() + { + $value = 'myvalue'; + $myaddr = new Address('mybin', 'mykey'); + $poolName = uniqid('', true) . '-' . mt_rand(); + + // Initialize a value in cache. + $this->createL1($poolName)->set(1, $myaddr, $value); + + // Opening a second instance of the same pool should work. + // Reading from the second handle should show the same value. + $this->assertEquals($value, $this->createL1($poolName)->get($myaddr)); + } + + public function testHitMiss() + { + $event_id = 1; + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + list($hits, $misses) = [$l1->getHits(), $l1->getMisses()]; + + $l1->get($myaddr); + $this->assertEquals($misses + 1, $l1->getMisses()); + + $l1->set($event_id++, $myaddr, 'myvalue'); + $l1->get($myaddr); + $this->assertEquals($hits + 1, $l1->getHits()); + } + + public function testStateStorage() + { + $event_id = 1; + $l1 = $this->createL1(); + $myaddr = new Address('mybin', 'mykey'); + + $this->assertEquals(0, $l1->getKeyOverhead($myaddr)); + $l1->set($event_id++, $myaddr, 'myvalue'); + $this->assertEquals(1, $l1->getKeyOverhead($myaddr)); + $l1->get($myaddr); + $this->assertEquals(0, $l1->getKeyOverhead($myaddr)); + $l1->set($event_id++, $myaddr, 'myvalue2'); + $this->assertEquals(1, $l1->getKeyOverhead($myaddr)); + + // An unknown get should create negative overhead, generally + // in anticipation of a set. + $myaddr2 = new Address('mybin', 'mykey2'); + $l1->get($myaddr2); + $this->assertEquals(-1, $l1->getKeyOverhead($myaddr2)); + } +} diff --git a/tests/LCacheTest.php b/tests/LCacheTest.php index d2766bd..ce4812d 100644 --- a/tests/LCacheTest.php +++ b/tests/LCacheTest.php @@ -8,6 +8,19 @@ class LCacheTest extends \PHPUnit_Extensions_Database_TestCase { protected $dbh = null; + private $_factory; + /** + * + * @return \LCache\L1CacheFactory + */ + protected function l1Factory() + { + if ($this->_factory === null) { + $this->_factory = new L1CacheFactory(); + } + return $this->_factory; + } + /** * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection */ @@ -30,160 +43,12 @@ protected function createSchema($prefix = '') $this->dbh->exec('CREATE INDEX ' . $prefix . 'rewritten_entry ON ' . $prefix . 'lcache_tags ("event_id")'); } - public function testNullL1() - { - $event_id = 1; - $cache = new NullL1(); - $myaddr = new Address('mybin', 'mykey'); - $cache->set($event_id++, $myaddr, 'myvalue'); - $entry = $cache->get($myaddr); - $this->assertNull($entry); - $this->assertEquals(0, $cache->getHits()); - $this->assertEquals(1, $cache->getMisses()); - - // Because this cache stores nothing it should be perpetually - // up-to-date. - $this->assertEquals(PHP_INT_MAX, $cache->getLastAppliedEventID()); - } - - protected function performSetGetDeleteTest($l1) - { - $event_id = 1; - - $myaddr = new Address('mybin', 'mykey'); - - $this->assertEquals(0, $l1->getHits()); - $this->assertEquals(0, $l1->getMisses()); - - // Try to get an entry from an empty L1. - $entry = $l1->get($myaddr); - $this->assertNull($entry); - $this->assertEquals(0, $l1->getHits()); - $this->assertEquals(1, $l1->getMisses()); - - // Set and get an entry. - $l1->set($event_id++, $myaddr, 'myvalue'); - $entry = $l1->get($myaddr); - $this->assertEquals('myvalue', $entry); - $this->assertEquals(1, $l1->getHits()); - $this->assertEquals(1, $l1->getMisses()); - - // Delete the entry and try to get it again. - $l1->delete($event_id++, $myaddr); - $entry = $l1->get($myaddr); - $this->assertNull($entry); - $this->assertEquals(1, $l1->getHits()); - $this->assertEquals(2, $l1->getMisses()); - - // Clear everything and try to read. - $l1->delete($event_id++, new Address()); - $entry = $l1->get($myaddr); - $this->assertNull($entry); - $this->assertEquals(0, $l1->getHits()); - $this->assertEquals(1, $l1->getMisses()); - - // This is a no-op for most L1 implementations, but it should not - // return false, regardless. - $this->assertTrue(false !== $l1->collectGarbage()); - - // Test complex values that need serialization. - $myarray = [1, 2, 3]; - $l1->set($event_id++, $myaddr, $myarray); - $entry = $l1->get($myaddr); - $this->assertEquals($myarray, $entry); - - // Test creation tracking. - $l1->setWithExpiration($event_id++, $myaddr, 'myvalue', 42); - $entry = $l1->getEntry($myaddr); - $this->assertEquals(42, $entry->created); - } - - public function testStaticL1SetGetDelete() - { - $l1 = new StaticL1(); - $this->performSetGetDeleteTest($l1); - } - - public function testSQLiteL1SetGetDelete() - { - $l1 = new SQLiteL1(); - $this->performSetGetDeleteTest($l1); - } - - public function testAPCuL1SetGetDelete() - { - $l1 = new APCuL1('setGetDelete'); - $this->performSetGetDeleteTest($l1); - } - - public function testStaticL1Antirollback() - { - $l1 = new StaticL1(); - $this->performL1AntirollbackTest($l1); - } - - public function performL1FullDelete($cache) - { - $event_id = 1; - - $myaddr = new Address('mybin', 'mykey'); - - // Set an entry and clear the storage. - $cache->set($event_id++, $myaddr, 'myvalue'); - $cache->delete($event_id++, new Address()); - $entry = $cache->get($myaddr); - $this->assertEquals(null, $entry); - $this->assertEquals(0, $cache->getHits()); - $this->assertEquals(1, $cache->getMisses()); - } - - public function testSQLiteL1FullDelete() - { - $l1 = new SQLiteL1(); - $this->performL1FullDelete($l1); - } - - public function testAPCuL1FullDelete() - { - $l1 = new APCuL1('setGetDelete'); - $this->performL1FullDelete($l1); - } - - public function testStaticL1FullDelete() - { - $l1 = new StaticL1(); - $this->performL1FullDelete($l1); - } - - public function performL1Expiration($l1) - { - $event_id = 1; - - $myaddr = new Address('mybin', 'mykey'); - - // Set and get an entry. - $l1->set($event_id++, $myaddr, 'myvalue', -1); - $this->assertNull($l1->get($myaddr)); - $this->assertEquals(0, $l1->getHits()); - $this->assertEquals(1, $l1->getMisses()); - } - - public function testSQLiteL1Expiration() - { - $l1 = new SQLiteL1(); - $this->performL1Expiration($l1); - } - - public function testAPCuL1Expiration() - { - $l1 = new APCuL1('expiration'); - $this->performL1Expiration($l1); - } - - public function testStaticL1Expiration() + public function testL1Factory() { - $l1 = new StaticL1(); - $this->performL1Expiration($l1); + // TODO: Move to L1CacheTest. + $staticL1 = $this->l1Factory()->create('static'); + $invalidL1 = $this->l1Factory()->create('invalid_cache_driver'); + $this->assertEquals(get_class($staticL1), get_class($invalidL1)); } public function testClearStaticL2() @@ -217,7 +82,7 @@ public function testStaticL2Reread() public function testNewPoolSynchronization() { $central = new StaticL2(); - $pool1 = new Integrated(new StaticL1(), $central); + $pool1 = new Integrated($this->l1Factory()->create('static'), $central); $myaddr = new Address('mybin', 'mykey'); @@ -237,7 +102,7 @@ public function testNewPoolSynchronization() // Add a new pool. Sync should return NULL applied changes but should // bump the last applied event ID. - $pool2 = new Integrated(new StaticL1(), $central); + $pool2 = new Integrated($this->l1Factory()->create('static'), $central); $applied = $pool2->synchronize(); $this->assertNull($applied); $this->assertEquals($pool1->getLastAppliedEventID(), $pool2->getLastAppliedEventID()); @@ -245,6 +110,7 @@ public function testNewPoolSynchronization() protected function performTombstoneTest($l1) { + // This test is not for L1 - this tests integratino logick. $central = new Integrated($l1, new StaticL2()); $dne = new Address('mypool', 'mykey-dne'); @@ -269,19 +135,19 @@ protected function performTombstoneTest($l1) public function testStaticL1Tombstone() { - $l1 = new StaticL1(); + $l1 = $this->l1Factory()->create('static'); $this->performTombstoneTest($l1); } public function testAPCuL1Tombstone() { - $l1 = new APCuL1('testAPCuL1Tombstone'); + $l1 = $this->l1Factory()->create('apcu', 'testAPCuL1Tombstone'); $this->performTombstoneTest($l1); } public function testSQLiteL1Tombstone() { - $l1 = new SQLiteL1(); + $l1 = $this->l1Factory()->create('sqlite'); $this->performTombstoneTest($l1); } @@ -446,13 +312,13 @@ protected function performTaggedSynchronizationTest($central, $first_l1, $second public function testSynchronizationStatic() { $central = new StaticL2(); - $this->performSynchronizationTest($central, new StaticL1(), new StaticL1()); + $this->performSynchronizationTest($central, $this->l1Factory()->create('static'), $this->l1Factory()->create('static')); } public function testTaggedSynchronizationStatic() { $central = new StaticL2(); - $this->performTaggedSynchronizationTest($central, new StaticL1(), new StaticL1()); + $this->performTaggedSynchronizationTest($central, $this->l1Factory()->create('static'), $this->l1Factory()->create('static')); } public function testSynchronizationAPCu() @@ -472,11 +338,23 @@ public function testSynchronizationAPCu() if ($run_test) { $central = new StaticL2(); - $this->performSynchronizationTest($central, new APCuL1('testSynchronizationAPCu1'), new APCuL1('testSynchronizationAPCu2')); + $this->performSynchronizationTest( + $central, + $this->l1Factory()->create('apcu', 'testSynchronizationAPCu1'), + $this->l1Factory()->create('apcu', 'testSynchronizationAPCu2') + ); // Because of how APCu only offers full cache clears, we test against a static cache for the other L1. - $this->performClearSynchronizationTest($central, new APCuL1('testSynchronizationAPCu1b'), new StaticL1()); - $this->performClearSynchronizationTest($central, new StaticL1(), new APCuL1('testSynchronizationAPCu1c')); + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('apcu', 'testSynchronizationAPCu1b'), + $this->l1Factory()->create('static') + ); + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('static'), + $this->l1Factory()->create('apcu', 'testSynchronizationAPCu1c') + ); } else { $this->markTestSkipped('The APCu extension is not installed, enabled (for the CLI), or functional.'); } @@ -485,33 +363,61 @@ public function testSynchronizationAPCu() public function testSynchronizationSQLiteL1() { $central = new StaticL2(); - $this->performSynchronizationTest($central, new SQLiteL1(), new SQLiteL1()); - - $this->performClearSynchronizationTest($central, new SQLiteL1(), new StaticL1()); - $this->performClearSynchronizationTest($central, new StaticL1(), new SQLiteL1()); - $this->performClearSynchronizationTest($central, new SQLiteL1(), new SQLiteL1()); + $this->performSynchronizationTest( + $central, + $this->l1Factory()->create('sqlite'), + $this->l1Factory()->create('sqlite') + ); + + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('sqlite'), + $this->l1Factory()->create('static') + ); + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('static'), + $this->l1Factory()->create('sqlite') + ); + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('sqlite'), + $this->l1Factory()->create('sqlite') + ); } public function testSynchronizationDatabase() { $this->createSchema(); $central = new DatabaseL2($this->dbh); - $this->performSynchronizationTest($central, new StaticL1('testSynchronizationDatabase1'), new StaticL1('testSynchronizationDatabase2')); - $this->performClearSynchronizationTest($central, new StaticL1('testSynchronizationDatabase1a'), new StaticL1('testSynchronizationDatabase2a')); + $this->performSynchronizationTest( + $central, + $this->l1Factory()->create('static', 'testSynchronizationDatabase1'), + $this->l1Factory()->create('static', 'testSynchronizationDatabase2') + ); + $this->performClearSynchronizationTest( + $central, + $this->l1Factory()->create('static', 'testSynchronizationDatabase1a'), + $this->l1Factory()->create('static', 'testSynchronizationDatabase2a') + ); } public function testTaggedSynchronizationDatabase() { $this->createSchema(); $central = new DatabaseL2($this->dbh); - $this->performTaggedSynchronizationTest($central, new StaticL1('testTaggedSynchronizationDatabase1'), new StaticL1('testTaggedSynchronizationDatabase2')); + $this->performTaggedSynchronizationTest( + $central, + $this->l1Factory()->create('static', 'testTaggedSynchronizationDatabase1'), + $this->l1Factory()->create('static', 'testTaggedSynchronizationDatabase2') + ); } public function testBrokenDatabaseFallback() { $this->createSchema(); $l2 = new DatabaseL2($this->dbh, '', true); - $l1 = new StaticL1('first'); + $l1 = $this->l1Factory()->create('static', 'first'); $pool = new Integrated($l1, $l2); $myaddr = new Address('mybin', 'mykey'); @@ -535,7 +441,7 @@ public function testBrokenDatabaseFallback() $this->assertNull($l2->getAddressesForTag('mytag')); // Try applying events to an uninitialized L1. - $this->assertNull($l2->applyEvents(new StaticL1())); + $this->assertNull($l2->applyEvents($this->l1Factory()->create('static'))); // Try garbage collection routines. $pool->collectGarbage(); @@ -547,7 +453,7 @@ public function testDatabaseL2SyncWithNoWrites() { $this->createSchema(); $l2 = new DatabaseL2($this->dbh, '', true); - $l1 = new StaticL1('first'); + $l1 = $this->l1Factory()->create('static', 'first'); $pool = new Integrated($l1, $l2); $pool->synchronize(); } @@ -569,38 +475,11 @@ public function testEmptyCleanUpDatabaseL2() $l2 = new DatabaseL2($this->dbh); } - protected function performExistsTest($l1) - { - $myaddr = new Address('mybin', 'mykey'); - $l1->set(1, $myaddr, 'myvalue'); - $this->assertTrue($l1->exists($myaddr)); - $l1->delete(2, $myaddr); - $this->assertFalse($l1->exists($myaddr)); - } - - public function testExistsAPCuL1() - { - $l1 = new APCuL1('first'); - $this->performExistsTest($l1); - } - - public function testExistsStaticL1() - { - $l1 = new StaticL1(); - $this->performExistsTest($l1); - } - - public function testExistsSQLiteL1() - { - $l1 = new SQLiteL1(); - $this->performExistsTest($l1); - } - public function testExistsIntegrated() { $this->createSchema(); $l2 = new DatabaseL2($this->dbh); - $l1 = new APCuL1('first'); + $l1 = $this->l1Factory()->create('apcu', 'first'); $pool = new Integrated($l1, $l2); $myaddr = new Address('mybin', 'mykey'); $pool->set($myaddr, 'myvalue'); @@ -618,86 +497,17 @@ public function testDatabaseL2Prefix() $this->assertEquals('myvalue', $l2->get($myaddr)); } - public function testAPCuL1PoolIDs() - { - // Test unique ID generation. - $l1 = new APCuL1(); - $this->assertNotNull($l1->getPool()); - - // Test host-based generation. - $_SERVER['SERVER_ADDR'] = 'localhost'; - $_SERVER['SERVER_PORT'] = '80'; - $l1 = new APCuL1(); - $this->assertEquals('localhost-80', $l1->getPool()); - } - - protected function performL1AntirollbackTest($l1) - { - $myaddr = new Address('mybin', 'mykey'); - $current_event_id = $l1->getLastAppliedEventID(); - if (is_null($current_event_id)) { - $current_event_id = 1; - } - $l1->set($current_event_id++, $myaddr, 'myvalue'); - $this->assertEquals('myvalue', $l1->get($myaddr)); - $l1->set($current_event_id - 2, $myaddr, 'myoldvalue'); - $this->assertEquals('myvalue', $l1->get($myaddr)); - } - - public function testAPCuL1Antirollback() - { - $l1 = new APCuL1('first'); - $this->performL1AntirollbackTest($l1); - } - - public function testSQLite1Antirollback() - { - $l1 = new SQLiteL1(); - $this->performL1AntirollbackTest($l1); - } - - protected function performL1HitMissTest($l1) - { - $myaddr = new Address('mybin', 'mykey'); - $current_hits = $l1->getHits(); - $current_misses = $l1->getMisses(); - $current_event_id = 1; - $l1->get($myaddr); - $this->assertEquals($current_misses + 1, $l1->getMisses()); - $l1->set($current_event_id++, $myaddr, 'myvalue'); - $l1->get($myaddr); - $this->assertEquals($current_hits + 1, $l1->getHits()); - } - - public function testStaticL1HitMiss() - { - $l1 = new StaticL1(); - $this->performL1HitMissTest($l1); - } - - public function testAPCuL1HitMiss() - { - $l1 = new APCuL1('testAPCuL1HitMiss'); - $this->performL1HitMissTest($l1); - } - - public function testSQLiteL1HitMiss() - { - $l1 = new SQLiteL1(); - $this->performL1HitMissTest($l1); - } - public function testPoolIntegrated() { $l2 = new StaticL2(); - $l1 = new APCuL1('first'); + $l1 = $this->l1Factory()->create('apcu', 'first'); $pool = new Integrated($l1, $l2); $this->assertEquals('first', $pool->getPool()); } protected function performFailedUnserializationTest($l2) { - $l1 = new StaticL1(); + $l1 = $this->l1Factory()->create('static'); $pool = new Integrated($l1, $l2); $myaddr = new Address('mybin', 'mykey'); @@ -723,7 +533,7 @@ protected function performFailedUnserializationTest($l2) protected function performCaughtUnserializationOnGetTest($l2) { - $l1 = new StaticL1(); + $l1 = $this->l1Factory()->create('static'); $pool = new Integrated($l1, $l2); $invalid_object = 'O:10:"HelloWorl":0:{}'; $myaddr = new Address('mybin', 'performCaughtUnserializationOnGetTest'); @@ -759,7 +569,7 @@ public function testStaticL2FailedUnserialization() // Callers should expect an UnserializationException. protected function performFailedUnserializationOnGetTest($l2) { - $l1 = new StaticL1(); + $l1 = $this->l1Factory()->create('static'); $pool = new Integrated($l1, $l2); $invalid_object = 'O:10:"HelloWorl":0:{}'; $myaddr = new Address('mybin', 'performFailedUnserializationOnGetTest'); @@ -788,7 +598,7 @@ public function testStaticL2FailedUnserializationOnGet() public function performGarbageCollectionTest($l2) { - $pool = new Integrated(new StaticL1(), $l2); + $pool = new Integrated($this->l1Factory()->create('static'), $l2); $myaddr = new Address('mybin', 'mykey'); $this->assertEquals(0, $l2->countGarbage()); $pool->set($myaddr, 'myvalue', -1); @@ -810,7 +620,7 @@ public function testStaticL2GarbageCollection() $this->performGarbageCollectionTest($l2); // Test item limits. - $pool = new Integrated(new StaticL1(), $l2); + $pool = new Integrated($this->l1Factory()->create('static'), $l2); $myaddr2 = new Address('mybin', 'mykey2'); $myaddr3 = new Address('mybin', 'mykey3'); $pool->collectGarbage(); @@ -821,6 +631,11 @@ public function testStaticL2GarbageCollection() $this->assertEquals(1, $l2->countGarbage()); } + /** + * @todo Is this still needed, or it can be deleted. + * Same tests are implemented against all L1 drivers directly in + * L1CacheTest::testStateStorage(). + */ protected function performHitSetCounterTest($l1) { $pool = new Integrated($l1, new StaticL2()); @@ -841,20 +656,19 @@ protected function performHitSetCounterTest($l1) $this->assertEquals(-1, $l1->getKeyOverhead($myaddr2)); } - public function testStaticL1Counters() { - $this->performHitSetCounterTest(new StaticL1()); + $this->performHitSetCounterTest($this->l1Factory()->create('static')); } public function testAPCuL1Counters() { - $this->performHitSetCounterTest(new APCuL1('counters')); + $this->performHitSetCounterTest($this->l1Factory()->create('apcu', 'counters')); } public function testSQLiteL1Counters() { - $this->performHitSetCounterTest(new SQLiteL1()); + $this->performHitSetCounterTest($this->l1Factory()->create('sqlite')); } protected function performExcessiveOverheadSkippingTest($l1) @@ -895,34 +709,34 @@ protected function performExcessiveOverheadSkippingTest($l1) public function testStaticL1ExcessiveOverheadSkipping() { - $this->performExcessiveOverheadSkippingTest(new StaticL1()); + $this->performExcessiveOverheadSkippingTest($this->l1Factory()->create('static')); } public function testAPCuL1ExcessiveOverheadSkipping() { - $this->performExcessiveOverheadSkippingTest(new APCuL1('overhead')); + $this->performExcessiveOverheadSkippingTest($this->l1Factory()->create('apcu', 'overhead')); } public function testSQLiteL1ExcessiveOverheadSkipping() { - $this->performExcessiveOverheadSkippingTest(new SQLiteL1()); + $this->performExcessiveOverheadSkippingTest($this->l1Factory()->create('sqlite')); } public function testAPCuL1IntegratedExpiration() { - $l1 = new APCuL1('expiration'); + $l1 = $this->l1Factory()->create('apcu', 'expiration'); $this->performIntegratedExpiration($l1); } public function testStaticL1IntegratedExpiration() { - $l1 = new StaticL1(); + $l1 = $this->l1Factory()->create('static'); $this->performIntegratedExpiration($l1); } public function testSQLiteL1IntegratedExpiration() { - $l1 = new SQLiteL1(); + $l1 = $this->l1Factory()->create('sqlite'); $this->performIntegratedExpiration($l1); } @@ -960,23 +774,6 @@ public function testDatabaseL2BatchDeletion() $this->assertNull($l2->get($myaddr)); } - public function testSQLiteL1SchemaErrorHandling() - { - $pool_name = uniqid('', true) . '-' . mt_rand(); - $l1_a = new SQLiteL1($pool_name); - - // Opening a second instance of the same pool should work. - $l1_b = new SQLiteL1($pool_name); - - $myaddr = new Address('mybin', 'mykey'); - - $l1_a->set(1, $myaddr, 'myvalue'); - - // Reading from the second handle should show the value written to the - // first. - $this->assertEquals('myvalue', $l1_b->get($myaddr)); - } - public function testDatabaseL2CleanupAfterWrite() { $this->createSchema();