1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12use Twig\Cache\FilesystemCache;
13
14require_once \dirname(__DIR__).'/FilesystemHelper.php';
15
16class Twig_Tests_Cache_FilesystemTest extends \PHPUnit\Framework\TestCase
17{
18    private $classname;
19    private $directory;
20    private $cache;
21
22    protected function setUp()
23    {
24        $nonce = hash('sha256', uniqid(mt_rand(), true));
25        $this->classname = '__Twig_Tests_Cache_FilesystemTest_Template_'.$nonce;
26        $this->directory = sys_get_temp_dir().'/twig-test';
27        $this->cache = new FilesystemCache($this->directory);
28    }
29
30    protected function tearDown()
31    {
32        if (file_exists($this->directory)) {
33            Twig_Tests_FilesystemHelper::removeDir($this->directory);
34        }
35    }
36
37    public function testLoad()
38    {
39        $key = $this->directory.'/cache/cachefile.php';
40
41        $dir = \dirname($key);
42        @mkdir($dir, 0777, true);
43        $this->assertTrue(is_dir($dir));
44        $this->assertFalse(class_exists($this->classname, false));
45
46        $content = $this->generateSource();
47        file_put_contents($key, $content);
48
49        $this->cache->load($key);
50
51        $this->assertTrue(class_exists($this->classname, false));
52    }
53
54    public function testLoadMissing()
55    {
56        $key = $this->directory.'/cache/cachefile.php';
57
58        $this->assertFalse(class_exists($this->classname, false));
59
60        $this->cache->load($key);
61
62        $this->assertFalse(class_exists($this->classname, false));
63    }
64
65    public function testWrite()
66    {
67        $key = $this->directory.'/cache/cachefile.php';
68        $content = $this->generateSource();
69
70        $this->assertFileNotExists($key);
71        $this->assertFileNotExists($this->directory);
72
73        $this->cache->write($key, $content);
74
75        $this->assertFileExists($this->directory);
76        $this->assertFileExists($key);
77        $this->assertSame(file_get_contents($key), $content);
78    }
79
80    /**
81     * @expectedException \RuntimeException
82     * @expectedExceptionMessage Unable to create the cache directory
83     */
84    public function testWriteFailMkdir()
85    {
86        if (\defined('PHP_WINDOWS_VERSION_BUILD')) {
87            $this->markTestSkipped('Read-only directories not possible on Windows.');
88        }
89
90        $key = $this->directory.'/cache/cachefile.php';
91        $content = $this->generateSource();
92
93        $this->assertFileNotExists($key);
94
95        // Create read-only root directory.
96        @mkdir($this->directory, 0555, true);
97        $this->assertTrue(is_dir($this->directory));
98
99        $this->cache->write($key, $content);
100    }
101
102    /**
103     * @expectedException \RuntimeException
104     * @expectedExceptionMessage Unable to write in the cache directory
105     */
106    public function testWriteFailDirWritable()
107    {
108        if (\defined('PHP_WINDOWS_VERSION_BUILD')) {
109            $this->markTestSkipped('Read-only directories not possible on Windows.');
110        }
111
112        $key = $this->directory.'/cache/cachefile.php';
113        $content = $this->generateSource();
114
115        $this->assertFileNotExists($key);
116
117        // Create root directory.
118        @mkdir($this->directory, 0777, true);
119        // Create read-only subdirectory.
120        @mkdir($this->directory.'/cache', 0555);
121        $this->assertTrue(is_dir($this->directory.'/cache'));
122
123        $this->cache->write($key, $content);
124    }
125
126    /**
127     * @expectedException \RuntimeException
128     * @expectedExceptionMessage Failed to write cache file
129     */
130    public function testWriteFailWriteFile()
131    {
132        $key = $this->directory.'/cache/cachefile.php';
133        $content = $this->generateSource();
134
135        $this->assertFileNotExists($key);
136
137        // Create a directory in the place of the cache file.
138        @mkdir($key, 0777, true);
139        $this->assertTrue(is_dir($key));
140
141        $this->cache->write($key, $content);
142    }
143
144    public function testGetTimestamp()
145    {
146        $key = $this->directory.'/cache/cachefile.php';
147
148        $dir = \dirname($key);
149        @mkdir($dir, 0777, true);
150        $this->assertTrue(is_dir($dir));
151
152        // Create the file with a specific modification time.
153        touch($key, 1234567890);
154
155        $this->assertSame(1234567890, $this->cache->getTimestamp($key));
156    }
157
158    public function testGetTimestampMissingFile()
159    {
160        $key = $this->directory.'/cache/cachefile.php';
161        $this->assertSame(0, $this->cache->getTimestamp($key));
162    }
163
164    /**
165     * Test file cache is tolerant towards trailing (back)slashes on the configured cache directory.
166     *
167     * @dataProvider provideDirectories
168     */
169    public function testGenerateKey($expected, $input)
170    {
171        $cache = new FilesystemCache($input);
172        $this->assertRegExp($expected, $cache->generateKey('_test_', \get_class($this)));
173    }
174
175    public function provideDirectories()
176    {
177        $pattern = '#a/b/[a-zA-Z0-9]+/[a-zA-Z0-9]+.php$#';
178
179        return [
180            [$pattern, 'a/b'],
181            [$pattern, 'a/b/'],
182            [$pattern, 'a/b\\'],
183            [$pattern, 'a/b\\/'],
184            [$pattern, 'a/b\\//'],
185            ['#/'.substr($pattern, 1), '/a/b'],
186        ];
187    }
188
189    private function generateSource()
190    {
191        return strtr('<?php class {{classname}} {}', [
192            '{{classname}}' => $this->classname,
193        ]);
194    }
195}
196