Completed
Pull Request — master (#117)
by Pavel
25:18
created

writeSymfonyPackageVersionUsingFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 13
rs 9.8333
c 0
b 0
f 0
cc 1
nc 1
nop 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 const JSON_PRETTY_PRINT;
14
use const JSON_UNESCAPED_SLASHES;
15
use const PHP_BINARY;
16
use function array_filter;
17
use function array_map;
18
use function array_walk;
19
use function chdir;
20
use function escapeshellarg;
21
use function exec;
22
use function file_get_contents;
23
use function file_put_contents;
24
use function getcwd;
25
use function in_array;
26
use function is_dir;
27
use function iterator_to_array;
28
use function json_decode;
29
use function json_encode;
30
use function mkdir;
31
use function putenv;
32
use function realpath;
33
use function rmdir;
34
use function scandir;
35
use function strlen;
36
use function substr;
37
use function sys_get_temp_dir;
38
use function uniqid;
39
use function unlink;
40
41
/**
42
 * @coversNothing
43
 */
44
class E2EInstallerTest extends TestCase
45
{
46
    /** @var string */
47
    private $tempGlobalComposerHome;
48
49
    /** @var string */
50
    private $tempLocalComposerHome;
51
52
    /** @var string */
53
    private $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' => [
199
                    'symfony/process' => '^5.0',
200
                ],
201
            ]
202
        );
203
        $this->createGenericArtifact(
204
            'symfony-process',
205
            '4.0.0',
206
            [
207
                'name' => 'symfony/process',
208
            ]
209
        );
210
        $this->createGenericArtifact(
211
            'symfony-process',
212
            '5.0.0',
213
            [
214
                'name' => 'symfony/process',
215
            ]
216
        );
217
        $this->writeComposerJsonFile(
218
            [
219
                'name'         => 'package-versions/e2e-transitive',
220
                'require'      => [
221
                    'ocramius/package-versions' => '1.0.0',
222
                    'infection/infection' => '^0.15.0',
223
                ],
224
                'repositories' => [
225
                    ['packagist' => false],
226
                    [
227
                        'type' => 'artifact',
228
                        'url' => $this->tempArtifact,
229
                    ],
230
                ],
231
            ],
232
            $this->tempLocalComposerHome
233
        );
234
235
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
236
        $this->assertFileExists(
237
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
238
        );
239
240
        $this->writeSymfonyPackageVersionUsingFile($this->tempLocalComposerHome);
241
        $this->assertSymfonyPackageVersionsIsUsable($this->tempLocalComposerHome);
242
    }
243
244
    /**
245
     * @group 101
246
     */
247
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
248
    {
249
        $this->createPackageVersionsArtifact();
250
        $this->createArtifact();
251
252
        $this->writeComposerJsonFile(
253
            [
254
                'name'         => 'package-versions/e2e-local',
255
                'require'      => ['ocramius/package-versions' => '1.0.0'],
256
                'repositories' => [
257
                    ['packagist' => false],
258
                    [
259
                        'type' => 'artifact',
260
                        'url' => $this->tempArtifact,
261
                    ],
262
                ],
263
            ],
264
            $this->tempLocalComposerHome
265
        );
266
267
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
268
        $this->assertFileExists(
269
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
270
        );
271
272
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
273
        $this->assertPackageVersionsIsUsable($this->tempLocalComposerHome);
274
    }
275
276
    private function createPackageVersionsArtifact() : void
277
    {
278
        $zip = new ZipArchive();
279
280
        $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE);
281
282
        $files = array_filter(
283
            iterator_to_array(new RecursiveIteratorIterator(
284
                new RecursiveCallbackFilterIterator(
285
                    new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS),
286
                    static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
287
                        return $iterator->getSubPathname()[0]  !== '.' && $iterator->getSubPathname() !== 'vendor';
288
                    }
289
                ),
290
                RecursiveIteratorIterator::LEAVES_ONLY
291
            )),
292
            static function (SplFileInfo $file) {
293
                return ! $file->isDir();
294
            }
295
        );
