Files
Twig/tests/Cache/FilesystemTest.php
T

261 lines
7.7 KiB
PHP

<?php
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Twig\Tests\Cache;
/*
* This file is part of Twig.
*
* (c) Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use Twig\Cache\FilesystemCache;
use Twig\Tests\FilesystemHelper;
class FilesystemTest extends TestCase
{
private $className;
private $directory;
private $cache;
protected function setUp(): void
{
$nonce = hash(\PHP_VERSION_ID < 80100 ? 'sha256' : 'xxh128', random_bytes(32));
$this->className = '__Twig_Tests_Cache_FilesystemTest_Template_'.$nonce;
$this->directory = sys_get_temp_dir().'/twig-test';
$this->cache = new FilesystemCache($this->directory);
}
protected function tearDown(): void
{
if (file_exists($this->directory)) {
FilesystemHelper::removeDir($this->directory);
}
}
public function testLoad(): void
{
$key = $this->directory.'/cache/cachefile.php';
$dir = \dirname($key);
@mkdir($dir, 0777, true);
$this->assertDirectoryExists($dir);
$this->assertFalse(class_exists($this->className, false));
$content = $this->generateSource();
file_put_contents($key, $content);
$this->cache->load($key);
$this->assertTrue(class_exists($this->className, false));
}
public function testLoadMissing(): void
{
$key = $this->directory.'/cache/cachefile.php';
$this->assertFalse(class_exists($this->className, false));
$this->cache->load($key);
$this->assertFalse(class_exists($this->className, false));
}
public function testLoadSkipsTheFileCheckWhenOpcacheHasTheFile(): void
{
$this->assertSame('loaded', $this->loadInOpcacheProcess(0, true));
}
public function testLoadChecksTheFileWhenBytecodeInvalidationIsForced(): void
{
$this->assertSame('', $this->loadInOpcacheProcess(FilesystemCache::FORCE_BYTECODE_INVALIDATION, true));
}
public function testLoadDoesNotWarnWhenTheOpcacheApiIsRestricted(): void
{
$this->assertSame('loaded', $this->loadInOpcacheProcess(0, false, '/nonexistent'));
}
public function testWrite(): void
{
$key = $this->directory.'/cache/cachefile.php';
$content = $this->generateSource();
$this->assertFileDoesNotExist($key);
$this->assertFileDoesNotExist($this->directory);
$this->cache->write($key, $content);
$this->assertFileExists($this->directory);
$this->assertFileExists($key);
$this->assertSame(file_get_contents($key), $content);
}
public function testWriteFailMkdir(): void
{
if (\defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Read-only directories not possible on Windows.');
}
$key = $this->directory.'/cache/cachefile.php';
$content = $this->generateSource();
$this->assertFileDoesNotExist($key);
// Create read-only root directory.
@mkdir($this->directory, 0555, true);
$this->assertDirectoryExists($this->directory);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Unable to create the cache directory');
$this->cache->write($key, $content);
}
public function testWriteFailDirWritable(): void
{
if (\defined('PHP_WINDOWS_VERSION_BUILD')) {
$this->markTestSkipped('Read-only directories not possible on Windows.');
}
$key = $this->directory.'/cache/cachefile.php';
$content = $this->generateSource();
$this->assertFileDoesNotExist($key);
// Create root directory.
@mkdir($this->directory, 0777, true);
// Create read-only subdirectory.
@mkdir($this->directory.'/cache', 0555);
$this->assertDirectoryExists($this->directory.'/cache');
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Unable to write in the cache directory');
$this->cache->write($key, $content);
}
public function testWriteFailWriteFile(): void
{
$key = $this->directory.'/cache/cachefile.php';
$content = $this->generateSource();
$this->assertFileDoesNotExist($key);
// Create a directory in the place of the cache file.
@mkdir($key, 0777, true);
$this->assertDirectoryExists($key);
$this->expectException(\RuntimeException::class);
$this->expectExceptionMessage('Failed to write cache file');
$this->cache->write($key, $content);
}
public function testGetTimestamp(): void
{
$key = $this->directory.'/cache/cachefile.php';
$dir = \dirname($key);
@mkdir($dir, 0777, true);
$this->assertDirectoryExists($dir);
// Create the file with a specific modification time.
touch($key, 1234567890);
$this->assertSame(1234567890, $this->cache->getTimestamp($key));
}
public function testGetTimestampMissingFile(): void
{
$key = $this->directory.'/cache/cachefile.php';
$this->assertSame(0, $this->cache->getTimestamp($key));
}
/**
* Test file cache is tolerant towards trailing (back)slashes on the configured cache directory.
*
* @dataProvider provideDirectories
*/
#[DataProvider('provideDirectories')]
public function testGenerateKey($expected, $input): void
{
$cache = new FilesystemCache($input);
$this->assertMatchesRegularExpression($expected, $cache->generateKey('_test_', static::class));
}
public static function provideDirectories()
{
$pattern = '#a/b/[a-zA-Z0-9]+/[a-zA-Z0-9]+.php$#';
return [
[$pattern, 'a/b'],
[$pattern, 'a/b/'],
[$pattern, 'a/b\\'],
[$pattern, 'a/b\\/'],
[$pattern, 'a/b\\//'],
['#/'.substr($pattern, 1), '/a/b'],
];
}
private function generateSource()
{
return strtr('<?php class {{class_name}} {}', [
'{{class_name}}' => $this->className,
]);
}
private function loadInOpcacheProcess(int $options, bool $removeCachedFile, string $restrictApi = ''): string
{
if (!\function_exists('opcache_compile_file')) {
$this->markTestSkipped('OPcache is not loaded.');
}
$code = <<<'PHP'
require $argv[1];
$cache = new Twig\Cache\FilesystemCache($argv[2], (int) $argv[3]);
$key = $cache->generateKey('index', 'Foo');
$cache->write($key, '<?php echo "loaded";');
if ($argv[4]) {
opcache_compile_file($key);
unlink($key);
}
$cache->load($key);
PHP;
$process = proc_open([
\PHP_BINARY,
'-d', 'opcache.enable=1',
'-d', 'opcache.enable_cli=1',
'-d', 'opcache.file_update_protection=0',
'-d', 'opcache.validate_timestamps=0',
'-d', 'opcache.restrict_api='.$restrictApi,
'-d', 'display_errors=1',
'-d', 'error_reporting=-1',
'-r', $code,
\dirname(__DIR__, 2).'/vendor/autoload.php',
$this->directory,
(string) $options,
$removeCachedFile ? '1' : '0',
], [1 => ['pipe', 'w'], 2 => ['redirect', 1]], $pipes);
$output = stream_get_contents($pipes[1]);
proc_close($process);
return $output;
}
}