From c024521f4dbb10633b1bd079c9a767f4828739e4 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Tue, 19 May 2026 08:29:54 +0530 Subject: [PATCH 1/2] Add SimpleCacheV3Test for PSR-16 v3 strict type hints (#121) --- README.md | 12 ++ composer.json | 1 + src/SimpleCacheV3Test.php | 328 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 341 insertions(+) create mode 100644 src/SimpleCacheV3Test.php diff --git a/README.md b/README.md index d8c4901..af6f0f3 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,18 @@ class CacheIntegrationTest extends SimpleCacheTest } ``` +If your implementation uses **psr/simple-cache ^3.0** (with strict PHP type hints), extend `SimpleCacheV3Test` instead. It provides the same test coverage with data providers adapted for v3's typed interface: + +```php +class CacheV3IntegrationTest extends SimpleCacheV3Test +{ + public function createSimpleCache() + { + return new SimpleCache(); + } +} +``` + ### Contribute Contributions are very welcome! Send a pull request or diff --git a/composer.json b/composer.json index f223b78..874f5fb 100755 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "php": ">=5.5.9" }, "require-dev": { + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "cache/cache": "^1.0", "symfony/cache": "^3.4.31|^4.3.4|^5.0", "symfony/phpunit-bridge": "^5.1,<5.3", diff --git a/src/SimpleCacheV3Test.php b/src/SimpleCacheV3Test.php new file mode 100644 index 0000000..f371a33 --- /dev/null +++ b/src/SimpleCacheV3Test.php @@ -0,0 +1,328 @@ +, Tobias Nyholm + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +namespace Cache\IntegrationTests; + +use PHPUnit\Framework\Attributes\DataProvider; + +/** + * PSR-16 integration test suite for implementations using psr/simple-cache ^3.0. + * + * PSR-16 v3 introduced strict PHP type hints (string $key, iterable $keys, + * null|int|DateInterval $ttl). These type hints cause PHP to throw \TypeError + * before the library code ever runs when clearly-invalid argument types are + * passed. This makes many of the original SimpleCacheTest data providers + * incompatible with v3. + * + * This subclass: + * - Filters data providers to only values that survive strict/coercion checks + * - Accepts both \TypeError (PHP-level rejection) and + * \Psr\SimpleCache\InvalidArgumentException (library-level validation) as + * valid failure modes + * - Is fully backward-compatible: existing v1/v2 consumers keep using + * SimpleCacheTest; v3 consumers switch to SimpleCacheV3Test + * + * @see \Cache\IntegrationTests\SimpleCacheTest + */ +abstract class SimpleCacheV3Test extends SimpleCacheTest +{ + /** + * Data provider for invalid array keys in setMultiple. + * + * Contains only string keys that are invalid per PSR-16 spec. + * Non-string key types (bool, null, float, int, object, array) are removed + * because PSR-16 v3's string type hint rejects them at PHP level before the + * implementation is invoked. + * + * @return list> + */ + public static function invalidArrayKeys() + { + return [ + [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + ]; + } + + /** + * Data provider for invalid cache keys. + * + * Keeps the parent's structure (array_merge around invalidArrayKeys) but + * adds no extra items, since the parent's extra `[2]` is an integer and + * PSR-16 v3's string type hint rejects it at PHP level. + * + * @return list> + */ + public static function invalidKeys() + { + return array_merge( + self::invalidArrayKeys(), + [] + ); + } + + /** + * Data provider for invalid TTL values. + * + * PSR-16 v3 accepts only null|int|DateInterval for TTL. Values that coerce + * to valid types in PHP weak mode (e.g. true→1, false→0, ' 1'→1, '025'→25) + * are removed because they would not exercise invalid-TTL handling. + * + * @return list> + */ + public static function invalidTtl() + { + return [ + [''], + ['abc'], + ['12foo'], + [new \stdClass()], + [['array']], + ]; + } + + /** + * Helper that runs the supplied callable inside a try/catch. + * + * With PSR-16 v3 strict type hints a non-string key, non-iterable iterable, + * or invalid TTL will cause a \TypeError before the library sees the data. + * The PSR-16 spec says a key MUST be a string, so that is consistent. + * When the implementation is also typed we accept TypeError as a valid + * failure indicator for clearly-invalid arguments. + * + * @param callable(): void $callable + */ + private function assertCacheExceptionOrTypeError(callable $callable): void + { + try { + $callable(); + $this->fail('Expected exception to be thrown.'); + } catch (\TypeError $e) { + // PSR-16 v3 typed interfaces throw TypeError for clearly-invalid + // argument types (e.g. null key, object key, string for iterable). + $this->assertTrue(true); + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + // Explicit library-level validation. + $this->assertTrue(true); + } + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testGetInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->get($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testGetMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->getMultiple(['key1', $key, 'key2']); + }); + } + + public function testGetMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->getMultiple('key'); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testSetInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->set($key, 'foobar'); + }); + } + + /** + * @dataProvider invalidArrayKeys + */ + #[DataProvider('invalidArrayKeys')] + public function testSetMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $values = function () use ($key) { + yield 'key1' => 'foo'; + yield $key => 'bar'; + yield 'key2' => 'baz'; + }; + $this->cache->setMultiple($values()); + }); + } + + public function testSetMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->setMultiple('key'); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testHasInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->has($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testDeleteInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->delete($key); + }); + } + + /** + * @dataProvider invalidKeys + */ + #[DataProvider('invalidKeys')] + public function testDeleteMultipleInvalidKeys($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->deleteMultiple(['key1', $key, 'key2']); + }); + } + + public function testDeleteMultipleNoIterable() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->deleteMultiple('key'); + }); + } + + /** + * @dataProvider invalidTtl + */ + #[DataProvider('invalidTtl')] + public function testSetInvalidTtl($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + } + + /** + * @dataProvider invalidTtl + */ + #[DataProvider('invalidTtl')] + public function testSetMultipleInvalidTtl($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + } + + /** + * Tests the PSR-16 mandated minimum key length of 64 characters. + * + * PSR-16 explicitly states: "The key length MUST be at least 64 characters." + * This test ensures every compliant implementation supports at least 64 chars. + * + * @see https://www.php-fig.org/psr/psr-16/ + */ + public function testBasicUsageWithLongKey64() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $key = str_repeat('a', 64); + + $this->assertFalse($this->cache->has($key)); + $this->assertTrue($this->cache->set($key, 'value')); + + $this->assertTrue($this->cache->has($key)); + $this->assertSame('value', $this->cache->get($key)); + + $this->assertTrue($this->cache->delete($key)); + + $this->assertFalse($this->cache->has($key)); + } +} From d88069881155e2cf2aea4349254929189c7290e1 Mon Sep 17 00:00:00 2001 From: GautamMKGarg Date: Sat, 30 May 2026 11:38:29 +0530 Subject: [PATCH 2/2] Merged SimpleCacheTest and SimpleCachev3Test as sugggested by stof --- README.md | 12 -- composer.json | 3 +- src/SimpleCacheTest.php | 431 ++++++++++++++++++++++++++++++++++++-- src/SimpleCacheV3Test.php | 328 ----------------------------- 4 files changed, 411 insertions(+), 363 deletions(-) delete mode 100644 src/SimpleCacheV3Test.php diff --git a/README.md b/README.md index af6f0f3..d8c4901 100644 --- a/README.md +++ b/README.md @@ -52,18 +52,6 @@ class CacheIntegrationTest extends SimpleCacheTest } ``` -If your implementation uses **psr/simple-cache ^3.0** (with strict PHP type hints), extend `SimpleCacheV3Test` instead. It provides the same test coverage with data providers adapted for v3's typed interface: - -```php -class CacheV3IntegrationTest extends SimpleCacheV3Test -{ - public function createSimpleCache() - { - return new SimpleCache(); - } -} -``` - ### Contribute Contributions are very welcome! Send a pull request or diff --git a/composer.json b/composer.json index 874f5fb..02d1994 100755 --- a/composer.json +++ b/composer.json @@ -23,7 +23,8 @@ } ], "require": { - "php": ">=5.5.9" + "php": ">=5.5.9", + "composer-runtime-api": "^2.0" }, "require-dev": { "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", diff --git a/src/SimpleCacheTest.php b/src/SimpleCacheTest.php index 0165b26..99bde35 100644 --- a/src/SimpleCacheTest.php +++ b/src/SimpleCacheTest.php @@ -1,5 +1,7 @@ > */ public static function invalidKeys() { - return array_merge( - self::invalidArrayKeys(), - [ - [2], - ] - ); + if (self::isPsr16V1()) { + return array_merge( + self::invalidArrayKeys(), + [ + [2], + ] + ); + } + + // v2/v3: only syntax violations survive PHP type hints + return [ + [''], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + ]; } /** - * Data provider for invalid array keys. + * Data provider for invalid array keys (used in setMultiple). * - * @return array + * @return list> */ public static function invalidArrayKeys() { + if (self::isPsr16V1()) { + return [ + [''], + [true], + [false], + [null], + [2.5], + ['{str'], + ['rand{'], + ['rand{str'], + ['rand}str'], + ['rand(str'], + ['rand)str'], + ['rand/str'], + ['rand\\str'], + ['rand@str'], + ['rand:str'], + [new \stdClass()], + [['array']], + ]; + } + return [ [''], - [true], - [false], - [null], - [2.5], ['{str'], ['rand{'], ['rand{str'], @@ -108,34 +187,140 @@ public static function invalidArrayKeys() ['rand\\str'], ['rand@str'], ['rand:str'], + ]; + } + + /** + * Data provider for invalid TTL values. + * + * On v1, includes every invalid TTL type. + * On v2/v3, only values that survive PHP's null|int|\DateInterval hint + * but are still invalid per the PSR-16 spec remain. + * + * @return list> + */ + public static function invalidTtl() + { + $values = [ + [''], + ['abc'], [new \stdClass()], [['array']], ]; + + if (self::isPsr16V1()) { + // In v1 all of these reach the library and must throw + // InvalidArgumentException. + $values = array_merge( + $values, + [ + [true], + [false], + [2.5], + [' 1'], // can be casted to a int + ['12foo'], // can be casted to a int + ['025'], // can be interpreted as hex + ] + ); + } + + return $values; } /** - * @return array + * Data provider for keys whose *type* violates the interface contract. + * + * These values are only valid when psr/simple-cache ^2.0|^3.0 is installed, + * because v2/v3 introduced parameter type hints that reject them with a + * PHP \TypeError before the library code runs. + * + * @return list> */ - public static function invalidTtl() + public static function invalidKeysTypeViolation() { + if (self::isPsr16V1()) { + return []; + } + return [ - [''], [true], [false], - ['abc'], + [null], + [2.5], + [2], + [new \stdClass()], + [['array']], + ]; + } + + /** + * Data provider for array keys whose *type* violates the interface contract. + * + * Differs from invalidKeysTypeViolation: integer keys are excluded because + * PHP arrays/generators auto-cast integer keys to strings (e.g. yield 0 => 'v'), + * so an integer key inside an iterable is not a testable contract violation. + * + * @return list> + */ + public static function invalidArrayKeysTypeViolation() + { + if (self::isPsr16V1()) { + return []; + } + + return [ + [true], + [false], + [null], [2.5], - [' 1'], // can be casted to a int - ['12foo'], // can be casted to a int - ['025'], // can be interpreted as hex [new \stdClass()], [['array']], ]; } + /** + * Data provider for TTL values whose *type* violates the interface contract. + * + * @return list> + */ + public static function invalidTtlTypeViolation() + { + if (self::isPsr16V1()) { + return []; + } + + return [ + [true], + [false], + [2.5], + [' 1'], + ['12foo'], + ['025'], + ]; + } + + /** + * Accepts either \Psr\SimpleCache\InvalidArgumentException (library-level) or + * \TypeError (PHP-level contract violation) as a valid failure. + * + * @param callable(): void $callable + */ + private function assertCacheExceptionOrTypeError(callable $callable): void + { + try { + $callable(); + $this->fail('Expected exception to be thrown.'); + } catch (\TypeError $e) { + $this->assertTrue(true); + } catch (\Psr\SimpleCache\InvalidArgumentException $e) { + $this->assertTrue(true); + } + } + /** * Data provider for valid keys. * - * @return array + * @return list> */ public static function validKeys() { @@ -148,7 +333,7 @@ public static function validKeys() /** * Data provider for valid data to store. * - * @return array + * @return list> */ public static function validData() { @@ -460,6 +645,21 @@ public function testGetInvalidKeys($key) $this->cache->get($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->get($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -474,12 +674,34 @@ public function testGetMultipleInvalidKeys($key) $result = $this->cache->getMultiple(['key1', $key, 'key2']); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testGetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->getMultiple(['key1', $key, 'key2']); + }); + } + public function testGetMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->getMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $result = $this->cache->getMultiple('key'); } @@ -498,6 +720,21 @@ public function testSetInvalidKeys($key) $this->cache->set($key, 'foobar'); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testSetInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->set($key, 'foobar'); + }); + } + /** * @dataProvider invalidArrayKeys */ @@ -517,12 +754,39 @@ public function testSetMultipleInvalidKeys($key) $this->cache->setMultiple($values()); } + /** + * @dataProvider invalidArrayKeysTypeViolation + */ + #[DataProvider('invalidArrayKeysTypeViolation')] + public function testSetMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $values = function () use ($key) { + yield 'key1' => 'foo'; + yield $key => 'bar'; + yield 'key2' => 'baz'; + }; + $this->cache->setMultiple($values()); + }); + } + public function testSetMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->setMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->setMultiple('key'); } @@ -541,6 +805,21 @@ public function testHasInvalidKeys($key) $this->cache->has($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testHasInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->has($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -555,6 +834,21 @@ public function testDeleteInvalidKeys($key) $this->cache->delete($key); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->delete($key); + }); + } + /** * @dataProvider invalidKeys */ @@ -569,12 +863,34 @@ public function testDeleteMultipleInvalidKeys($key) $this->cache->deleteMultiple(['key1', $key, 'key2']); } + /** + * @dataProvider invalidKeysTypeViolation + */ + #[DataProvider('invalidKeysTypeViolation')] + public function testDeleteMultipleInvalidKeysTypeViolation($key) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($key) { + $this->cache->deleteMultiple(['key1', $key, 'key2']); + }); + } + public function testDeleteMultipleNoIterable() { if (isset($this->skippedTests[__FUNCTION__])) { $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () { + $this->cache->deleteMultiple('key'); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->deleteMultiple('key'); } @@ -589,10 +905,32 @@ public function testSetInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->set('key', 'value', $ttl); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->set('key', 'value', $ttl); + }); + } + /** * @dataProvider invalidTtl */ @@ -603,10 +941,32 @@ public function testSetMultipleInvalidTtl($ttl) $this->markTestSkipped($this->skippedTests[__FUNCTION__]); } + if (!self::isPsr16V1()) { + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + return; + } + $this->expectException('Psr\SimpleCache\InvalidArgumentException'); $this->cache->setMultiple(['key' => 'value'], $ttl); } + /** + * @dataProvider invalidTtlTypeViolation + */ + #[DataProvider('invalidTtlTypeViolation')] + public function testSetMultipleInvalidTtlTypeViolation($ttl) + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $this->assertCacheExceptionOrTypeError(function () use ($ttl) { + $this->cache->setMultiple(['key' => 'value'], $ttl); + }); + } + public function testNullOverwrite() { if (isset($this->skippedTests[__FUNCTION__])) { @@ -807,4 +1167,31 @@ public function testObjectDoesNotChangeInCache() $cacheObject = $this->cache->get('key'); $this->assertEquals('value', $cacheObject->foo, 'Object in cache should not have their values changed.'); } + + /** + * Tests the PSR-16 mandated minimum key length of 64 characters. + * + * PSR-16 explicitly states: "The key length MUST be at least 64 characters." + * This test ensures every compliant implementation supports at least 64 chars. + * + * @see https://www.php-fig.org/psr/psr-16/ + */ + public function testBasicUsageWithLongKey64() + { + if (isset($this->skippedTests[__FUNCTION__])) { + $this->markTestSkipped($this->skippedTests[__FUNCTION__]); + } + + $key = str_repeat('a', 64); + + $this->assertFalse($this->cache->has($key)); + $this->assertTrue($this->cache->set($key, 'value')); + + $this->assertTrue($this->cache->has($key)); + $this->assertSame('value', $this->cache->get($key)); + + $this->assertTrue($this->cache->delete($key)); + + $this->assertFalse($this->cache->has($key)); + } } diff --git a/src/SimpleCacheV3Test.php b/src/SimpleCacheV3Test.php deleted file mode 100644 index f371a33..0000000 --- a/src/SimpleCacheV3Test.php +++ /dev/null @@ -1,328 +0,0 @@ -, Tobias Nyholm - * - * This source file is subject to the MIT license that is bundled - * with this source code in the file LICENSE. - */ - -namespace Cache\IntegrationTests; - -use PHPUnit\Framework\Attributes\DataProvider; - -/** - * PSR-16 integration test suite for implementations using psr/simple-cache ^3.0. - * - * PSR-16 v3 introduced strict PHP type hints (string $key, iterable $keys, - * null|int|DateInterval $ttl). These type hints cause PHP to throw \TypeError - * before the library code ever runs when clearly-invalid argument types are - * passed. This makes many of the original SimpleCacheTest data providers - * incompatible with v3. - * - * This subclass: - * - Filters data providers to only values that survive strict/coercion checks - * - Accepts both \TypeError (PHP-level rejection) and - * \Psr\SimpleCache\InvalidArgumentException (library-level validation) as - * valid failure modes - * - Is fully backward-compatible: existing v1/v2 consumers keep using - * SimpleCacheTest; v3 consumers switch to SimpleCacheV3Test - * - * @see \Cache\IntegrationTests\SimpleCacheTest - */ -abstract class SimpleCacheV3Test extends SimpleCacheTest -{ - /** - * Data provider for invalid array keys in setMultiple. - * - * Contains only string keys that are invalid per PSR-16 spec. - * Non-string key types (bool, null, float, int, object, array) are removed - * because PSR-16 v3's string type hint rejects them at PHP level before the - * implementation is invoked. - * - * @return list> - */ - public static function invalidArrayKeys() - { - return [ - [''], - ['{str'], - ['rand{'], - ['rand{str'], - ['rand}str'], - ['rand(str'], - ['rand)str'], - ['rand/str'], - ['rand\\str'], - ['rand@str'], - ['rand:str'], - ]; - } - - /** - * Data provider for invalid cache keys. - * - * Keeps the parent's structure (array_merge around invalidArrayKeys) but - * adds no extra items, since the parent's extra `[2]` is an integer and - * PSR-16 v3's string type hint rejects it at PHP level. - * - * @return list> - */ - public static function invalidKeys() - { - return array_merge( - self::invalidArrayKeys(), - [] - ); - } - - /** - * Data provider for invalid TTL values. - * - * PSR-16 v3 accepts only null|int|DateInterval for TTL. Values that coerce - * to valid types in PHP weak mode (e.g. true→1, false→0, ' 1'→1, '025'→25) - * are removed because they would not exercise invalid-TTL handling. - * - * @return list> - */ - public static function invalidTtl() - { - return [ - [''], - ['abc'], - ['12foo'], - [new \stdClass()], - [['array']], - ]; - } - - /** - * Helper that runs the supplied callable inside a try/catch. - * - * With PSR-16 v3 strict type hints a non-string key, non-iterable iterable, - * or invalid TTL will cause a \TypeError before the library sees the data. - * The PSR-16 spec says a key MUST be a string, so that is consistent. - * When the implementation is also typed we accept TypeError as a valid - * failure indicator for clearly-invalid arguments. - * - * @param callable(): void $callable - */ - private function assertCacheExceptionOrTypeError(callable $callable): void - { - try { - $callable(); - $this->fail('Expected exception to be thrown.'); - } catch (\TypeError $e) { - // PSR-16 v3 typed interfaces throw TypeError for clearly-invalid - // argument types (e.g. null key, object key, string for iterable). - $this->assertTrue(true); - } catch (\Psr\SimpleCache\InvalidArgumentException $e) { - // Explicit library-level validation. - $this->assertTrue(true); - } - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testGetInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->get($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testGetMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->getMultiple(['key1', $key, 'key2']); - }); - } - - public function testGetMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->getMultiple('key'); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testSetInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->set($key, 'foobar'); - }); - } - - /** - * @dataProvider invalidArrayKeys - */ - #[DataProvider('invalidArrayKeys')] - public function testSetMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $values = function () use ($key) { - yield 'key1' => 'foo'; - yield $key => 'bar'; - yield 'key2' => 'baz'; - }; - $this->cache->setMultiple($values()); - }); - } - - public function testSetMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->setMultiple('key'); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testHasInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->has($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testDeleteInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->delete($key); - }); - } - - /** - * @dataProvider invalidKeys - */ - #[DataProvider('invalidKeys')] - public function testDeleteMultipleInvalidKeys($key) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($key) { - $this->cache->deleteMultiple(['key1', $key, 'key2']); - }); - } - - public function testDeleteMultipleNoIterable() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () { - $this->cache->deleteMultiple('key'); - }); - } - - /** - * @dataProvider invalidTtl - */ - #[DataProvider('invalidTtl')] - public function testSetInvalidTtl($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->set('key', 'value', $ttl); - }); - } - - /** - * @dataProvider invalidTtl - */ - #[DataProvider('invalidTtl')] - public function testSetMultipleInvalidTtl($ttl) - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $this->assertCacheExceptionOrTypeError(function () use ($ttl) { - $this->cache->setMultiple(['key' => 'value'], $ttl); - }); - } - - /** - * Tests the PSR-16 mandated minimum key length of 64 characters. - * - * PSR-16 explicitly states: "The key length MUST be at least 64 characters." - * This test ensures every compliant implementation supports at least 64 chars. - * - * @see https://www.php-fig.org/psr/psr-16/ - */ - public function testBasicUsageWithLongKey64() - { - if (isset($this->skippedTests[__FUNCTION__])) { - $this->markTestSkipped($this->skippedTests[__FUNCTION__]); - } - - $key = str_repeat('a', 64); - - $this->assertFalse($this->cache->has($key)); - $this->assertTrue($this->cache->set($key, 'value')); - - $this->assertTrue($this->cache->has($key)); - $this->assertSame('value', $this->cache->get($key)); - - $this->assertTrue($this->cache->delete($key)); - - $this->assertFalse($this->cache->has($key)); - } -}