Passed
Pull Request — master (#117)
by Pavel
01:55
created

E2EInstallerTest   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 379
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
eloc 184
c 5
b 0
f 0
dl 0
loc 379
rs 10
wmc 20

17 Methods

Rating   Name   Duplication   Size   Complexity  
A writeSymfonyPackageVersionUsingFile() 0 5 1
A setUp() 0 11 1
A execComposerInDir() 0 9 1
A tearDown() 0 7 1
A createGenericArtifact() 0 8 1
A testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() 0 27 1
A createPackageVersionsArtifact() 0 39 3
A createArtifact() 0 6 1
A testInstallingPluginWithNoScriptsOverridesOriginalRequirements() 0 46 1
A rmDir() 0 21 2
A testGloballyInstalledPluginDoesNotGenerateVersionsForLocalProject() 0 40 1
A assertSymfonyPackageVersionsIsUsable() 0 7 1
A testRemovingPluginWithNoDevDoesNotAttemptToGenerateVersions() 0 29 1
A writeComposerJsonFile() 0 5 1
A writePackageVersionUsingFile() 0 5 1
A assertPackageVersionsIsUsable() 0 7 1
A testRemovingPluginDoesNotAttemptToGenerateVersions() 0 32 1
1
<?php
2
3
declare(strict_types=1);
4
5
namespace PackageVersionsTest;
6
7
use PHPUnit\Framework\TestCase;
8
use RecursiveCallbackFilterIterator;
9
use RecursiveDirectoryIterator;
10
use RecursiveIteratorIterator;
11
use SplFileInfo;
12
use ZipArchive;
13
use function array_filter;
14
use function array_map;
15
use function array_walk;
16
use function chdir;
17
use function escapeshellarg;
18
use function exec;
19
use function file_get_contents;
20
use function file_put_contents;
21
use function getcwd;
22
use function implode;
23
use function in_array;
24
use function is_dir;
25
use function iterator_to_array;
26
use function json_decode;
27
use function json_encode;
28
use function mkdir;
29
use function putenv;
30
use function realpath;
31
use function rmdir;
32
use function scandir;
33
use function sprintf;
34
use function strlen;
35
use function substr;
36
use function sys_get_temp_dir;
37
use function uniqid;
38
use function unlink;
39
use const JSON_PRETTY_PRINT;
40
use const JSON_UNESCAPED_SLASHES;
41
use const PHP_BINARY;
42
use const PHP_EOL;
43
44
/**
45
 * @coversNothing
46
 */
47
class E2EInstallerTest extends TestCase
48
{
49
    private string $tempGlobalComposerHome;
50
51
    private string $tempLocalComposerHome;
52
53
    private string $tempArtifact;
54
55
    public function setUp() : void
56
    {
57
        $this->tempGlobalComposerHome = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/global';
58
        $this->tempLocalComposerHome  = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/local';
59
        $this->tempArtifact           = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/artifacts';
60
        mkdir($this->tempGlobalComposerHome, 0700, true);
61
        mkdir($this->tempLocalComposerHome, 0700, true);
62
        mkdir($this->tempArtifact, 0700, true);
63
64
        putenv('COMPOSER_HOME=' . $this->tempGlobalComposerHome);
65
        chdir(__DIR__);
66
    }
67
68
    public function tearDown() : void
69
    {
70
        $this->rmDir($this->tempGlobalComposerHome);
71
        $this->rmDir($this->tempLocalComposerHome);
72
        $this->rmDir($this->tempArtifact);
73
74
        putenv('COMPOSER_HOME');
75
    }
76
77
    public function testGloballyInstalledPluginDoesNotGenerateVersionsForLocalProject() : void
78
    {
79
        $this->createPackageVersionsArtifact();
80
81
        $this->writeComposerJsonFile(
82
            [
83
                'name'         => 'package-versions/e2e-global',
84
                'require'      => ['ocramius/package-versions' => '1.0.0'],
85
                'repositories' => [
86
                    ['packagist' => false],
87
                    [
88
                        'type' => 'artifact',
89
                        'url' => $this->tempArtifact,
90
                    ],
91
                ],
92
            ],
93
            $this->tempGlobalComposerHome
94
        );
95
96
        $this->execComposerInDir('global update', $this->tempGlobalComposerHome);
97
98
        $this->createArtifact();
99
        $this->writeComposerJsonFile(
100
            [
101
                'name'         => 'package-versions/e2e-local',
102
                'require'      => ['test/package' => '2.0.0'],
103
                'repositories' => [
104
                    ['packagist' => false],
105
                    [
106
                        'type' => 'artifact',
107
                        'url' => $this->tempArtifact,
108
                    ],
109
                ],
110
            ],
111
            $this->tempLocalComposerHome
112
        );
113
114
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
115
        $this->assertFileNotExists(
116
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
117
        );
118
    }
119
120
    public function testRemovingPluginDoesNotAttemptToGenerateVersions() : void
121
    {
122
        $this->createPackageVersionsArtifact();
123
        $this->createArtifact();
124
125
        $this->writeComposerJsonFile(
126
            [
127
                'name'         => 'package-versions/e2e-local',
128
                'require'      => [
129
                    'test/package' => '2.0.0',
130
                    'ocramius/package-versions' => '1.0.0',
131
                ],
132
                'repositories' => [
133
                    ['packagist' => false],
134
                    [
135
                        'type' => 'artifact',
136
                        'url' => $this->tempArtifact,
137
                    ],
138
                ],
139
            ],
140
            $this->tempLocalComposerHome
141
        );
142
143
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
144
        $this->assertFileExists(
145
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
146
        );
147
148
        $this->execComposerInDir('remove ocramius/package-versions', $this->tempLocalComposerHome);
149
150
        $this->assertFileNotExists(
151
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
152
        );
153
    }
154
155
    /**
156
     * @group #41
157
     * @group #46
158
     */
159
    public function testRemovingPluginWithNoDevDoesNotAttemptToGenerateVersions() : void
160
    {
161
        $this->createPackageVersionsArtifact();
162
        $this->createArtifact();
163
164
        $this->writeComposerJsonFile(
165
            [
166
                'name'         => 'package-versions/e2e-local',
167
                'require-dev'      => ['ocramius/package-versions' => '1.0.0'],
168
                'repositories' => [
169
                    ['packagist' => false],
170
                    [
171
                        'type' => 'artifact',
172
                        'url' => $this->tempArtifact,
173
                    ],
174
                ],
175
            ],
176
            $this->tempLocalComposerHome
177
        );
178
179
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
180
        $this->assertFileExists(
181
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
182
        );
183
184
        $this->execComposerInDir('install --no-dev', $this->tempLocalComposerHome);
185
186
        $this->assertFileNotExists(
187
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
188
        );
189
    }
190
191
    public function testInstallingPluginWithNoScriptsOverridesOriginalRequirements() : void
192
    {
193
        $this->createPackageVersionsArtifact();
194
        $this->createGenericArtifact(
195
            'infection-infection',
196
            '0.15.0',
197
            [
198
                'name' => 'infection/infection',
199
                'require' => ['symfony/process' => '^5.0'],
200
            ]
201
        );
202
        $this->createGenericArtifact(
203
            'symfony-process',
204
            '4.0.0',
205
            ['name' => 'symfony/process']
206
        );
207
        $this->createGenericArtifact(
208
            'symfony-process',
209
            '5.0.0',
210
            ['name' => 'symfony/process']
211
        );
212
        $this->writeComposerJsonFile(
213
            [
214
                'name'         => 'package-versions/e2e-transitive',
215
                'require'      => [
216
                    'ocramius/package-versions' => '1.0.0',
217
                    'infection/infection' => '^0.15.0',
218
                ],
219
                'repositories' => [
220
                    ['packagist' => false],
221
                    [
222
                        'type' => 'artifact',
223
                        'url' => $this->tempArtifact,
224
                    ],
225
                ],
226
            ],
227
            $this->tempLocalComposerHome
228
        );
229
230
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
231
        $this->assertFileExists(
232
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
233
        );
234
235
        $this->writeSymfonyPackageVersionUsingFile($this->tempLocalComposerHome);
236
        $this->assertSymfonyPackageVersionsIsUsable($this->tempLocalComposerHome);
237
    }
238
239
    /**
240
     * @group 101
241
     */
242
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
243
    {
244
        $this->createPackageVersionsArtifact();
245
        $this->createArtifact();
246
247
        $this->writeComposerJsonFile(
248
            [
249
                'name'         => 'package-versions/e2e-local',
250
                'require'      => ['ocramius/package-versions' => '1.0.0'],
251
                'repositories' => [
252
                    ['packagist' => false],
253
                    [
254
                        'type' => 'artifact',
255
                        'url' => $this->tempArtifact,
256
                    ],
257
                ],
258
            ],
259
            $this->tempLocalComposerHome
260
        );
261
262
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
263
        $this->assertFileExists(
264
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
265
        );
266
267
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
268
        $this->assertPackageVersionsIsUsable($this->tempLocalComposerHome);
269
    }
270
271
    private function createPackageVersionsArtifact() : void
272
    {
273
        $zip = new ZipArchive();
274
275
        $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE);
276
277
        $files = array_filter(
278
            iterator_to_array(new RecursiveIteratorIterator(
279
                new RecursiveCallbackFilterIterator(
280
                    new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS),
281
                    static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type string expected by parameter $callback of RecursiveCallbackFilterIterator::__construct(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

281
                    /** @scrutinizer ignore-type */ static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
Loading history...
282
                        return $iterator->getSubPathname()[0]  !== '.' && $iterator->getSubPathname() !== 'vendor';
283
                    }
284
                ),
285
                RecursiveIteratorIterator::LEAVES_ONLY
286
            )),
287
            static function (SplFileInfo $file) {
288
                return ! $file->isDir();
289
            }
290
        );
291
292
        array_walk(
293
            $files,
294
            static function (SplFileInfo $file) use ($zip) {
295
                if ($file->getFilename() === 'composer.json') {
296
                    $contents            = json_decode(file_get_contents($file->getRealPath()), true);
297
                    $contents['version'] = '1.0.0';
298
299
                    return $zip->addFromString('composer.json', json_encode($contents));
300
                }
301
302
                $zip->addFile(
303
                    $file->getRealPath(),
304
                    substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1)
305
                );
306
            }
307
        );