296
297
        array_walk(
298
            $files,
299
            static function (SplFileInfo $file) use ($zip) {
300
                if ($file->getFilename() === 'composer.json') {
301
                    $contents            = json_decode(file_get_contents($file->getRealPath()), true);
302
                    $contents['version'] = '1.0.0';
303
304
                    return $zip->addFromString('composer.json', json_encode($contents));
305
                }
306
307
                $zip->addFile(
308
                    $file->getRealPath(),
309
                    substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1)
310
                );
311
            }
312
        );
313
314
        $zip->close();
315
    }
316
317
    private function createArtifact() : void
318
    {
319
        $this->createGenericArtifact(
320
            'test-package',
321
            '2.0.0',
322
            [
323
                'name' => 'test/package',
324
            ]
325
        );
326
    }
327
328
    /**
329
     * @param mixed[] $composerJsonContent
330
     */
331
    private function createGenericArtifact(string $name, string $version, array $composerJsonContent): void
332
    {
333
        $composerJsonContent['version'] = $version;
334
        $zip = new ZipArchive();
335
336
        $zip->open(sprintf('%s/%s-%s.zip', $this->tempArtifact , $name, $version), ZipArchive::CREATE);
337
        $zip->addFromString('composer.json', json_encode($composerJsonContent, JSON_PRETTY_PRINT));
338
        $zip->close();
339
    }
340
341
    /**
342
     * @param mixed[] $config
343
     */
344
    private function writeComposerJsonFile(array $config, string $directory) : void
345
    {
346
        file_put_contents(
347
            $directory . '/composer.json',
348
            json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
349
        );
350
    }
351
352
    private function writeSymfonyPackageVersionUsingFile(string $directory) : void
353
    {
354
        file_put_contents(
355
            $directory . '/use-package-versions.php',
356
            <<<'PHP'
357
<?php
358
359
require_once __DIR__ . '/vendor/autoload.php';
360
361
echo \PackageVersions\Versions::getVersion('symfony/process');
362
PHP
363
        );
364
    }
365
366
    private function assertSymfonyPackageVersionsIsUsable(string $directory) : void
367
    {
368
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
369
370
        self::assertSame(0, $exitCode);
371
        self::assertCount(1, $output);
0 ignored issues
show
Documentation introduced by
$output is of type null|array, but the function expects a object<Countable>|object...nit\Framework\iterable>.

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...
372
        self::assertRegExp('/^v?5\\..*\\@[a-f0-9]*$/', $output[0]);
373
    }
374
375
    private function writePackageVersionUsingFile(string $directory) : void
376
    {
377
        file_put_contents(
378
            $directory . '/use-package-versions.php',
379
            <<<'PHP'
380
<?php
381
382
require_once __DIR__ . '/vendor/autoload.php';
383
384
echo \PackageVersions\Versions::getVersion('ocramius/package-versions');
385
PHP
386
        );
387
    }
388
389
    private function assertPackageVersionsIsUsable(string $directory) : void
390
    {
391
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
392
393
        self::assertSame(0, $exitCode);
394
        self::assertCount(1, $output);
0 ignored issues
show
Documentation introduced by
$output is of type null|array, but the function expects a object<Countable>|object...nit\Framework\iterable>.

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...
395
        self::assertRegExp('/^1\\..*\\@[a-f0-9]*$/', $output[0]);
396
    }
397
398
    /**
399
     * @return mixed[]
400
     */
401
    private function execComposerInDir(string $command, string $dir) : array
402
    {
403
        $currentDir = getcwd();
404
        chdir($dir);
405
        exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' 2>&1', $output, $exitCode);
406
        $this->assertEquals(0, $exitCode, implode(PHP_EOL, $output));
407
        chdir($currentDir);
408
409
        return $output;
410
    }
411
412
    private function rmDir(string $directory) : void
413
    {
414
        if (! is_dir($directory)) {
415
            unlink($directory);
416
417
            return;
418
        }
419
420
        array_map(
421
            function ($item) use ($directory) : void {
422
                $this->rmDir($directory . '/' . $item);
423
            },
424
            array_filter(
425
                scandir($directory),
426
                static function (string $dirItem) {
427
                    return ! in_array($dirItem, ['.', '..'], true);
428
                }
429
            )
430
        );
431
432
        rmdir($directory);
433
    }
434
}
435