From 0c9386b9d6e2f1b1b471eeab3fc4bd35637da423 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 16 Jun 2022 07:28:15 +0200 Subject: [PATCH 1/6] Tests: add TypeProviderHelper class Based on a similar class I wrote for the Requests library. If/when that class get published as a package, this class should be removed in favour of the package. --- Tests/TypeProviderHelper.php | 92 ++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Tests/TypeProviderHelper.php diff --git a/Tests/TypeProviderHelper.php b/Tests/TypeProviderHelper.php new file mode 100644 index 00000000..ca2ae22e --- /dev/null +++ b/Tests/TypeProviderHelper.php @@ -0,0 +1,92 @@ +> + */ + public static function getAll() + { + return [ + 'null' => [ + 'input' => null, + ], + 'boolean false' => [ + 'input' => false, + ], + 'boolean true' => [ + 'input' => true, + ], + 'integer 0' => [ + 'input' => 0, + ], + 'negative integer' => [ + 'input' => -123, + ], + 'positive integer' => [ + 'input' => 786687, + ], + 'float 0.0' => [ + 'input' => 0.0, + ], + 'negative float' => [ + 'input' => 5.600e-3, + ], + 'positive float' => [ + 'input' => 124.7, + ], + 'empty string' => [ + 'input' => '', + ], + 'numeric string' => [ + 'input' => '123', + ], + 'textual string' => [ + 'input' => 'foobar', + ], + 'textual string starting with numbers' => [ + 'input' => '123 My Street', + ], + 'empty array' => [ + 'input' => [], + ], + 'array with values, no keys' => [ + 'input' => [1, 2, 3], + ], + 'array with values, string keys' => [ + 'input' => ['a' => 1, 'b' => 2], + ], + 'plain object' => [ + 'input' => new stdClass(), + ], + 'ArrayIterator object' => [ + 'input' => new ArrayIterator([1, 2, 3]), + ], + 'Iterator object, no array access' => [ + 'input' => new EmptyIterator(), + ], + ]; + } +} From 6c755fb81c553e9d5d0a18f06c8b3d382df8e556 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Thu, 16 Jun 2022 07:34:19 +0200 Subject: [PATCH 2/6] :sparkles: New (internal) `Cache` and `NoFileCache` classes ... to allow for caching the results of processing intensive utility methods. The `Cache` class is intended to be used for utility functions which depend on the `$phpcsFile` object. The `NoFileCache` class is for file-independent functions. Use selectively and with care as the memory usage of PHPCS will increase when using these caches! Includes full set of tests to cover this new functionality. --- PHPCSUtils/Internal/Cache.php | 175 ++++++++++ PHPCSUtils/Internal/NoFileCache.php | 134 ++++++++ Tests/Internal/Cache/GetClearTest.php | 335 ++++++++++++++++++++ Tests/Internal/Cache/SetTest.php | 253 +++++++++++++++ Tests/Internal/NoFileCache/GetClearTest.php | 191 +++++++++++ Tests/Internal/NoFileCache/SetTest.php | 174 ++++++++++ 6 files changed, 1262 insertions(+) create mode 100644 PHPCSUtils/Internal/Cache.php create mode 100644 PHPCSUtils/Internal/NoFileCache.php create mode 100644 Tests/Internal/Cache/GetClearTest.php create mode 100644 Tests/Internal/Cache/SetTest.php create mode 100644 Tests/Internal/NoFileCache/GetClearTest.php create mode 100644 Tests/Internal/NoFileCache/SetTest.php diff --git a/PHPCSUtils/Internal/Cache.php b/PHPCSUtils/Internal/Cache.php new file mode 100644 index 00000000..64a3d4c2 --- /dev/null +++ b/PHPCSUtils/Internal/Cache.php @@ -0,0 +1,175 @@ +> Format: $cache[$loop][$fileName][$key][$id] = mixed $value; + */ + private static $cache = []; + + /** + * Check whether a result has been cached for a certain utility function. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param string $key The key to identify a particular set of results. + * It is recommended to pass __METHOD__ to this parameter. + * @param int|string $id Unique identifier for these results. + * Generally speaking this will be the $stackPtr passed + * to the utility function, but it can also something else, + * like a serialization of args passed to a function or an + * md5 hash of an input. + * + * @return bool + */ + public static function isCached(File $phpcsFile, $key, $id) + { + $fileName = $phpcsFile->getFilename(); + $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; + + return isset(self::$cache[$loop][$fileName][$key]) + && \array_key_exists($id, self::$cache[$loop][$fileName][$key]); + } + + /** + * Retrieve a previously cached result for a certain utility function. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param string $key The key to identify a particular set of results. + * It is recommended to pass __METHOD__ to this parameter. + * @param int|string $id Unique identifier for these results. + * Generally speaking this will be the $stackPtr passed + * to the utility function, but it can also something else, + * like a serialization of args passed to a function or an + * md5 hash of an input. + * + * @return mixed + */ + public static function get(File $phpcsFile, $key, $id) + { + $fileName = $phpcsFile->getFilename(); + $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; + + if (isset(self::$cache[$loop][$fileName][$key]) + && \array_key_exists($id, self::$cache[$loop][$fileName][$key]) + ) { + return self::$cache[$loop][$fileName][$key][$id]; + } + + return null; + } + + /** + * Retrieve all previously cached results for a certain utility function and a certain file. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param string $key The key to identify a particular set of results. + * It is recommended to pass __METHOD__ to this parameter. + * + * @return array + */ + public static function getForFile(File $phpcsFile, $key) + { + $fileName = $phpcsFile->getFilename(); + $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; + + if (isset(self::$cache[$loop][$fileName]) + && \array_key_exists($key, self::$cache[$loop][$fileName]) + ) { + return self::$cache[$loop][$fileName][$key]; + } + + return []; + } + + /** + * Cache the result for a certain utility function. + * + * @param \PHP_CodeSniffer\Files\File $phpcsFile The file being scanned. + * @param string $key The key to identify a particular set of results. + * It is recommended to pass __METHOD__ to this parameter. + * @param int|string $id Unique identifier for these results. + * Generally speaking this will be the $stackPtr passed + * to the utility function, but it can also something else, + * like a serialization of args passed to a function or an + * md5 hash of an input. + * @param mixed $value An arbitrary value to write to the cache. + * + * @return mixed + */ + public static function set(File $phpcsFile, $key, $id, $value) + { + $fileName = $phpcsFile->getFilename(); + $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; + + /* + * If this is a phpcbf run and we've reached the next loop, clear the cache + * of all previous loops to free up memory. + */ + if (isset(self::$cache[$loop]) === false + && empty(self::$cache) === false + ) { + self::clear(); + } + + self::$cache[$loop][$fileName][$key][$id] = $value; + } + + /** + * Clear the cache. + * + * @return void + */ + public static function clear() + { + self::$cache = []; + } +} diff --git a/PHPCSUtils/Internal/NoFileCache.php b/PHPCSUtils/Internal/NoFileCache.php new file mode 100644 index 00000000..2fcf86ef --- /dev/null +++ b/PHPCSUtils/Internal/NoFileCache.php @@ -0,0 +1,134 @@ +> Format: $cache[$key][$id] = mixed $value; + */ + private static $cache = []; + + /** + * Check whether a result has been cached for a certain utility function. + * + * @param string $key The key to identify a particular set of results. + * It is recommended to pass `__METHOD__` to this parameter. + * @param int|string $id Unique identifier for these results. + * It is recommended for this to be a serialization of arguments passed + * to the function or an md5 hash of an input. + * + * @return bool + */ + public static function isCached($key, $id) + { + return isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key]); + } + + /** + * Retrieve a previously cached result for a certain utility function. + * + * @param string $key The key to identify a particular set of results. + * It is recommended to pass `__METHOD__` to this parameter. + * @param int|string $id Unique identifier for these results. + * It is recommended for this to be a serialization of arguments passed + * to the function or an md5 hash of an input. + * + * @return mixed + */ + public static function get($key, $id) + { + if (isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key])) { + return self::$cache[$key][$id]; + } + + return null; + } + + /** + * Retrieve all previously cached results for a certain utility function. + * + * @param string $key The key to identify a particular set of results. + * It is recommended to pass `__METHOD__` to this parameter. + * + * @return array + */ + public static function getForKey($key) + { + if (\array_key_exists($key, self::$cache)) { + return self::$cache[$key]; + } + + return []; + } + + /** + * Cache the result for a certain utility function. + * + * @param string $key The key to identify a particular set of results. + * It is recommended to pass `__METHOD__` to this parameter. + * @param int|string $id Unique identifier for these results. + * It is recommended for this to be a serialization of arguments passed + * to the function or an md5 hash of an input. + * @param mixed $value An arbitrary value to write to the cache. + * + * @return mixed + */ + public static function set($key, $id, $value) + { + self::$cache[$key][$id] = $value; + } + + /** + * Clear the cache. + * + * @return void + */ + public static function clear() + { + self::$cache = []; + } +} diff --git a/Tests/Internal/Cache/GetClearTest.php b/Tests/Internal/Cache/GetClearTest.php new file mode 100644 index 00000000..eb460d33 --- /dev/null +++ b/Tests/Internal/Cache/GetClearTest.php @@ -0,0 +1,335 @@ + $dataset) { + Cache::set(self::$phpcsFile, 'Utility1', $id, $dataset['input']); + Cache::set(self::$phpcsFile, 'Utility2', $id, $dataset['input']); + } + } + + /** + * Clear the cache between tests and reset the base fixer loop. + * + * @after + * + * @return void + */ + protected function clearCacheAndFixer() + { + Cache::clear(); + self::$phpcsFile->fixer->enabled = false; + self::$phpcsFile->fixer->loops = 0; + } + + /** + * Test that a cache for a loop which has not been set is identified correctly as such. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseForUnavailableLoop() + { + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 3; + $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility1', 'numeric string')); + } + + /** + * Test that a cache key which has not been set is identified correctly as such. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseForUnavailableKey() + { + $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility3', 'numeric string')); + } + + /** + * Test that a cache id which has not been set is identified correctly as such. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseForUnavailableId() + { + $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility1', 'this ID does not exist')); + } + + /** + * Test that retrieving a cache key for a loop which has not been set, yields null. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullForUnavailableLoop() + { + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 2; + $this->assertNull(Cache::get(self::$phpcsFile, 'Utility1', 'numeric string')); + } + + /** + * Test that retrieving a cache key which has not been set, yields null. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullForUnavailableKey() + { + $this->assertNull(Cache::get(self::$phpcsFile, 'Utility3', 'numeric string')); + } + + /** + * Test that retrieving a cache id which has not been set, yields null. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullForUnavailableId() + { + $this->assertNull(Cache::get(self::$phpcsFile, 'Utility1', 'this ID does not exist')); + } + + /** + * Test that retrieving a cache set for a loop which has not been set, yields an empty array. + * + * @covers ::getForFile + * + * @return void + */ + public function testGetForFileWillReturnEmptyArrayForUnavailableLoop() + { + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 15; + $this->assertSame([], Cache::getForFile(self::$phpcsFile, 'Utility1')); + } + + /** + * Test that retrieving a cache set for a cache key which has not been set, yields an empty array. + * + * @covers ::getForFile + * + * @return void + */ + public function testGetForFileWillReturnEmptyArrayForUnavailableKey() + { + $this->assertSame([], Cache::getForFile(self::$phpcsFile, 'Utility3')); + } + + /** + * Test that previously cached data can be retrieved correctly. + * + * @dataProvider dataEveryTypeOfInput + * + * @covers ::get + * + * @param int|string $id The ID of the cached value to retrieve. + * @param mixed $expected The expected cached value. + * + * @return void + */ + public function testGetWillRetrievedPreviouslySetValue($id, $expected) + { + if (\is_object($expected)) { + $this->assertEquals($expected, Cache::get(self::$phpcsFile, 'Utility1', $id)); + } else { + $this->assertSame($expected, Cache::get(self::$phpcsFile, 'Utility1', $id)); + } + } + + /** + * Data provider. + * + * @return array + */ + public function dataEveryTypeOfInput() + { + $allTypes = TypeProviderHelper::getAll(); + $data = []; + foreach ($allTypes as $key => $dataset) { + $data[$key] = [ + 'id' => $key, + 'expected' => $dataset['input'], + ]; + } + + return $data; + } + + /** + * Test that the `getForFile()` method correctly retrieves a subset of the cached data. + * + * @covers ::getForFile + * + * @return void + */ + public function testGetForFile() + { + $dataSet1 = Cache::getForFile(self::$phpcsFile, 'Utility1'); + $dataSet2 = Cache::getForFile(self::$phpcsFile, 'Utility2'); + $this->assertSame($dataSet1, $dataSet2); + } + + /** + * Test that data cached during a previous loop will not be recognized once we're in a different loop. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillNotConfuseDataFromDifferentLoops() + { + $id = 50; + + // Set an initial cache value and verify it is available. + Cache::set(self::$phpcsFile, __METHOD__, $id, 'Test value'); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the originally cached value' + ); + + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 2; + + // Verify that the original cache is disregarded. + $this->assertFalse( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() still found the originally cached value' + ); + } + + /** + * Test that data cached during a previous loop will not be returned once we're in a different loop. + * + * @covers ::get + * + * @return void + */ + public function testGetWillNotConfuseDataFromDifferentLoops() + { + $id = 872; + + // Set an initial cache value and verify it is available. + Cache::set(self::$phpcsFile, __METHOD__, $id, 'Test value'); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the originally cached value' + ); + + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 4; + + // Verify that the original cache is disregarded. + $this->assertNull( + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'Cache::get() still returned the originally cached value' + ); + } + + /** + * Test that data cached during a previous loop will not be returned once we're in a different loop. + * + * @covers ::getForFile + * + * @return void + */ + public function testGetForFileWillNotConfuseDataFromDifferentLoops() + { + $id = 233; + + // Set an initial cache value and verify it is available. + Cache::set(self::$phpcsFile, __METHOD__, $id, 'Test value'); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the originally cached value' + ); + + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 1; + + // Verify that the original cache is disregarded. + $this->assertSame( + [], + Cache::getForFile(self::$phpcsFile, __METHOD__), + 'Cache::getForFile() still returned the originally cached value' + ); + } + + /** + * Test that previously cached data is no longer available when the cache has been cleared. + * + * @dataProvider dataEveryTypeOfInput + * + * @covers ::clear + * + * @param int|string $id The ID of the cached value to retrieve. + * + * @return void + */ + public function testClearCache($id) + { + Cache::clear(); + + $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility1', $id)); + $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility2', $id)); + } +} diff --git a/Tests/Internal/Cache/SetTest.php b/Tests/Internal/Cache/SetTest.php new file mode 100644 index 00000000..1620d907 --- /dev/null +++ b/Tests/Internal/Cache/SetTest.php @@ -0,0 +1,253 @@ +fixer->enabled = false; + self::$phpcsFile->fixer->loops = 0; + } + + /** + * Test that every data type is accepted as a cachable value, including `null`, that the + * `Cache::isCached()` function recognizes a set value correctly and that all values can be retrieved. + * + * @dataProvider dataEveryTypeOfInput + * + * @param mixed $input Value to cache. + * + * @return void + */ + public function testSetAcceptsEveryTypeOfInput($input) + { + $id = $this->getName(); + Cache::set(self::$phpcsFile, __METHOD__, $id, $input); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the cached value' + ); + + $this->assertSame( + $input, + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'Value retrieved via Cache::get() did not match expectations' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function dataEveryTypeOfInput() + { + return TypeProviderHelper::getAll(); + } + + /** + * Verify that all supported types of IDs are accepted. + * + * Note: this doesn't test the unhappy path of passing an unsupported type of key, but + * non-int/string keys are not accepted by PHP for arrays anyway, so that should result + * in PHP warnings/errors anyway and as this is an internal class, I'm not too concerned + * about that kind of mistake being made. + * + * @dataProvider dataSetAcceptsIntAndStringIdKeys + * + * @param int|string $id ID for the cache. + * + * @return void + */ + public function testSetAcceptsIntAndStringIdKeys($id) + { + $value = 'value' . $id; + + Cache::set(self::$phpcsFile, __METHOD__, $id, $value); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the cached value' + ); + + $this->assertSame( + $value, + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'Value retrieved via Cache::get() did not match expectations' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function dataSetAcceptsIntAndStringIdKeys() + { + return [ + 'ID: int zero' => [ + 'id' => 0, + ], + 'ID: positive int' => [ + 'id' => 12832, + ], + 'ID: negative int' => [ + 'id' => -274, + ], + 'ID: string' => [ + 'id' => 'string ID', + ], + ]; + } + + /** + * Verify that a previously set cached value will be overwritten when set() is called again + * with the same file, ID and key. + * + * @return void + */ + public function testSetWillOverwriteExistingValue() + { + $id = 'my key'; + $origValue = 'original value'; + + Cache::set(self::$phpcsFile, __METHOD__, $id, $origValue); + + // Verify that the original value was set correctly. + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the originally cached value' + ); + $this->assertSame( + $origValue, + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'Original value retrieved via Cache::get() did not match expectations' + ); + + $newValue = 'new value'; + Cache::set(self::$phpcsFile, __METHOD__, $id, $newValue); + + // Verify the overwrite happened. + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() could not find the newly cached value' + ); + $this->assertSame( + $newValue, + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'New value retrieved via Cache::get() did not match expectations' + ); + } + + /** + * Test that previously cached data is no longer available if the fixer has moved on to the next loop. + * + * @return void + */ + public function testSetDoesNotClearCacheWhenFixerDisabled() + { + $idA = 50; + $idB = 52; + + // Set an initial cache value and verify it is available. + Cache::set(self::$phpcsFile, __METHOD__, $idA, 'Test value'); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $idA), + 'Cache::isCached() could not find the originally cached value' + ); + + // Changing loops without the fixer being available should have no effect. + self::$phpcsFile->fixer->loops = 5; + Cache::set(self::$phpcsFile, 'Another method', $idB, 'Test value'); + + // Verify the original cache is still available. + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $idA), + 'Cache::isCached() could not find the originally cached value' + ); + // ... as well as the newly cached value + $this->assertTrue( + Cache::isCached(self::$phpcsFile, 'Another method', $idB), + 'Cache::isCached() could not find the newly cached value' + ); + } + + /** + * Test that previously cached data is no longer available if the fixer has moved on to the next loop. + * + * @return void + */ + public function testSetClearsCacheOnDifferentLoop() + { + $idA = 123; + $idB = 276; + + // Set an initial cache value and verify it is available. + Cache::set(self::$phpcsFile, __METHOD__, $idA, 'Test value'); + + $this->assertTrue( + Cache::isCached(self::$phpcsFile, __METHOD__, $idA), + 'Cache::isCached() could not find the originally cached value' + ); + + // Set a different cache on a different loop, which should clear the original cache. + self::$phpcsFile->fixer->enabled = true; + self::$phpcsFile->fixer->loops = 5; + Cache::set(self::$phpcsFile, 'Another method', $idB, 'Test value'); + + // Verify the original cache is no longer available. + $this->assertFalse( + Cache::isCached(self::$phpcsFile, __METHOD__, $idA), + 'Cache::isCached() still found the originally cached value' + ); + } +} diff --git a/Tests/Internal/NoFileCache/GetClearTest.php b/Tests/Internal/NoFileCache/GetClearTest.php new file mode 100644 index 00000000..7e9c582f --- /dev/null +++ b/Tests/Internal/NoFileCache/GetClearTest.php @@ -0,0 +1,191 @@ + $dataset) { + NoFileCache::set('Utility1', $id, $dataset['input']); + NoFileCache::set('Utility2', $id, $dataset['input']); + } + } + + /** + * Clear the cache between tests. + * + * @after + * + * @return void + */ + protected function clearCache() + { + NoFileCache::clear(); + } + + /** + * Test that a cache key which has not been set is identified correctly as such. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseForUnavailableKey() + { + $this->assertFalse(NoFileCache::isCached('Utility3', 'numeric string')); + } + + /** + * Test that a cache id which has not been set is identified correctly as such. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseForUnavailableId() + { + $this->assertFalse(NoFileCache::isCached('Utility1', 'this ID does not exist')); + } + + /** + * Test that retrieving a cache key which has not been set, yields null. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullForUnavailableKey() + { + $this->assertNull(NoFileCache::get('Utility3', 'numeric string')); + } + + /** + * Test that retrieving a cache id which has not been set, yields null. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullForUnavailableId() + { + $this->assertNull(NoFileCache::get('Utility1', 'this ID does not exist')); + } + + /** + * Test that retrieving a cache set for a cache key which has not been set, yields an empty array. + * + * @covers ::getForKey + * + * @return void + */ + public function testGetForKeyWillReturnEmptyArrayForUnavailableData() + { + $this->assertSame([], NoFileCache::getForKey('Utility3')); + } + + /** + * Test that previously cached data can be retrieved correctly. + * + * @dataProvider dataEveryTypeOfInput + * + * @covers ::get + * + * @param int|string $id The ID of the cached value to retrieve. + * @param mixed $expected The expected cached value. + * + * @return void + */ + public function testGetWillRetrievedPreviouslySetValue($id, $expected) + { + if (\is_object($expected)) { + $this->assertEquals($expected, NoFileCache::get('Utility1', $id)); + } else { + $this->assertSame($expected, NoFileCache::get('Utility1', $id)); + } + } + + /** + * Data provider. + * + * @return array + */ + public function dataEveryTypeOfInput() + { + $allTypes = TypeProviderHelper::getAll(); + $data = []; + foreach ($allTypes as $key => $dataset) { + $data[$key] = [ + 'id' => $key, + 'expected' => $dataset['input'], + ]; + } + + return $data; + } + + /** + * Test that the `getForKey()` method correctly retrieves a subset of the cached data. + * + * @covers ::getForKey + * + * @return void + */ + public function testGetForKey() + { + $dataSet1 = NoFileCache::getForKey('Utility1'); + $dataSet2 = NoFileCache::getForKey('Utility2'); + $this->assertSame($dataSet1, $dataSet2); + } + + /** + * Test that previously cached data is no longer available when the cache has been cleared. + * + * @dataProvider dataEveryTypeOfInput + * + * @covers ::clear + * + * @param int|string $id The ID of the cached value to retrieve. + * + * @return void + */ + public function testClearCache($id) + { + NoFileCache::clear(); + + $this->assertFalse(NoFileCache::isCached('Utility1', $id)); + $this->assertFalse(NoFileCache::isCached('Utility2', $id)); + } +} diff --git a/Tests/Internal/NoFileCache/SetTest.php b/Tests/Internal/NoFileCache/SetTest.php new file mode 100644 index 00000000..4e310e62 --- /dev/null +++ b/Tests/Internal/NoFileCache/SetTest.php @@ -0,0 +1,174 @@ +getName(); + NoFileCache::set(__METHOD__, $id, $input); + + $this->assertTrue( + NoFileCache::isCached(__METHOD__, $id), + 'NoFileCache::isCached() could not find the cached value' + ); + + $this->assertSame( + $input, + NoFileCache::get(__METHOD__, $id), + 'Value retrieved via NoFileCache::get() did not match expectations' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function dataEveryTypeOfInput() + { + return TypeProviderHelper::getAll(); + } + + /** + * Verify that all supported types of IDs are accepted. + * + * Note: this doesn't test the unhappy path of passing an unsupported type of key, but + * non-int/string keys are not accepted by PHP for arrays anyway, so that should result + * in PHP warnings/errors anyway and as this is an internal class, I'm not too concerned + * about that kind of mistake being made. + * + * @dataProvider dataSetAcceptsIntAndStringIdKeys + * + * @param int|string $id ID for the cache. + * + * @return void + */ + public function testSetAcceptsIntAndStringIdKeys($id) + { + $value = 'value' . $id; + + NoFileCache::set(__METHOD__, $id, $value); + + $this->assertTrue( + NoFileCache::isCached(__METHOD__, $id), + 'NoFileCache::isCached() could not find the cached value' + ); + + $this->assertSame( + $value, + NoFileCache::get(__METHOD__, $id), + 'Value retrieved via NoFileCache::get() did not match expectations' + ); + } + + /** + * Data provider. + * + * @return array + */ + public function dataSetAcceptsIntAndStringIdKeys() + { + return [ + 'ID: int zero' => [ + 'id' => 0, + ], + 'ID: positive int' => [ + 'id' => 12832, + ], + 'ID: negative int' => [ + 'id' => -274, + ], + 'ID: string' => [ + 'id' => 'string ID', + ], + ]; + } + + /** + * Verify that a previously set cached value will be overwritten when set() is called again + * with the same file, ID and key. + * + * @return void + */ + public function testSetWillOverwriteExistingValue() + { + $id = 'my key'; + $origValue = 'original value'; + + NoFileCache::set(__METHOD__, $id, $origValue); + + // Verify that the original value was set correctly. + $this->assertTrue( + NoFileCache::isCached(__METHOD__, $id), + 'NoFileCache::isCached() could not find the originally cached value' + ); + $this->assertSame( + $origValue, + NoFileCache::get(__METHOD__, $id), + 'Original value retrieved via NoFileCache::get() did not match expectations' + ); + + $newValue = 'new value'; + NoFileCache::set(__METHOD__, $id, $newValue); + + // Verify the overwrite happened. + $this->assertTrue( + NoFileCache::isCached(__METHOD__, $id), + 'NoFileCache::isCached() could not find the newly cached value' + ); + $this->assertSame( + $newValue, + NoFileCache::get(__METHOD__, $id), + 'New value retrieved via NoFileCache::get() did not match expectations' + ); + } +} From 16e78d5c4cc750fffe70a9ff0d4d354257699252 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 28 Jun 2022 10:42:37 +0200 Subject: [PATCH 3/6] Implement use of the new `Cache` class ... in select places throughout the codebase. --- PHPCSUtils/Utils/Arrays.php | 31 ++++++++++++++++++++--- PHPCSUtils/Utils/ControlStructures.php | 12 +++++++-- PHPCSUtils/Utils/FunctionDeclarations.php | 7 +++++ PHPCSUtils/Utils/Lists.php | 23 ++++++++++++++++- PHPCSUtils/Utils/Namespaces.php | 12 +++++++-- PHPCSUtils/Utils/PassedParameters.php | 10 ++++++++ PHPCSUtils/Utils/TextStrings.php | 6 +++++ PHPCSUtils/Utils/UseStatements.php | 6 +++++ 8 files changed, 98 insertions(+), 9 deletions(-) diff --git a/PHPCSUtils/Utils/Arrays.php b/PHPCSUtils/Utils/Arrays.php index fffe513c..224d4fdf 100644 --- a/PHPCSUtils/Utils/Arrays.php +++ b/PHPCSUtils/Utils/Arrays.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCTokens; use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\FunctionDeclarations; use PHPCSUtils\Utils\Lists; @@ -72,6 +73,10 @@ public static function isShortArray(File $phpcsFile, $stackPtr) return false; } + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + // All known tokenizer bugs are in PHPCS versions before 3.6.0. $phpcsVersion = Helper::getVersion(); @@ -81,6 +86,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) if (isset(Collections::shortArrayTokens()[$tokens[$stackPtr]['code']]) === false) { if (\version_compare($phpcsVersion, '3.3.0', '>=')) { // These will just be properly tokenized, plain square brackets. No need for further checks. + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } @@ -90,6 +96,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) } if (isset($tokens[$opener]['bracket_closer']) === false) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } @@ -109,6 +116,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) if ($prevNonEmpty !== 0 || isset(Collections::phpOpenTags()[$tokens[$prevNonEmpty]['code']]) === false ) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -127,6 +135,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) if ($tokens[$prevNonEmpty]['code'] !== \T_CLOSE_CURLY_BRACKET || isset($tokens[$prevNonEmpty]['scope_condition']) === false ) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -149,6 +158,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) * @link https://github.com/squizlabs/PHP_CodeSniffer/pull/3172 */ if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_QUOTED_STRING) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -162,6 +172,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) * @link https://github.com/squizlabs/PHP_CodeSniffer/pull/3013 */ if (isset(BCTokens::magicConstants()[$tokens[$prevNonEmpty]['code']]) === true) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -177,6 +188,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) if ($tokens[$prevNonEmpty]['code'] === \T_CLOSE_SHORT_ARRAY || $tokens[$prevNonEmpty]['code'] === \T_CONSTANT_ENCAPSED_STRING ) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } @@ -192,6 +204,7 @@ public static function isShortArray(File $phpcsFile, $stackPtr) $openCurly = $tokens[$prevNonEmpty]['bracket_opener']; $beforeCurlies = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($openCurly - 1), null, true); if ($tokens[$beforeCurlies]['code'] === \T_DOLLAR) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -199,7 +212,9 @@ public static function isShortArray(File $phpcsFile, $stackPtr) } // In all other circumstances, make sure this isn't a short list instead of a short array. - return (Lists::isShortList($phpcsFile, $stackPtr) === false); + $returnValue = (Lists::isShortList($phpcsFile, $stackPtr) === false); + Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue); + return $returnValue; } /** @@ -300,11 +315,16 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end) ); } + if (Cache::isCached($phpcsFile, __METHOD__, "$start-$end") === true) { + return Cache::get($phpcsFile, __METHOD__, "$start-$end"); + } + $targets = self::$doubleArrowTargets; $targets += Collections::closedScopes(); $targets += Collections::arrowFunctionTokensBC(); $doubleArrow = ($start - 1); + $returnValue = false; ++$end; do { $doubleArrow = $phpcsFile->findNext( @@ -318,7 +338,8 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end) } if ($tokens[$doubleArrow]['code'] === \T_DOUBLE_ARROW) { - return $doubleArrow; + $returnValue = $doubleArrow; + break; } /* @@ -327,7 +348,8 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end) * @link https://github.com/squizlabs/PHP_CodeSniffer/issues/2865 */ if ($tokens[$doubleArrow]['code'] === \T_STRING && $tokens[$doubleArrow]['content'] === '=>') { - return $doubleArrow; + $returnValue = $doubleArrow; + break; } // Skip over closed scopes which may contain foreach structures or generators. @@ -350,6 +372,7 @@ public static function getDoubleArrowPtr(File $phpcsFile, $start, $end) break; } while ($doubleArrow < $end); - return false; + Cache::set($phpcsFile, __METHOD__, "$start-$end", $returnValue); + return $returnValue; } } diff --git a/PHPCSUtils/Utils/ControlStructures.php b/PHPCSUtils/Utils/ControlStructures.php index b4b6c120..0f0ade83 100644 --- a/PHPCSUtils/Utils/ControlStructures.php +++ b/PHPCSUtils/Utils/ControlStructures.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; /** @@ -249,6 +250,10 @@ public static function getDeclareScopeOpenClose(File $phpcsFile, $stackPtr) ]; } + if (Cache::isCached($phpcsFile, $stackPtr, __METHOD__) === true) { + return Cache::get($phpcsFile, $stackPtr, __METHOD__); + } + $declareCount = 0; $opener = null; $closer = null; @@ -335,14 +340,17 @@ public static function getDeclareScopeOpenClose(File $phpcsFile, $stackPtr) } } + $return = false; + if (isset($opener, $closer)) { - return [ + $return = [ 'opener' => $opener, 'closer' => $closer, ]; } - return false; + Cache::set($phpcsFile, $stackPtr, __METHOD__, $return); + return $return; } /** diff --git a/PHPCSUtils/Utils/FunctionDeclarations.php b/PHPCSUtils/Utils/FunctionDeclarations.php index 3b301977..b9aa6662 100644 --- a/PHPCSUtils/Utils/FunctionDeclarations.php +++ b/PHPCSUtils/Utils/FunctionDeclarations.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCTokens; use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; use PHPCSUtils\Utils\ObjectDeclarations; @@ -750,6 +751,10 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr) * * Now see about finding the relevant arrow function tokens. */ + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + $returnValue = []; $nextNonEmpty = $phpcsFile->findNext( @@ -858,11 +863,13 @@ public static function getArrowFunctionOpenClose(File $phpcsFile, $stackPtr) } if ($scopeCloser === $phpcsFile->numTokens) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } $returnValue['scope_closer'] = $scopeCloser; + Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue); return $returnValue; } diff --git a/PHPCSUtils/Utils/Lists.php b/PHPCSUtils/Utils/Lists.php index 6cd56279..4ebfa000 100644 --- a/PHPCSUtils/Utils/Lists.php +++ b/PHPCSUtils/Utils/Lists.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCTokens; use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; use PHPCSUtils\Utils\Parentheses; @@ -75,6 +76,10 @@ public static function isShortList(File $phpcsFile, $stackPtr) return false; } + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + $phpcsVersion = Helper::getVersion(); /* @@ -103,6 +108,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) if (isset($tokens[$opener]['bracket_closer']) === false) { // Definitely not a short list. + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } @@ -115,6 +121,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) $closer = $tokens[$opener]['bracket_closer']; $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true); if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, true); return true; } } @@ -139,6 +146,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) * @link https://github.com/squizlabs/PHP_CodeSniffer/pull/3172 */ if ($tokens[$prevNonEmpty]['code'] === \T_DOUBLE_QUOTED_STRING) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -152,6 +160,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) * @link https://github.com/squizlabs/PHP_CodeSniffer/pull/3013 */ if (isset(BCTokens::magicConstants()[$tokens[$prevNonEmpty]['code']]) === true) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -167,6 +176,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) if ($tokens[$prevNonEmpty]['code'] === \T_CLOSE_SHORT_ARRAY || $tokens[$prevNonEmpty]['code'] === \T_CONSTANT_ENCAPSED_STRING ) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } @@ -182,6 +192,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) $openCurly = $tokens[$prevNonEmpty]['bracket_opener']; $beforeCurlies = $phpcsFile->findPrevious(Tokens::$emptyTokens, ($openCurly - 1), null, true); if ($tokens[$beforeCurlies]['code'] === \T_DOLLAR) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } @@ -202,6 +213,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) $nextNonEmpty = $phpcsFile->findNext(Tokens::$emptyTokens, ($closer + 1), null, true); if ($nextNonEmpty !== false && $tokens[$nextNonEmpty]['code'] === \T_EQUAL) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, true); return true; } @@ -212,6 +224,7 @@ public static function isShortList(File $phpcsFile, $stackPtr) || $tokens[$prevNonEmpty]['code'] === \T_DOUBLE_ARROW) && Parentheses::lastOwnerIn($phpcsFile, $prevNonEmpty, \T_FOREACH) !== false ) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, true); return true; } @@ -228,13 +241,16 @@ public static function isShortList(File $phpcsFile, $stackPtr) ); if ($parentOpen === false) { + Cache::set($phpcsFile, __METHOD__, $stackPtr, false); return false; } } while (isset($tokens[$parentOpen]['bracket_closer']) === true && $tokens[$parentOpen]['bracket_closer'] < $opener ); - return self::isShortList($phpcsFile, $parentOpen); + $returnValue = self::isShortList($phpcsFile, $parentOpen); + Cache::set($phpcsFile, __METHOD__, $stackPtr, $returnValue); + return $returnValue; } /** @@ -385,6 +401,10 @@ public static function getAssignments(File $phpcsFile, $stackPtr) throw new RuntimeException('The Lists::getAssignments() method expects a long/short list token.'); } + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + $opener = $openClose['opener']; $closer = $openClose['closer']; @@ -502,6 +522,7 @@ public static function getAssignments(File $phpcsFile, $stackPtr) } } + Cache::set($phpcsFile, __METHOD__, $stackPtr, $vars); return $vars; } } diff --git a/PHPCSUtils/Utils/Namespaces.php b/PHPCSUtils/Utils/Namespaces.php index c3b02b32..62a569f7 100644 --- a/PHPCSUtils/Utils/Namespaces.php +++ b/PHPCSUtils/Utils/Namespaces.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCFile; use PHPCSUtils\BackCompat\BCTokens; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\Conditions; use PHPCSUtils\Utils\GetTokensAsString; @@ -260,6 +261,9 @@ public static function findNamespacePtr(File $phpcsFile, $stackPtr) * - and that namespace declarations can't be nested in anything, so we can skip over any * nesting structures. */ + if (Cache::isCached($phpcsFile, $stackPtr, __METHOD__) === true) { + return Cache::get($phpcsFile, $stackPtr, __METHOD__); + } // Start by breaking out of any scoped structures this token is in. $prev = $stackPtr; @@ -338,16 +342,20 @@ public static function findNamespacePtr(File $phpcsFile, $stackPtr) if ($tokens[$prev]['code'] === \T_NAMESPACE && self::isDeclaration($phpcsFile, $prev) === true ) { + $return = $prev; + // Now make sure the token was not part of the declaration. $endOfStatement = $phpcsFile->findNext(Collections::namespaceDeclarationClosers(), ($prev + 1)); if ($endOfStatement > $stackPtr) { - return false; + $return = false; } - return $prev; + Cache::set($phpcsFile, $stackPtr, __METHOD__, $return); + return $return; } } while (true); + Cache::set($phpcsFile, $stackPtr, __METHOD__, false); return false; } diff --git a/PHPCSUtils/Utils/PassedParameters.php b/PHPCSUtils/Utils/PassedParameters.php index 78ea4b31..43983914 100644 --- a/PHPCSUtils/Utils/PassedParameters.php +++ b/PHPCSUtils/Utils/PassedParameters.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\Arrays; use PHPCSUtils\Utils\GetTokensAsString; @@ -205,6 +206,14 @@ public static function getParameters(File $phpcsFile, $stackPtr, $limit = 0, $is return []; } + if (Cache::isCached($phpcsFile, __METHOD__, "$stackPtr-$limit") === true) { + return Cache::get($phpcsFile, __METHOD__, "$stackPtr-$limit"); + } + + if ($limit !== 0 && Cache::isCached($phpcsFile, __METHOD__, "$stackPtr-0") === true) { + return \array_slice(Cache::get($phpcsFile, __METHOD__, "$stackPtr-0"), 0, $limit, true); + } + // Ok, we know we have a valid token with parameters and valid open & close brackets/parenthesis. $tokens = $phpcsFile->getTokens(); @@ -337,6 +346,7 @@ public static function getParameters(File $phpcsFile, $stackPtr, $limit = 0, $is ++$cnt; } + Cache::set($phpcsFile, __METHOD__, "$stackPtr-$limit", $parameters); return $parameters; } diff --git a/PHPCSUtils/Utils/TextStrings.php b/PHPCSUtils/Utils/TextStrings.php index ac46a14c..b64a56d9 100644 --- a/PHPCSUtils/Utils/TextStrings.php +++ b/PHPCSUtils/Utils/TextStrings.php @@ -13,6 +13,7 @@ use PHP_CodeSniffer\Exceptions\RuntimeException; use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; @@ -140,6 +141,10 @@ public static function getEndOfCompleteTextString(File $phpcsFile, $stackPtr) } } + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + switch ($tokens[$stackPtr]['code']) { case \T_START_HEREDOC: $targetType = \T_HEREDOC; @@ -185,6 +190,7 @@ public static function getEndOfCompleteTextString(File $phpcsFile, $stackPtr) $lastPtr = $end; } + Cache::set($phpcsFile, __METHOD__, $stackPtr, $lastPtr); return $lastPtr; } diff --git a/PHPCSUtils/Utils/UseStatements.php b/PHPCSUtils/Utils/UseStatements.php index 38fecf2f..12edb5e2 100644 --- a/PHPCSUtils/Utils/UseStatements.php +++ b/PHPCSUtils/Utils/UseStatements.php @@ -15,6 +15,7 @@ use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\BackCompat\BCTokens; use PHPCSUtils\BackCompat\Helper; +use PHPCSUtils\Internal\Cache; use PHPCSUtils\Utils\Conditions; use PHPCSUtils\Utils\Parentheses; @@ -221,6 +222,10 @@ public static function splitImportUseStatement(File $phpcsFile, $stackPtr) return $statements; } + if (Cache::isCached($phpcsFile, __METHOD__, $stackPtr) === true) { + return Cache::get($phpcsFile, __METHOD__, $stackPtr); + } + ++$endOfStatement; $start = true; @@ -401,6 +406,7 @@ public static function splitImportUseStatement(File $phpcsFile, $stackPtr) } } + Cache::set($phpcsFile, __METHOD__, $stackPtr, $statements); return $statements; } From c7fcc16548a7f3b6a9a8a7191f770b7cc17ed0f7 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Wed, 29 Jun 2022 15:15:47 +0200 Subject: [PATCH 4/6] Implement use of the new `NoFileCache` class ... in select places throughout the codebase. --- PHPCSUtils/Utils/TextStrings.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/PHPCSUtils/Utils/TextStrings.php b/PHPCSUtils/Utils/TextStrings.php index b64a56d9..4438dd81 100644 --- a/PHPCSUtils/Utils/TextStrings.php +++ b/PHPCSUtils/Utils/TextStrings.php @@ -14,6 +14,7 @@ use PHP_CodeSniffer\Files\File; use PHP_CodeSniffer\Util\Tokens; use PHPCSUtils\Internal\Cache; +use PHPCSUtils\Internal\NoFileCache; use PHPCSUtils\Tokens\Collections; use PHPCSUtils\Utils\GetTokensAsString; @@ -362,6 +363,11 @@ public static function getStripEmbeds($text) ]; } + $textHash = \md5($text); + if (NoFileCache::isCached(__METHOD__, $textHash) === true) { + return NoFileCache::get(__METHOD__, $textHash); + } + $offset = 0; $strLen = \strlen($text); // Use iconv ? $stripped = ''; @@ -410,9 +416,12 @@ public static function getStripEmbeds($text) $stripped .= \substr($text, $offset); } - return [ + $returnValue = [ 'embeds' => $variables, 'remaining' => $stripped, ]; + + NoFileCache::set(__METHOD__, $textHash, $returnValue); + return $returnValue; } } From 72b52c70afb2333d183bc60ebb6fb256ad6c9fe6 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 28 Jun 2022 09:14:02 +0200 Subject: [PATCH 5/6] [NoFile]Cache: allow for disabling the cache in test situations ... and cover this change with tests on the off-chance that someone will use this functionality in a non-test situation. --- PHPCSUtils/Internal/Cache.php | 28 ++++++++++ PHPCSUtils/Internal/NoFileCache.php | 22 ++++++-- Tests/Internal/Cache/GetClearTest.php | 57 +++++++++++++++++++++ Tests/Internal/Cache/SetTest.php | 28 ++++++++++ Tests/Internal/NoFileCache/GetClearTest.php | 57 +++++++++++++++++++++ Tests/Internal/NoFileCache/SetTest.php | 28 ++++++++++ 6 files changed, 217 insertions(+), 3 deletions(-) diff --git a/PHPCSUtils/Internal/Cache.php b/PHPCSUtils/Internal/Cache.php index 64a3d4c2..19d364bb 100644 --- a/PHPCSUtils/Internal/Cache.php +++ b/PHPCSUtils/Internal/Cache.php @@ -49,6 +49,18 @@ final class Cache { + /** + * Whether caching is enabled or not. + * + * Note: this switch is ONLY intended for use within test suites and should never + * be touched in any other circumstances! + * + * Don't forget to always turn the cache back on in a `tear_down()` method! + * + * @var bool + */ + public static $enabled = true; + /** * Results cache. * @@ -72,6 +84,10 @@ final class Cache */ public static function isCached(File $phpcsFile, $key, $id) { + if (self::$enabled === false) { + return false; + } + $fileName = $phpcsFile->getFilename(); $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; @@ -95,6 +111,10 @@ public static function isCached(File $phpcsFile, $key, $id) */ public static function get(File $phpcsFile, $key, $id) { + if (self::$enabled === false) { + return null; + } + $fileName = $phpcsFile->getFilename(); $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; @@ -118,6 +138,10 @@ public static function get(File $phpcsFile, $key, $id) */ public static function getForFile(File $phpcsFile, $key) { + if (self::$enabled === false) { + return []; + } + $fileName = $phpcsFile->getFilename(); $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; @@ -147,6 +171,10 @@ public static function getForFile(File $phpcsFile, $key) */ public static function set(File $phpcsFile, $key, $id, $value) { + if (self::$enabled === false) { + return; + } + $fileName = $phpcsFile->getFilename(); $loop = $phpcsFile->fixer->enabled === true ? $phpcsFile->fixer->loops : 0; diff --git a/PHPCSUtils/Internal/NoFileCache.php b/PHPCSUtils/Internal/NoFileCache.php index 2fcf86ef..9844e011 100644 --- a/PHPCSUtils/Internal/NoFileCache.php +++ b/PHPCSUtils/Internal/NoFileCache.php @@ -45,6 +45,18 @@ final class NoFileCache { + /** + * Whether caching is enabled or not. + * + * Note: this switch is ONLY intended for use within test suites and should never + * be touched in any other circumstances! + * + * Don't forget to always turn the cache back on in a `tear_down()` method! + * + * @var bool + */ + public static $enabled = true; + /** * Results cache. * @@ -65,7 +77,7 @@ final class NoFileCache */ public static function isCached($key, $id) { - return isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key]); + return self::$enabled === true && isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key]); } /** @@ -81,7 +93,7 @@ public static function isCached($key, $id) */ public static function get($key, $id) { - if (isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key])) { + if (self::$enabled === true && isset(self::$cache[$key]) && \array_key_exists($id, self::$cache[$key])) { return self::$cache[$key][$id]; } @@ -98,7 +110,7 @@ public static function get($key, $id) */ public static function getForKey($key) { - if (\array_key_exists($key, self::$cache)) { + if (self::$enabled === true && \array_key_exists($key, self::$cache)) { return self::$cache[$key]; } @@ -119,6 +131,10 @@ public static function getForKey($key) */ public static function set($key, $id, $value) { + if (self::$enabled === false) { + return; + } + self::$cache[$key][$id] = $value; } diff --git a/Tests/Internal/Cache/GetClearTest.php b/Tests/Internal/Cache/GetClearTest.php index eb460d33..0a7cc8bc 100644 --- a/Tests/Internal/Cache/GetClearTest.php +++ b/Tests/Internal/Cache/GetClearTest.php @@ -108,6 +108,25 @@ public function testIsCachedWillReturnFalseForUnavailableId() $this->assertFalse(Cache::isCached(self::$phpcsFile, 'Utility1', 'this ID does not exist')); } + /** + * Test that disabling the cache will short-circuit cache checking. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseWhenCachingDisabled() + { + $origStatus = Cache::$enabled; + Cache::$enabled = false; + + $isCached = Cache::isCached(self::$phpcsFile, 'Utility1', 'numeric string'); + + Cache::$enabled = $origStatus; + + $this->assertFalse($isCached); + } + /** * Test that retrieving a cache key for a loop which has not been set, yields null. * @@ -146,6 +165,25 @@ public function testGetWillReturnNullForUnavailableId() $this->assertNull(Cache::get(self::$phpcsFile, 'Utility1', 'this ID does not exist')); } + /** + * Test that disabling the cache will short-circuit cache retrieval. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullWhenCachingDisabled() + { + $origStatus = Cache::$enabled; + Cache::$enabled = false; + + $retrieved = Cache::get(self::$phpcsFile, 'Utility1', 'numeric string'); + + Cache::$enabled = $origStatus; + + $this->assertNull($retrieved); + } + /** * Test that retrieving a cache set for a loop which has not been set, yields an empty array. * @@ -172,6 +210,25 @@ public function testGetForFileWillReturnEmptyArrayForUnavailableKey() $this->assertSame([], Cache::getForFile(self::$phpcsFile, 'Utility3')); } + /** + * Test that disabling the cache will short-circuit cache for file retrieval. + * + * @covers ::getForFile + * + * @return void + */ + public function testGetForFileWillReturnEmptyArrayWhenCachingDisabled() + { + $origStatus = Cache::$enabled; + Cache::$enabled = false; + + $retrieved = Cache::getForFile(self::$phpcsFile, 'Utility1'); + + Cache::$enabled = $origStatus; + + $this->assertSame([], $retrieved); + } + /** * Test that previously cached data can be retrieved correctly. * diff --git a/Tests/Internal/Cache/SetTest.php b/Tests/Internal/Cache/SetTest.php index 1620d907..4daa5506 100644 --- a/Tests/Internal/Cache/SetTest.php +++ b/Tests/Internal/Cache/SetTest.php @@ -187,6 +187,34 @@ public function testSetWillOverwriteExistingValue() ); } + /** + * Verify that disabling the cache will short-circuit caching (and not eat memory). + * + * @return void + */ + public function testSetWillNotSaveDataWhenCachingIsDisabled() + { + $id = 'id'; + $value = 'value'; + + $origStatus = Cache::$enabled; + Cache::$enabled = false; + + Cache::set(self::$phpcsFile, __METHOD__, $id, $value); + + Cache::$enabled = $origStatus; + + $this->assertFalse( + Cache::isCached(self::$phpcsFile, __METHOD__, $id), + 'Cache::isCached() found a cache which was set while caching was disabled' + ); + + $this->assertNull( + Cache::get(self::$phpcsFile, __METHOD__, $id), + 'Value retrieved via Cache::get() did not match expectations' + ); + } + /** * Test that previously cached data is no longer available if the fixer has moved on to the next loop. * diff --git a/Tests/Internal/NoFileCache/GetClearTest.php b/Tests/Internal/NoFileCache/GetClearTest.php index 7e9c582f..34e000ea 100644 --- a/Tests/Internal/NoFileCache/GetClearTest.php +++ b/Tests/Internal/NoFileCache/GetClearTest.php @@ -80,6 +80,25 @@ public function testIsCachedWillReturnFalseForUnavailableId() $this->assertFalse(NoFileCache::isCached('Utility1', 'this ID does not exist')); } + /** + * Test that disabling the cache will short-circuit cache checking. + * + * @covers ::isCached + * + * @return void + */ + public function testIsCachedWillReturnFalseWhenCachingDisabled() + { + $origStatus = NoFileCache::$enabled; + NoFileCache::$enabled = false; + + $isCached = NoFileCache::isCached('Utility1', 'numeric string'); + + NoFileCache::$enabled = $origStatus; + + $this->assertFalse($isCached); + } + /** * Test that retrieving a cache key which has not been set, yields null. * @@ -104,6 +123,25 @@ public function testGetWillReturnNullForUnavailableId() $this->assertNull(NoFileCache::get('Utility1', 'this ID does not exist')); } + /** + * Test that disabling the cache will short-circuit cache retrieval. + * + * @covers ::get + * + * @return void + */ + public function testGetWillReturnNullWhenCachingDisabled() + { + $origStatus = NoFileCache::$enabled; + NoFileCache::$enabled = false; + + $retrieved = NoFileCache::get('Utility1', 'numeric string'); + + NoFileCache::$enabled = $origStatus; + + $this->assertNull($retrieved); + } + /** * Test that retrieving a cache set for a cache key which has not been set, yields an empty array. * @@ -116,6 +154,25 @@ public function testGetForKeyWillReturnEmptyArrayForUnavailableData() $this->assertSame([], NoFileCache::getForKey('Utility3')); } + /** + * Test that disabling the cache will short-circuit cache for key retrieval. + * + * @covers ::getForKey + * + * @return void + */ + public function testGetForKeyWillReturnEmptyArrayWhenCachingDisabled() + { + $origStatus = NoFileCache::$enabled; + NoFileCache::$enabled = false; + + $retrieved = NoFileCache::getForKey('Utility1'); + + NoFileCache::$enabled = $origStatus; + + $this->assertSame([], $retrieved); + } + /** * Test that previously cached data can be retrieved correctly. * diff --git a/Tests/Internal/NoFileCache/SetTest.php b/Tests/Internal/NoFileCache/SetTest.php index 4e310e62..2d7a4001 100644 --- a/Tests/Internal/NoFileCache/SetTest.php +++ b/Tests/Internal/NoFileCache/SetTest.php @@ -171,4 +171,32 @@ public function testSetWillOverwriteExistingValue() 'New value retrieved via NoFileCache::get() did not match expectations' ); } + + /** + * Verify that disabling the cache will short-circuit caching (and not eat memory). + * + * @return void + */ + public function testSetWillNotSaveDataWhenCachingIsDisabled() + { + $id = 'id'; + $value = 'value'; + + $origStatus = NoFileCache::$enabled; + NoFileCache::$enabled = false; + + NoFileCache::set(__METHOD__, $id, $value); + + NoFileCache::$enabled = $origStatus; + + $this->assertFalse( + NoFileCache::isCached(__METHOD__, $id), + 'NoFileCache::isCached() found a cache which was set while caching was disabled' + ); + + $this->assertNull( + NoFileCache::get(__METHOD__, $id), + 'Value retrieved via NoFileCache::get() did not match expectations' + ); + } } From 34b61cd629b9ba93f7116df1637865ac88013b67 Mon Sep 17 00:00:00 2001 From: jrfnl Date: Tue, 28 Jun 2022 23:14:09 +0200 Subject: [PATCH 6/6] Tests: run the tests twice - once with cache, once without This commit: * Sets up a mechanism in the test `bootstrap.php` file to turn the cache on/off depending on an environment variable, which can be set from within a `phpunit.xml` file or on the OS-level. * Ensures that the `Cache` classes specific tests are always run first/in a separate test suite so the cache resetting done in those tests doesn't affect the test runs with caching turned on for functions which will _use_ caching. * Ensures that the `Cache` classes specific tests are always run with the cache turned on. * Adjusts the GH actions test workflows to run the complete test suite once with the cache turned on and once with the cache turned off. The test run with caching turned on will run the tests twice and only run the tests which don't need isolation. This should safeguard that the cache does not negatively influence sniff results. * For code coverage the tests will only be run with caching turned on (to maximize coverage). As all tests will be run with and without caching in the combined `quicktest` and `test` runs, this shouldn't be an issue. Note: the effect of caching on the test suite will be minimal as most tests will only execute a function once for a particular input/code snippet. Still good to safeguard/double-check though. Includes making two small adjustment to existing tests to: * Prevent "constant already defined notices" on repeated test runs. * Prevent a file from not being tokenized on repeated test runs. --- .github/workflows/quicktest.yml | 9 ++++- .github/workflows/test.yml | 16 ++++++-- Tests/Internal/Cache/GetClearTest.php | 29 +++++++++++++- Tests/Internal/Cache/SetTest.php | 29 +++++++++++++- Tests/Internal/NoFileCache/GetClearTest.php | 39 ++++++++++++++++++- Tests/Internal/NoFileCache/SetTest.php | 38 +++++++++++++++++- Tests/Tokens/TokenHelper/TokenExistsTest.php | 4 +- .../IsArrowFunction2926Test.php | 12 ++++++ Tests/bootstrap.php | 25 +++++++++++- composer.json | 1 + phpunit.xml.dist | 14 +++++++ 11 files changed, 205 insertions(+), 11 deletions(-) diff --git a/.github/workflows/quicktest.yml b/.github/workflows/quicktest.yml index 874a5cc8..4b118da4 100644 --- a/.github/workflows/quicktest.yml +++ b/.github/workflows/quicktest.yml @@ -74,7 +74,14 @@ jobs: if: matrix.phpcs_version == 'dev-master' run: composer lint - - name: Run the unit tests + - name: Run the unit tests without caching run: vendor/bin/phpunit --no-coverage env: PHPCS_VERSION: ${{ matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: false + + - name: Run the unit tests with caching + run: vendor/bin/phpunit --testsuite PHPCSUtils --no-coverage --repeat 2 + env: + PHPCS_VERSION: ${{ matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d73f2065..e6718147 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -189,11 +189,19 @@ jobs: with: composer-options: --ignore-platform-reqs - - name: Run the unit tests (non-risky) + - name: Run the unit tests without caching (non-risky) if: matrix.risky == false run: vendor/bin/phpunit --no-coverage env: PHPCS_VERSION: ${{ matrix.phpcs_version == '4.0.x-dev' && '4.0.0' || matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: false + + - name: Run the unit tests with caching (non-risky) + if: matrix.risky == false + run: vendor/bin/phpunit --testsuite PHPCSUtils --no-coverage --repeat 2 + env: + PHPCS_VERSION: ${{ matrix.phpcs_version == '4.0.x-dev' && '4.0.0' || matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: true - name: Run the unit tests (risky) if: ${{ matrix.risky }} @@ -281,17 +289,19 @@ jobs: if: ${{ steps.phpunit_version.outputs.VERSION >= '9.3' }} run: vendor/bin/phpunit --coverage-cache ./build/phpunit-cache --warm-coverage-cache - - name: "Run the unit tests with code coverage (PHPUnit < 9.3)" + - name: "Run the unit tests with caching with code coverage (PHPUnit < 9.3)" if: ${{ steps.phpunit_version.outputs.VERSION < '9.3' }} run: vendor/bin/phpunit env: PHPCS_VERSION: ${{ matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: true - - name: "Run the unit tests with code coverage (PHPUnit 9.3+)" + - name: "Run the unit tests with caching with code coverage (PHPUnit 9.3+)" if: ${{ steps.phpunit_version.outputs.VERSION >= '9.3' }} run: vendor/bin/phpunit --coverage-cache ./build/phpunit-cache env: PHPCS_VERSION: ${{ matrix.phpcs_version }} + PHPCSUTILS_USE_CACHE: true # Uploading the results with PHP Coveralls v1 won't work from GH Actions, so switch the PHP version. - name: Switch to PHP 7.4 diff --git a/Tests/Internal/Cache/GetClearTest.php b/Tests/Internal/Cache/GetClearTest.php index 0a7cc8bc..1e2337c4 100644 --- a/Tests/Internal/Cache/GetClearTest.php +++ b/Tests/Internal/Cache/GetClearTest.php @@ -26,6 +26,13 @@ class GetClearTest extends UtilityMethodTestCase { + /** + * Original value for whether or not caching is enabled. + * + * @var bool + */ + private static $origCacheEnabled; + /** * Initialize PHPCS & tokenize the test case file. * @@ -35,8 +42,16 @@ class GetClearTest extends UtilityMethodTestCase */ public static function setUpTestFile() { - Cache::clear(); self::$caseFile = \dirname(\dirname(__DIR__)) . '/DummyFile.inc'; + + /* + * Make sure caching is ALWAYS enabled for these tests and + * make sure these tests are run with a clear cache to start with. + */ + self::$origCacheEnabled = Cache::$enabled; + Cache::$enabled = true; + Cache::clear(); + parent::setUpTestFile(); } @@ -70,6 +85,18 @@ protected function clearCacheAndFixer() self::$phpcsFile->fixer->loops = 0; } + /** + * Reset the caching status. + * + * @afterClass + * + * @return void + */ + public static function resetCachingStatus() + { + Cache::$enabled = self::$origCacheEnabled; + } + /** * Test that a cache for a loop which has not been set is identified correctly as such. * diff --git a/Tests/Internal/Cache/SetTest.php b/Tests/Internal/Cache/SetTest.php index 4daa5506..9767c83b 100644 --- a/Tests/Internal/Cache/SetTest.php +++ b/Tests/Internal/Cache/SetTest.php @@ -28,6 +28,13 @@ class SetTest extends UtilityMethodTestCase { + /** + * Original value for whether or not caching is enabled. + * + * @var bool + */ + private static $origCacheEnabled; + /** * Initialize PHPCS & tokenize the test case file. * @@ -37,8 +44,16 @@ class SetTest extends UtilityMethodTestCase */ public static function setUpTestFile() { - Cache::clear(); self::$caseFile = \dirname(\dirname(__DIR__)) . '/DummyFile.inc'; + + /* + * Make sure caching is ALWAYS enabled for these tests and + * make sure these tests are run with a clear cache to start with. + */ + self::$origCacheEnabled = Cache::$enabled; + Cache::$enabled = true; + Cache::clear(); + parent::setUpTestFile(); } @@ -56,6 +71,18 @@ protected function clearCacheAndFixer() self::$phpcsFile->fixer->loops = 0; } + /** + * Reset the caching status. + * + * @afterClass + * + * @return void + */ + public static function resetCachingStatus() + { + Cache::$enabled = self::$origCacheEnabled; + } + /** * Test that every data type is accepted as a cachable value, including `null`, that the * `Cache::isCached()` function recognizes a set value correctly and that all values can be retrieved. diff --git a/Tests/Internal/NoFileCache/GetClearTest.php b/Tests/Internal/NoFileCache/GetClearTest.php index 34e000ea..c0e0ca0a 100644 --- a/Tests/Internal/NoFileCache/GetClearTest.php +++ b/Tests/Internal/NoFileCache/GetClearTest.php @@ -26,6 +26,31 @@ class GetClearTest extends TestCase { + /** + * Original value for whether or not caching is enabled. + * + * @var bool + */ + private static $origCacheEnabled; + + /** + * Enable caching. + * + * @beforeClass + * + * @return void + */ + public static function enableCache() + { + /* + * Make sure caching is ALWAYS enabled for these tests and + * make sure these tests are run with a clear cache to start with. + */ + self::$origCacheEnabled = NoFileCache::$enabled; + NoFileCache::$enabled = true; + NoFileCache::clear(); + } + /** * Fill the initial cache. * @@ -35,8 +60,6 @@ class GetClearTest extends TestCase */ protected function createCache() { - NoFileCache::clear(); - $data = TypeProviderHelper::getAll(); foreach ($data as $id => $dataset) { NoFileCache::set('Utility1', $id, $dataset['input']); @@ -56,6 +79,18 @@ protected function clearCache() NoFileCache::clear(); } + /** + * Reset the caching status. + * + * @afterClass + * + * @return void + */ + public static function resetCachingStatus() + { + NoFileCache::$enabled = self::$origCacheEnabled; + } + /** * Test that a cache key which has not been set is identified correctly as such. * diff --git a/Tests/Internal/NoFileCache/SetTest.php b/Tests/Internal/NoFileCache/SetTest.php index 2d7a4001..68c084c8 100644 --- a/Tests/Internal/NoFileCache/SetTest.php +++ b/Tests/Internal/NoFileCache/SetTest.php @@ -28,10 +28,34 @@ class SetTest extends TestCase { + /** + * Original value for whether or not caching is enabled. + * + * @var bool + */ + private static $origCacheEnabled; + + /** + * Enable caching. + * + * @beforeClass + * + * @return void + */ + public static function enableCache() + { + /* + * Make sure caching is ALWAYS enabled for these tests and + * make sure these tests are run with a clear cache to start with. + */ + self::$origCacheEnabled = NoFileCache::$enabled; + NoFileCache::$enabled = true; + NoFileCache::clear(); + } + /** * Clear the cache between tests. * - * @before * @after * * @return void @@ -41,6 +65,18 @@ protected function clearCache() NoFileCache::clear(); } + /** + * Reset the caching status. + * + * @afterClass + * + * @return void + */ + public static function resetCachingStatus() + { + NoFileCache::$enabled = self::$origCacheEnabled; + } + /** * Test that every data type is accepted as a cachable value, including `null`, that the * `NoFileCache::isCached()` function recognizes a set value correctly and that all values can be retrieved. diff --git a/Tests/Tokens/TokenHelper/TokenExistsTest.php b/Tests/Tokens/TokenHelper/TokenExistsTest.php index 139bd301..431814fb 100644 --- a/Tests/Tokens/TokenHelper/TokenExistsTest.php +++ b/Tests/Tokens/TokenHelper/TokenExistsTest.php @@ -33,7 +33,9 @@ class TokenExistsTest extends TestCase */ public static function setUpConstants() { - \define('T_FAKETOKEN', -5); + if (\defined('T_FAKETOKEN') === false) { + \define('T_FAKETOKEN', -5); + } } /** diff --git a/Tests/Utils/FunctionDeclarations/IsArrowFunction2926Test.php b/Tests/Utils/FunctionDeclarations/IsArrowFunction2926Test.php index c2c519cb..248675c9 100644 --- a/Tests/Utils/FunctionDeclarations/IsArrowFunction2926Test.php +++ b/Tests/Utils/FunctionDeclarations/IsArrowFunction2926Test.php @@ -94,6 +94,18 @@ public function setUpTestFileForReal() } } + /** + * Reset static properties to their default value after the tests have finished. + * + * @afterClass + * + * @return void + */ + public static function resetProperties() + { + self::$tokenized = false; + } + /** * Test correctly detecting arrow functions. * diff --git a/Tests/bootstrap.php b/Tests/bootstrap.php index 21d5439a..06589268 100644 --- a/Tests/bootstrap.php +++ b/Tests/bootstrap.php @@ -14,6 +14,9 @@ namespace PHPCSUtils\Tests; +use PHPCSUtils\Internal\Cache; +use PHPCSUtils\Internal\NoFileCache; + if (\defined('PHP_CODESNIFFER_IN_TESTS') === false) { \define('PHP_CODESNIFFER_IN_TESTS', true); } @@ -124,4 +127,24 @@ */ require_once \dirname(__DIR__) . '/phpcsutils-autoload.php'; -unset($phpcsDir, $vendorDir); +/* + * Determine whether to run the test suite with caching enabled or disabled. + * + * Use `` in a `phpunit.xml` file + * or set the ENV variable on an OS-level. + * + * If the ENV variable has not been set, the tests will run with caching turned OFF. + */ +if (\defined('PHPCSUTILS_USE_CACHE') === false) { + $useCache = \getenv('PHPCSUTILS_USE_CACHE'); + if ($useCache === false) { + Cache::$enabled = false; + NoFileCache::$enabled = false; + } else { + $useCache = \filter_var($useCache, \FILTER_VALIDATE_BOOLEAN); + Cache::$enabled = $useCache; + NoFileCache::$enabled = $useCache; + } +} + +unset($phpcsDir, $vendorDir, $useCache); diff --git a/composer.json b/composer.json index 6bc94b2a..912d14d9 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "dealerdirect/phpcodesniffer-composer-installer" : "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7" }, "require-dev" : { + "ext-filter": "*", "php-parallel-lint/php-parallel-lint": "^1.3.2", "php-parallel-lint/php-console-highlighter": "^1.0", "phpunit/phpunit": "^4.8.36 || ^5.7.21 || ^6.0 || ^7.0 || ^8.0 || ^9.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9fdd1338..14d42f00 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -16,6 +16,14 @@ + + ./Tests/Internal/Cache/GetClearTest.php + ./Tests/Internal/Cache/SetTest.php + ./Tests/Internal/NoFileCache/GetClearTest.php + ./Tests/Internal/NoFileCache/SetTest.php +