308
309
        $zip->close();
310
    }
311
312
    private function createArtifact() : void
313
    {
314
        $this->createGenericArtifact(
315
            'test-package',
316
            '2.0.0',
317
            ['name' => 'test/package']
318
        );
319
    }
320
321
    /**
322
     * @param mixed[] $composerJsonContent
323
     */
324
    private function createGenericArtifact(string $name, string $version, array $composerJsonContent) : void
325
    {
326
        $composerJsonContent['version'] = $version;
327
        $zip                            = new ZipArchive();
328
329
        $zip->open(sprintf('%s/%s-%s.zip', $this->tempArtifact, $name, $version), ZipArchive::CREATE);
330
        $zip->addFromString('composer.json', json_encode($composerJsonContent, JSON_PRETTY_PRINT));
331
        $zip->close();
332
    }
333
334
    /**
335
     * @param mixed[] $config
336
     */
337
    private function writeComposerJsonFile(array $config, string $directory) : void
338
    {
339
        file_put_contents(
340
            $directory . '/composer.json',
341
            json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
342
        );
343
    }
344
345
    private function writeSymfonyPackageVersionUsingFile(string $directory) : void
346
    {
347
        file_put_contents(
348
            $directory . '/use-package-versions.php',
349
            <<<'PHP'
350
<?php
351
352
require_once __DIR__ . '/vendor/autoload.php';
353
354
echo \PackageVersions\Versions::getVersion('symfony/process');
355
PHP
356
        );
357
    }
