Completed
Pull Request — master (#15)
by
unknown
09:39
created

FinderTest   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 398
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Importance

Changes 0
Metric Value
wmc 29
lcom 1
cbo 11
dl 0
loc 398
rs 10
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A setUp() 0 4 1
A tearDown() 0 4 1
A testGetMethod() 0 4 1
A testIfNotHiddenLetsSubpathsThrough() 0 10 1
A testIfDoubleNotHiddenLetsSubpathsThrough() 0 10 1
A testIfNeitherHiddenNorExtLetsSubpathsThrough() 0 19 1
A testIfNegatedOrCullsExactMatches() 0 28 1
A testIfNegatedAndCullsExactMatches() 0 32 1
B testIfCorrectFilesAreBeingYielded() 0 77 2
A testSubtreePruningOptimization() 0 46 1
A testGlobSubtreePruning() 0 43 1
A generatorToFileList() 0 6 1
B mockFileTree() 0 52 7
B mockListContents() 0 18 8
A mockFileSystem() 0 13 1
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of phpDocumentor.
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * @link      http://phpdoc.org
12
 */
13
14
namespace Flyfinder;
15
16
use Flyfinder\Specification\Glob;
17
use Flyfinder\Specification\HasExtension;
18
use Flyfinder\Specification\InPath;
19
use Flyfinder\Specification\IsHidden;
20
use League\Flysystem\Filesystem;
21
use League\Flysystem\FilesystemInterface;
22
use Mockery as m;
23
use PHPUnit\Framework\TestCase;
24
25
/**
26
 * Test case for Finder
27
 *
28
 * @coversDefaultClass Flyfinder\Finder
29
 */
30
class FinderTest extends TestCase
31
{
32
    /** @var Finder */
33
    private $fixture;
34
35
    /**
36
     * Initializes the fixture for this test.
37
     */
38
    public function setUp() : void
39
    {
40
        $this->fixture = new Finder();
41
    }
42
43
    public function tearDown() : void
44
    {
45
        m::close();
46
    }
47
48
    /**
49
     * @covers ::getMethod
50
     */
51
    public function testGetMethod() : void
52
    {
53
        $this->assertSame('find', $this->fixture->getMethod());
54
    }
55
56
    public function testIfNotHiddenLetsSubpathsThrough()
57
    {
58
        $files = [ 'foo/bar/.hidden/baz/not-hidden.txt' ];
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->setFilesystem($this->mockFileSystem($files));
71
        $notHidden = (new IsHidden())->notSpecification()->notSpecification();
72
        $this->assertEquals(
73
            $files,
74
            $this->generatorToFileList($this->fixture->handle($notHidden))
75
        );
76
    }
77
78
    public function testIfNeitherHiddenNorExtLetsSubpathsThrough()
79
    {
80
        $files = [ 'foo/bar/.hidden/baz.ext/neither-hidden-nor.ext.zzz' ];
81
        $this->fixture->setFilesystem($this->mockFileSystem($files));
82
83
        $neitherHiddenNorExt =
84
            (new IsHidden())->notSpecification()
85
                ->andSpecification((new HasExtension(['ext']))->notSpecification());
86
        $this->assertEquals(
87
            $files,
88
            $this->generatorToFileList($this->fixture->handle($neitherHiddenNorExt))
89
        );
90
91
        $neitherHiddenNorExtDeMorgan = (new IsHidden())->orSpecification(new HasExtension(['ext']))->notSpecification();
92
        $this->assertEquals(
93
            $files,
94
            $this->generatorToFileList($this->fixture->handle($neitherHiddenNorExtDeMorgan))
95
        );
96
    }
97
98
    public function testIfNegatedOrCullsExactMatches()
99
    {
100
        $files = [
101
            'foo/bar/baz/whatever.txt',
102
            'foo/gen/pics/bottle.jpg',
103
            'foo/lou/time.txt'
104
        ];
105
        $this->fixture->setFilesystem($this->mockFileSystem($files,['foo/bar','foo/gen']));
106
107
        $negatedOr =
108
            (new InPath(new Path("foo/gen")))
109
                ->orSpecification(new InPath(new Path("foo/bar")))
110
                ->notSpecification();
111
112
        $this->assertEquals(
113
            ['foo/lou/time.txt'],
114
            $this->generatorToFileList($this->fixture->handle($negatedOr))
115
        );
116
117
        $negatedOrDeMorgan =
118
            (new InPath(new Path("foo/gen")))->notSpecification()
119
            ->andSpecification((new InPath(new Path("foo/bar")))->notSpecification());
120
121
        $this->assertEquals(
122
            ['foo/lou/time.txt'],
123
            $this->generatorToFileList($this->fixture->handle($negatedOrDeMorgan))
124
        );
125
    }
126
127
    public function testIfNegatedAndCullsExactMatches()
128
    {
129
        $files = [
130
            'foo/bar/baz/whatever.txt',
131
            'foo/gen/pics/bottle.jpg',
132
            'foo/lou/time.txt'
133
        ];
134
        $expected = [
135
            'foo/gen/pics/bottle.jpg',
136
            'foo/lou/time.txt'
137
        ];
138
        $this->fixture->setFilesystem($this->mockFileSystem($files,['foo/bar']));
139
140
        $negatedAnd =
141
            (new InPath(new Path("foo/*")))
142
                ->andSpecification(new InPath(new Path("*/bar")))
143
                ->notSpecification();
144
145
        $this->assertEquals(
146
            $expected,
147
            $this->generatorToFileList($this->fixture->handle($negatedAnd))
148
        );
149
150
        $negatedAndDeMorgan =
151
            (new InPath(new Path("foo/*")))->notSpecification()
152
                ->orSpecification((new InPath(new Path("*/bar")))->notSpecification());
153
154
        $this->assertEquals(
155
            $expected,
156
            $this->generatorToFileList($this->fixture->handle($negatedAndDeMorgan))
157
        );
158
    }
159
160
161
    /**
162
     * @covers ::handle
163
     * @covers ::setFilesystem
164
     * @covers ::<private>
165
     */
166
    public function testIfCorrectFilesAreBeingYielded() : void
167
    {
168
        $isHidden   = m::mock(IsHidden::class);
169
        $filesystem = m::mock(Filesystem::class);
170
171
        $listContents1 = [
172
            0 => [
173
                'type' => 'dir',
174
                'path' => '.hiddendir',
175
                'dirname' => '',
176
                'basename' => '.hiddendir',
177
                'filename' => '.hiddendir',
178
            ],
179
            1 => [
180
                'type' => 'file',
181
                'path' => 'test.txt',
182
                'basename' => 'test.txt',
183
            ],
184
        ];
185
186
        $listContents2 = [
187
            0 => [
188
                'type' => 'file',
189
                'path' => '.hiddendir/.test.txt',
190
                'dirname' => '.hiddendir',
191
                'basename' => '.test.txt',
192
                'filename' => '.test',
193
                'extension' => 'txt',
194
            ],
195
        ];
196
197
        $filesystem->shouldReceive('listContents')
198
            ->with('')
199
            ->andReturn($listContents1);
200
201
        $filesystem->shouldReceive('listContents')
202
            ->with('.hiddendir')
203
            ->andReturn($listContents2);
204
205
        $isHidden->shouldReceive('isSatisfiedBy')
206
            ->with($listContents1[0])
207
            ->andReturn(true);
208
209
        $isHidden->shouldReceive('canBeSatisfiedBySomethingBelow')
210
            ->with($listContents1[0])
211
            ->andReturn(true);
212
213
        $isHidden->shouldReceive('isSatisfiedBy')
214
            ->with($listContents1[1])
215
            ->andReturn(false);
216
217
        $isHidden->shouldReceive('isSatisfiedBy')
218
            ->with($listContents2[0])
219
            ->andReturn(true);
220
221
        $this->fixture->setFilesystem($filesystem);
0 ignored issues
show
Documentation introduced by
$filesystem is of type object<Mockery\LegacyMockInterface>, but the function expects a object<League\Flysystem\FilesystemInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
222
        $generator = $this->fixture->handle($isHidden);
0 ignored issues
show
Documentation introduced by
$isHidden is of type object<Mockery\LegacyMockInterface>, but the function expects a object<Flyfinder\Specifi...SpecificationInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
224
        $result = [];
225
226
        foreach ($generator as $value) {
227
            $result[] = $value;
228
        }
229
230
        $expected = [
231
            0 => [
232
                'type' => 'file',
233
                'path' => '.hiddendir/.test.txt',
234
                'dirname' => '.hiddendir',
235
                'basename' => '.test.txt',
236
                'filename' => '.test',
237
                'extension' => 'txt',
238
            ],
239
        ];
240
241
        $this->assertSame($expected, $result);
242
    }
243
244
    public function testSubtreePruningOptimization()
245
    {
246
        $filesystem = $this->mockFileSystem(
247
            [
248
                'foo/bar/baz/file.txt',
249
                'foo/bar/baz/file2.txt',
250
                'foo/bar/baz/excluded/excluded.txt',
251
                'foo/bar/baz/excluded/culled/culled.txt',
252
                'foo/bar/baz/excluded/important/reincluded.txt',
253
                'foo/bar/file3.txt',
254
                'foo/lou/someSubdir/file4.txt',
255
                'foo/irrelevant1/',
256
                'irrelevant2/irrelevant3/irrelevantFile.txt'
257
            ],
258
            [
259
                'foo/irrelevant1',
260
                'irrelevant2',
261
                'foo/bar/baz/excluded/culled'
262
            ]
263
        );
264
265
        $inFooBar = new InPath(new Path("foo/bar"));
266
        $inFooLou = new InPath(new Path("foo/lou"));
267
        $inExcl = new InPath(new Path("foo/bar/baz/excl*"));
268
        $inReincl = new InPath(new Path("foo/bar/baz/*/important"));
269
        $spec =
270
            $inFooBar
271
                ->orSpecification($inFooLou)
272
                ->andSpecification($inExcl->notSpecification())
273
                ->orSpecification($inReincl);
274
275
        $finder = $this->fixture;
276
        $finder->setFilesystem($filesystem);
277
        $generator = $finder->handle($spec);
278
279
        $expected = [
280
            'foo/bar/baz/file.txt',
281
            'foo/bar/baz/file2.txt',
282
            'foo/bar/file3.txt',
283
            'foo/bar/baz/excluded/important/reincluded.txt',
284
            'foo/lou/someSubdir/file4.txt',
285
        ];
286
        sort($expected);
287
288
        $this->assertEquals($expected, $this->generatorToFileList($generator));
289
    }
290
291
    public function testGlobSubtreePruning()
292
    {
293
        $filesystem = $this->mockFileSystem(
294
            [
295
                'foo/bar/baz/file.txt',
296
                'foo/bar/baz/file2.txt',
297
                'foo/bar/baz/excluded/excluded.txt',
298
                'foo/bar/baz/excluded/culled/culled.txt',
299
                'foo/bar/baz/excluded/important/reincluded.txt',
300
                'foo/bar/file3.txt',
301
                'foo/lou/someSubdir/file4.txt',
302
                'foo/irrelevant1/',
303
                'irrelevant2/irrelevant3/irrelevantFile.txt'
304
            ],
305
            [
306
                'foo/irrelevant1',
307
                'irrelevant2',
308
                'foo/bar/baz/excluded/culled'
309
            ]
310
        );
311
        $txtInFooBar = new Glob('/foo/bar/**/*.txt');
312
        $inFooLou = new Glob("/foo/lou/**/*");
313
        $inExcl = new Glob("/foo/bar/baz/excl*/**/*");
314
        $inReincl = new Glob("/foo/bar/baz/*/important/**/*");
315
        $spec = $txtInFooBar
316
            ->orSpecification($inFooLou)
317
            ->andSpecification($inExcl->notSpecification())
318
            ->orSpecification($inReincl);
319
320
        $finder = $this->fixture;
321
        $finder->setFilesystem($filesystem);
322
        $generator = $finder->handle($spec);
323
        $expected = [
324
            'foo/bar/baz/file.txt',
325
            'foo/bar/baz/file2.txt',
326
            'foo/bar/file3.txt',
327
            'foo/bar/baz/excluded/important/reincluded.txt',
328
            'foo/lou/someSubdir/file4.txt',
329
        ];
330
        sort($expected);
331
332
        $this->assertEquals($expected, $this->generatorToFileList($generator));
333
    }
334
335
    protected function generatorToFileList(\Generator $generator) : array
336
    {
337
        $actual = array_values(array_map(function($v) {return $v['path']; }, iterator_to_array($generator)));
338
        sort($actual);
339
        return $actual;
340
    }
341
342
    protected function mockFileTree(array $pathList) : array
343
    {
344
        $result = [
345
            "." => [
346
                'type' => 'dir',
347
                'path' => '',
348
                'dirname' => '.',
349
                'basename' => '.',
350
                'filename' => '.',
351
                'contents' => []
352
            ]
353
        ];
354
        foreach($pathList as $path) {
355
356
            $isFile = "/" !== substr($path,-1);
357
            $child = null;
358
            while(true) {
359
                $info = pathinfo($path);
360
                if ($isFile) {
361
                    $isFile = false;
362
                    $result[$path] = [
363
                        'type' => 'file',
364
                        'path' => $path,
365
                        'dirname' => $info['dirname'],
366
                        'basename' => $info['basename'],
367
                        'filename' => $info['filename'],
368
                        'extension' => $info['extension']
369
                    ];
370
                }
371
                else {
372
                    $existed = true;
373
                    if (!isset($result[$path])) {
374
                        $existed = false;
375
                        $result[$path] = [
376
                            'type' => 'dir',
377
                            'path' => $path,
378
                            'basename' => $info['basename'],
379
                            'filename' => $info['filename'],
380
                            'contents' => []
381
                        ];
382
                    }
383
                    if (null!==$child) {
384
                        $result[$path]['contents'][] = $child;
385
                    }
386
                    if ($existed) break;
387
                }
388
                $child = $info['basename'];
389
                $path = $info['dirname'];
390
            }
391
        }
392
        return $result;
393
    }
394
395
    protected function mockListContents(array $fileTreeMock, string $path) : array
396
    {
397
        $path = trim($path,"/");
398
        if (substr($path."  ",0,2)==="./") $path = substr($path,2);
399
        if ($path==="") $path = ".";
400
401
        if (!isset($fileTreeMock[$path]) || 'file' === $fileTreeMock[$path]['type']) {
402
            return [];
403
        }
404
        $result = [];
405
        foreach($fileTreeMock[$path]['contents'] as $basename) {
406
            $childPath = ($path==="." ? "" : $path."/").$basename;
407
            if (isset($fileTreeMock[$childPath])) {
408
                $result[$basename] = $fileTreeMock[$childPath];
409
            }
410
        }
411
        return $result;
412
    }
413
414
    protected function mockFileSystem(array $paths, array $pathsThatShouldNotBeListed = []) : FilesystemInterface
415
    {
416
        $fsData = $this->mockFileTree($paths);
417
        $filesystem = m::mock(Filesystem::class);
418
        $filesystem->shouldReceive('listContents')
419
            ->zeroOrMoreTimes()
420
            ->andReturnUsing(function(string $path) use ($fsData, $pathsThatShouldNotBeListed) : array {
421
422
                $this->assertNotContains($path, $pathsThatShouldNotBeListed);
423
                return array_values($this->mockListContents($fsData, $path));
424
            });
425
        return $filesystem;
426
    }
427
}
428