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

E2EInstallerTest::createGenericArtifact()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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