358
359
    private function assertSymfonyPackageVersionsIsUsable(string $directory) : void
360
    {
361
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
362
363
        self::assertSame(0, $exitCode);
364
        self::assertCount(1, $output);
365
        self::assertRegExp('/^v?5\\..*\\@[a-f0-9]*$/', $output[0]);
366
    }
367
368
    private function writePackageVersionUsingFile(string $directory) : void
369
    {
370
        file_put_contents(
371
            $directory . '/use-package-versions.php',
372
            <<<'PHP'
373
<?php
374
375
require_once __DIR__ . '/vendor/autoload.php';
376
377
echo \PackageVersions\Versions::getVersion('ocramius/package-versions');
378
PHP
379
        );
380
    }
381
382
    private function assertPackageVersionsIsUsable(string $directory) : void
383
    {
384
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
385
386
        self::assertSame(0, $exitCode);
387
        self::assertCount(1, $output);
388
        self::assertRegExp('/^1\\..*\\@[a-f0-9]*$/', $output[0]);
389
    }
390
391
    /**
392
     * @return mixed[]
393
     */
394
    private function execComposerInDir(string $command, string $dir) : array
395
    {
396
        $currentDir = getcwd();
397
        chdir($dir);
398
        exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' 2>&1', $output, $exitCode);
399
        $this->assertEquals(0, $exitCode, implode(PHP_EOL, $output));
400
        chdir($currentDir);
401
402
        return $output;
403
    }
404
405
    private function rmDir(string $directory) : void
406
    {
407
        if (! is_dir($directory)) {
408
            unlink($directory);
409
410
            return;
411
        }
412
413
        array_map(
414
            function ($item) use ($directory) : void {
415
                $this->rmDir($directory . '/' . $item);
416
            },
417
            array_filter(
418
                scandir($directory),
0 ignored issues
show
Bug introduced by
It seems like scandir($directory) can also be of type false; however, parameter $input of array_filter() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

418
                /** @scrutinizer ignore-type */ scandir($directory),
Loading history...
419
                static function (string $dirItem) {
420
                    return ! in_array($dirItem, ['.', '..'], true);
421
                }
422
            )
423
        );
424
425
        rmdir($directory);
426
    }
427
}
428