Completed
Push — master ( 159829...0e7ac7 )
by Marco
02:53
created

FileCacheTest::testFileExtensionCorrectlyEscaped()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 1
eloc 17
nc 1
nop 0
1
<?php
2
3
namespace Doctrine\Tests\Common\Cache;
4
5
use Doctrine\Common\Cache\Cache;
6
use Doctrine\Common\Cache\FileCache;
7
use InvalidArgumentException;
8
9
/**
10
 * @group DCOM-101
11
 */
12
class FileCacheTest extends \Doctrine\Tests\DoctrineTestCase
13
{
14
    /**
15
     * @var \Doctrine\Common\Cache\FileCache
16
     */
17
    private $driver;
18
19
    protected function setUp()
20
    {
21
        $this->driver = $this
22
            ->getMockBuilder(FileCache::class)
23
            ->setMethods(['doFetch', 'doContains', 'doSave'])
24
            ->disableOriginalConstructor()
25
            ->getMock();
26
    }
27
28
    public function testFilenameShouldCreateThePathWithOneSubDirectory()
29
    {
30
        $cache          = $this->driver;
31
        $method         = new \ReflectionMethod($cache, 'getFilename');
32
        $key            = 'item-key';
33
        $expectedDir    = [
34
            '84',
35
        ];
36
        $expectedDir    = implode(DIRECTORY_SEPARATOR, $expectedDir);
37
38
        $method->setAccessible(true);
39
40
        $path       = $method->invoke($cache, $key);
41
        $dirname    = pathinfo($path, PATHINFO_DIRNAME);
42
43
        $this->assertEquals(DIRECTORY_SEPARATOR . $expectedDir, $dirname);
44
    }
45
46
    public function testFileExtensionCorrectlyEscaped()
47
    {
48
        $driver1 = $this
49
            ->getMockBuilder(FileCache::class)
50
            ->setMethods(['doFetch', 'doContains', 'doSave'])
51
            ->setConstructorArgs([__DIR__, '.*'])
52
            ->getMock();
53
54
        $driver2 = $this
55
            ->getMockBuilder(FileCache::class)
56
            ->setMethods(['doFetch', 'doContains', 'doSave'])
57
            ->setConstructorArgs([__DIR__, '.php'])
58
            ->getMock();
59
60
        $doGetStats = new \ReflectionMethod($driver1, 'doGetStats');
61
62
        $doGetStats->setAccessible(true);
63
64
        $stats1 = $doGetStats->invoke($driver1);
65
        $stats2 = $doGetStats->invoke($driver2);
66
67
        $this->assertSame(0, $stats1[Cache::STATS_MEMORY_USAGE]);
68
        $this->assertGreaterThan(0, $stats2[Cache::STATS_MEMORY_USAGE]);
69
    }
70
71
    /**
72
     * @group DCOM-266
73
     */
74
    public function testFileExtensionSlashCorrectlyEscaped()
75
    {
76
        $driver = $this
77
            ->getMockBuilder(FileCache::class)
78
            ->setMethods(['doFetch', 'doContains', 'doSave'])
79
            ->setConstructorArgs([__DIR__ . '/../', DIRECTORY_SEPARATOR . basename(__FILE__)])
80
            ->getMock();
81
82
        $doGetStats = new \ReflectionMethod($driver, 'doGetStats');
83
84
        $doGetStats->setAccessible(true);
85
86
        $stats = $doGetStats->invoke($driver);
87
88
        $this->assertGreaterThan(0, $stats[Cache::STATS_MEMORY_USAGE]);
89
    }
90
91
    public function testNonIntUmaskThrowsInvalidArgumentException()
92
    {
93
        $this->expectException(InvalidArgumentException::class);
94
95
        $this
96
            ->getMockBuilder(FileCache::class)
97
            ->setMethods(['doFetch', 'doContains', 'doSave'])
98
            ->setConstructorArgs(['', '', 'invalid'])
99
            ->getMock();
100
    }
101
102
    public function testGetDirectoryReturnsRealpathDirectoryString()
103
    {
104
        $directory = __DIR__ . '/../';
105
106
        $driver = $this
107
            ->getMockBuilder(FileCache::class)
108
            ->setMethods(['doFetch', 'doContains', 'doSave'])
109
            ->setConstructorArgs([$directory])
110
            ->getMock();
111
112
        $doGetDirectory = new \ReflectionMethod($driver, 'getDirectory');
113
114
        $actualDirectory = $doGetDirectory->invoke($driver);
115
        $expectedDirectory = realpath($directory);
116
117
        $this->assertEquals($expectedDirectory, $actualDirectory);
118
    }
119
120
    public function testGetExtensionReturnsExtensionString()
121
    {
122
        $directory = __DIR__ . '/../';
123
        $extension = DIRECTORY_SEPARATOR . basename(__FILE__);
124
125
        $driver = $this
126
            ->getMockBuilder(FileCache::class)
127
            ->setMethods(['doFetch', 'doContains', 'doSave'])
128
            ->setConstructorArgs([$directory, $extension])
129
            ->getMock();
130
131
        $doGetExtension = new \ReflectionMethod($driver, 'getExtension');
132
133
        $actualExtension = $doGetExtension->invoke($driver);
134
135
        $this->assertEquals($extension, $actualExtension);
136
    }
137
138
    const WIN_MAX_PATH_LEN = 258;
139
140
    public static function getBasePathForWindowsPathLengthTests($pathLength)
141
    {
142
        // Not using __DIR__ because it can get screwed up when xdebug debugger is attached.
143
        $basePath = realpath(sys_get_temp_dir()) . '/' . uniqid('doctrine-cache', true);
144
145
        /** @noinspection MkdirRaceConditionInspection */
146
        @mkdir($basePath);
147
148
        $basePath = realpath($basePath);
149
150
        // Test whether the desired path length is odd or even.
151
        $desiredPathLengthIsOdd = ($pathLength % 2) == 1;
152
153
        // If the cache key is not too long, the filecache codepath will add
154
        // a slash and bin2hex($key). The length of the added portion will be an odd number.
155
        // len(desired) = len(base path) + len(slash . bin2hex($key))
156
        //          odd = even           + odd
157
        //         even = odd            + odd
158
        $basePathLengthShouldBeOdd = !$desiredPathLengthIsOdd;
159
160
        $basePathLengthIsOdd = (strlen($basePath) % 2) == 1;
161
162
        // If the base path needs to be odd or even where it is not, we add an odd number of
163
        // characters as a pad. In this case, we're adding '\aa' (or '/aa' depending on platform)
164
        // This is all to make it so that the key we're testing would result in
165
        // a path that is exactly the length we want to test IF the path length limit
166
        // were not in place in FileCache.
167
        if ($basePathLengthIsOdd != $basePathLengthShouldBeOdd) {
168
            $basePath .= DIRECTORY_SEPARATOR . "aa";
169
        }
170
171
        return $basePath;
172
    }
173
174
    /**
175
     * @param int    $length
176
     * @param string $basePath
177
     *
178
     * @return array
179
     */
180
    public static function getKeyAndPathFittingLength($length, $basePath)
181
    {
182
        $baseDirLength = strlen($basePath);
183
        $extensionLength = strlen('.doctrine.cache');
184
        $directoryLength = strlen(DIRECTORY_SEPARATOR . 'aa' . DIRECTORY_SEPARATOR);
185
        $keyLength = $length - ($baseDirLength + $extensionLength + $directoryLength); // - 1 because of slash
186
187
        $key = str_repeat('a', floor($keyLength / 2));
188
189
        $keyHash = hash('sha256', $key);
190
191
        $keyPath = $basePath
192
            . DIRECTORY_SEPARATOR
193
            . substr($keyHash, 0, 2)
194
            . DIRECTORY_SEPARATOR
195
            . bin2hex($key)
196
            . '.doctrine.cache';
197
198
        $hashedKeyPath = $basePath
199
            . DIRECTORY_SEPARATOR
200
            . substr($keyHash, 0, 2)
201
            . DIRECTORY_SEPARATOR
202
            . '_' . $keyHash
203
            . '.doctrine.cache';
204
205
        return [$key, $keyPath, $hashedKeyPath];
206
    }
207
208
    public function getPathLengthsToTest()
209
    {
210
        // Windows officially supports 260 bytes including null terminator
211
        // 259 characters is too large due to PHP bug (https://bugs.php.net/bug.php?id=70943)
212
        // 260 characters is too large - null terminator is included in allowable length
213
        return [
214
            [257, false],
215
            [258, false],
216
            [259, true],
217
            [260, true]
218
        ];
219
    }
220
221
    /**
222
     * @runInSeparateProcess
223
     * @dataProvider getPathLengthsToTest
224
     *
225
     * @covers \Doctrine\Common\Cache\FileCache::getFilename
226
     *
227
     * @param int  $length
228
     * @param bool $pathShouldBeHashed
229
     */
230
    public function testWindowsPathLengthLimitationsAreCorrectlyRespected($length, $pathShouldBeHashed)
231
    {
232
        if (! defined('PHP_WINDOWS_VERSION_BUILD')) {
233
            define('PHP_WINDOWS_VERSION_BUILD', 'Yes, this is the "usual suspect", with the usual limitations');
234
        }
235
236
        $basePath = self::getBasePathForWindowsPathLengthTests($length);
237
238
        $fileCache = $this->getMockForAbstractClass(
239
            'Doctrine\Common\Cache\FileCache',
240
            [$basePath, '.doctrine.cache']
241
        );
242
243
        list($key, $keyPath, $hashedKeyPath) = self::getKeyAndPathFittingLength($length, $basePath);
244
245
        $getFileName = new \ReflectionMethod($fileCache, 'getFilename');
246
247
        $getFileName->setAccessible(true);
248
249
        $this->assertEquals(
250
            $length,
251
            strlen($keyPath),
252
            sprintf('Path expected to be %d characters long is %d characters long', $length, strlen($keyPath))
253
        );
254
255
        if ($pathShouldBeHashed) {
256
            $keyPath = $hashedKeyPath;
257
        }
258
259
        if ($pathShouldBeHashed) {
260
            $this->assertSame(
261
                $hashedKeyPath,
262
                $getFileName->invoke($fileCache, $key),
263
                'Keys should be hashed correctly if they are over the limit.'
264
            );
265
        } else {
266
            $this->assertSame(
267
                $keyPath,
268
                $getFileName->invoke($fileCache, $key),
269
                'Keys below limit of the allowed length are used directly, unhashed'
270
            );
271
        }
272
    }
273
}
274