Completed
Pull Request — master (#15)
by
unknown
05:30
created

FinderTest::mockListContents()   B

Complexity

Conditions 8
Paths 24

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 8.4444
c 0
b 0
f 0
cc 8
nc 24
nop 2
1
<?php
2
/**
3
 * This file is part of phpDocumentor.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 *
8
 * @copyright 2010-2018 Mike van Riel<[email protected]>
9
 * @license   http://www.opensource.org/licenses/mit-license.php MIT
10
 * @link      http://phpdoc.org
11
 */
12
13
namespace Flyfinder;
14
15
use Flyfinder\Specification\HasExtension;
16
use Flyfinder\Specification\InPath;
17
use Flyfinder\Specification\IsHidden;
18
use League\Flysystem\Filesystem;
19
use League\Flysystem\FilesystemInterface;
20
use Mockery as m;
21
use PHPUnit\Framework\TestCase;
22
23
/**
24
 * Test case for Finder
25
 * @coversDefaultClass Flyfinder\Finder
26
 */
27
class FinderTest extends TestCase
28
{
29
    use TestsBothAlgorithms;
30
31
    /** @var Finder */
32
    private $fixture;
33
34
    /**
35
     * Initializes the fixture for this test.
36
     */
37
    public function setUp()
38
    {
39
        $this->fixture = new Finder();
40
    }
41
42
    public function tearDown()
43
    {
44
        m::close();
45
    }
46
47
    /**
48
     * @covers ::getMethod
49
     */
50
    public function testGetMethod()
51
    {
52
        $this->assertSame('find', $this->fixture->getMethod());
53
    }
54
55
    public function testIfNotHiddenLetsSubpathsThrough()
56
    {
57
        $files = [ 'foo/bar/.hidden/baz/not-hidden.txt' ];
58
        $this->fixture->setAlgorithm(Finder::ALGORITHM_OPTIMIZED);
59
        $this->fixture->setFilesystem($this->mockFileSystem($files));
60
        $notHidden = (new IsHidden())->notSpecification();
61
        $this->assertEquals(
62
            $files,
63
            $this->generatorToFileList($this->fixture->handle($notHidden))
64
        );
65
    }
66
67
    public function testIfDoubleNotHiddenLetsSubpathsThrough()
68
    {
69
        $files = [ '.foo/.bar/not-hidden/.baz/.hidden.txt' ];
70
        $this->fixture->setAlgorithm(Finder::ALGORITHM_OPTIMIZED);
71
        $this->fixture->setFilesystem($this->mockFileSystem($files));
72
        $notHidden = (new IsHidden())->notSpecification()->notSpecification();
73
        $this->assertEquals(
74
            $files,
75
            $this->generatorToFileList($this->fixture->handle($notHidden))
76
        );
77
    }
78
79
    public function testIfNeitherHiddenNorExtLetsSubpathsThrough()
80
    {
81
        $files = [ 'foo/bar/.hidden/baz.ext/neither-hidden-nor.ext.zzz' ];
82
        $this->fixture->setAlgorithm(Finder::ALGORITHM_OPTIMIZED);
83
        $this->fixture->setFilesystem($this->mockFileSystem($files));
84
85
        $neitherHiddenNorExt =
86
            (new IsHidden())->notSpecification()
87
                ->andSpecification((new HasExtension(['ext']))->notSpecification());
88
        $this->assertEquals(
89
            $files,
90
            $this->generatorToFileList($this->fixture->handle($neitherHiddenNorExt))
91
        );
92
93
        $neitherHiddenNorExtDeMorgan = (new IsHidden())->orSpecification(new HasExtension(['ext']))->notSpecification();
94
        $this->assertEquals(
95
            $files,
96
            $this->generatorToFileList($this->fixture->handle($neitherHiddenNorExtDeMorgan))
97
        );
98
    }
99
100
    /**
101
     * @covers ::handle
102
     * @covers ::setFilesystem
103
     * @covers ::<private>
104
     * @dataProvider algorithms
105
     * @param int $finderAlgorithm
106
     */
107
    public function testIfCorrectFilesAreBeingYielded(int $finderAlgorithm)
108
    {
109
        $this->fixture->setAlgorithm($finderAlgorithm);
110
111
        $isHidden = m::mock(IsHidden::class);
112
        $filesystem = m::mock(Filesystem::class);
113
114
        $listContents1 = [
115
            0 => [
116
                'type' => 'dir',
117
                'path' => '.hiddendir',
118
                'dirname' => '',
119
                'basename' => '.hiddendir',
120
                'filename' => '.hiddendir',
121
            ],
122
            1 => [
123
                'type' => 'file',
124
                'path' => 'test.txt',
125
                'basename' => 'test.txt',
126
            ],
127
        ];
128
129
        $listContents2 = [
130
            0 => [
131
                'type' => 'file',
132
                'path' => '.hiddendir/.test.txt',
133
                'dirname' => '.hiddendir',
134
                'basename' => '.test.txt',
135
                'filename' => '.test',
136
                'extension' => 'txt',
137
            ],
138
        ];
139
140
        $filesystem->shouldReceive('listContents')
141
            ->with('')
142
            ->andReturn($listContents1);
143
144
        $filesystem->shouldReceive('listContents')
145
            ->with('.hiddendir')
146
            ->andReturn($listContents2);
147
148
        $isHidden->shouldReceive('isSatisfiedBy')
149
            ->with($listContents1[0])
150
            ->andReturn(true);
151
152
        if (Finder::ALGORITHM_OPTIMIZED === $finderAlgorithm) {
153
            $isHidden->shouldReceive('canBeSatisfiedByAnythingBelow')
154
                ->with($listContents1[0])
155
                ->andReturn(true);
156
        }
157
158
        $isHidden->shouldReceive('isSatisfiedBy')
159
            ->with($listContents1[1])
160
            ->andReturn(false);
161
162
        $isHidden->shouldReceive('isSatisfiedBy')
163
            ->with($listContents2[0])
164
            ->andReturn(true);
165
166
        $this->fixture->setFilesystem($filesystem);
167
        $generator = $this->fixture->handle($isHidden);
168
169
        $result = [];
170
171
        foreach ($generator as $value) {
172
            $result[] = $value;
173
        }
174
175
        $expected = [
176
            0 => [
177
                'type' => 'file',
178
                'path' => '.hiddendir/.test.txt',
179
                'dirname' => '.hiddendir',
180
                'basename' => '.test.txt',
181
                'filename' => '.test',
182
                'extension' => 'txt',
183
            ],
184
        ];
185
186
        $this->assertSame($expected, $result);
187
    }
188
189
    /**
190
     * @param int $finderAlgorithm
191
     * @dataProvider algorithms
192
     */
193
    public function testSubtreeCullingOptimization(int $finderAlgorithm)
194
    {
195
        $this->fixture->setAlgorithm($finderAlgorithm);
196
197
        $filesystem = $this->mockFileSystem(
198
            [
199
                'foo/bar/baz/file.txt',
200
                'foo/bar/baz/file2.txt',
201
                'foo/bar/baz/excluded/excluded.txt',
202
                'foo/bar/baz/excluded/culled/culled.txt',
203
                'foo/bar/baz/excluded/important/reincluded.txt',
204
                'foo/bar/file3.txt',
205
                'foo/lou/someSubdir/file4.txt',
206
                'foo/irrelevant1/',
207
                'irrelevant2/irrelevant3/irrelevantFile.txt'
208
            ],
209
            Finder::ALGORITHM_OPTIMIZED === $finderAlgorithm
210
                ? [
211
                    'foo/irrelevant1',
212
                    'irrelevant2',
213
                    'foo/bar/baz/excluded/culled'
214
                ]
215
                : []
216
        );
217
218
        $inFooBar = new InPath(new Path("foo/bar"));
219
        $inFooLou = new InPath(new Path("foo/lou"));
220
        $inExcl = new InPath(new Path("foo/bar/baz/excl*"));
221
        $inReincl = new InPath(new Path("foo/bar/baz/*/important"));
222
        $spec =
223
            $inFooBar
224
                ->orSpecification($inFooLou)
225
                ->andSpecification($inExcl->notSpecification())
226
                ->orSpecification($inReincl);
227
228
        $finder = $this->fixture;
229
        $finder->setFilesystem($filesystem);
230
        $generator = $finder->handle($spec);
231
232
        $expected = [
233
            'foo/bar/baz/file.txt',
234
            'foo/bar/baz/file2.txt',
235
            'foo/bar/file3.txt',
236
            'foo/bar/baz/excluded/important/reincluded.txt',
237
            'foo/lou/someSubdir/file4.txt',
238
        ];
239
        sort($expected);
240
241
        $this->assertEquals($expected, $this->generatorToFileList($generator));
242
243
        $this->addToAssertionCount(1);
244
    }
245
246
    protected function generatorToFileList(\Generator $generator) : array
247
    {
248
        $actual = array_values(array_map(function($v) {return $v['path']; }, iterator_to_array($generator)));
249
        sort($actual);
250
        return $actual;
251
    }
252
253
    protected function mockFileTree(array $pathList) : array
254
    {
255
        $result = [
256
            "." => [
257
                'type' => 'dir',
258
                'path' => '',
259
                'dirname' => '.',
260
                'basename' => '.',
261
                'filename' => '.',
262
                'contents' => []
263
            ]
264
        ];
265
        foreach($pathList as $path) {
266
267
            $isFile = "/" !== substr($path,-1);
268
            $child = null;
269
            while(true) {
270
                $info = pathinfo($path);
271
                if ($isFile) {
272
                    $isFile = false;
273
                    $result[$path] = [
274
                        'type' => 'file',
275
                        'path' => $path,
276
                        'dirname' => $info['dirname'],
277
                        'basename' => $info['basename'],
278
                        'filename' => $info['filename'],
279
                        'extension' => $info['extension']
280
                    ];
281
                }
282
                else {
283
                    $existed = true;
284
                    if (!isset($result[$path])) {
285
                        $existed = false;
286
                        $result[$path] = [
287
                            'type' => 'dir',
288
                            'path' => $path,
289
                            'basename' => $info['basename'],
290
                            'filename' => $info['filename'],
291
                            'contents' => []
292
                        ];
293
                    }
294
                    if (null!==$child) {
295
                        $result[$path]['contents'][] = $child;
296
                    }
297
                    if ($existed) break;
298
                }
299
                $child = $info['basename'];
300
                $path = $info['dirname'];
301
            }
302
        }
303
        return $result;
304
    }
305
306
    protected function mockListContents(array $fileTreeMock, string $path) : array
307
    {
308
        $path = trim($path,"/");
309
        if (substr($path."  ",0,2)==="./") $path = substr($path,2);
310
        if ($path==="") $path = ".";
311
312
        if (!isset($fileTreeMock[$path]) || 'file' === $fileTreeMock[$path]['type']) {
313
            return [];
314
        }
315
        $result = [];
316
        foreach($fileTreeMock[$path]['contents'] as $basename) {
317
            $childPath = ($path==="." ? "" : $path."/").$basename;
318
            if (isset($fileTreeMock[$childPath])) {
319
                $result[$basename] = $fileTreeMock[$childPath];
320
            }
321
        }
322
        return $result;
323
    }
324
325
    protected function mockFileSystem(array $paths, array $pathsThatShouldNotBeListed = []) : FilesystemInterface
326
    {
327
        $fsData = $this->mockFileTree($paths);
328
        $filesystem = m::mock(Filesystem::class);
329
        $filesystem->shouldReceive('listContents')
330
            ->zeroOrMoreTimes()
331
            ->andReturnUsing(function(string $path) use ($fsData, $pathsThatShouldNotBeListed) : array {
332
333
                $this->assertNotContains($path, $pathsThatShouldNotBeListed);
334
                return array_values($this->mockListContents($fsData, $path));
335
            });
336
        return $filesystem;
337
    }
338
}
339