Completed
Pull Request — master (#117)
by Pavel
23:46
created

testInstallingPluginWithNoScriptsOverridesOriginalRequirements()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 44
rs 9.216
c 0
b 0
f 0
cc 2
nc 2
nop 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 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
194
        $this->writeComposerJsonFile(
195
            [
196
                'name'         => 'package-versions/e2e-transitive',
197
                'require'      => [
198
                    'ocramius/package-versions' => '1.0.0',
199
                    'infection/infection' => '^0.15.0',
200
                ],
201
                'repositories' => [
202
                    ['packagist' => false],
203
                    [
204
                        'type' => 'artifact',
205
                        'url' => $this->tempArtifact,
206
                    ],
207
                    [
208
                        'type' => 'vcs',
209
                        'url' => 'https://github.com/scaytrase/package-versions-main-stub.git',
210
                    ],
211
                    [
212
                        'type' => 'vcs',
213
                        'url' => 'https://github.com/scaytrase/package-versions-sec-stub.git',
214
                    ],
215
                ],
216
            ],
217
            $this->tempLocalComposerHome
218
        );
219
220
        if (getenv('GH_TOKEN')) {
221
            $this->execComposerInDir(
222
                'config -g github-oauth.github.com ' . getenv('GH_TOKEN'),
223
                $this->tempLocalComposerHome
224
            );
225
        }
226
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
227
        $this->assertFileExists(
228
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
229
        );
230
231
        $this->writeSymfonyPackageVersionUsingFile($this->tempLocalComposerHome);
232
        $this->assertSymfonyPackageVersionsIsUsable($this->tempLocalComposerHome);
233
    }
234
235
    /**
236
     * @group 101
237
     */
238
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
239
    {
240
        $this->createPackageVersionsArtifact();
241
        $this->createArtifact();
242
243
        $this->writeComposerJsonFile(
244
            [
245
                'name'         => 'package-versions/e2e-local',
246
                'require'      => ['ocramius/package-versions' => '1.0.0'],
247
                'repositories' => [
248
                    ['packagist' => false],
249
                    [
250
                        'type' => 'artifact',
251
                        'url' => $this->tempArtifact,
252
                    ],
253
                ],
254
            ],
255
            $this->tempLocalComposerHome
256
        );
257
258
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
259
        $this->assertFileExists(
260
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
261
        );
262
263
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
264
        $this->assertPackageVersionsIsUsable($this->tempLocalComposerHome);
265
    }
266
267
    private function createPackageVersionsArtifact() : void
268
    {
269
        $zip = new ZipArchive();
270
271
        $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE);
272
273
        $files = array_filter(
274
            iterator_to_array(new RecursiveIteratorIterator(
275
                new RecursiveCallbackFilterIterator(
276
                    new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS),
277
                    static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
278
                        return $iterator->getSubPathname()[0]  !== '.' && $iterator->getSubPathname() !== 'vendor';
279
                    }
280
                ),
281
                RecursiveIteratorIterator::LEAVES_ONLY
282
            )),
283
            static function (SplFileInfo $file) {
284
                return ! $file->isDir();
285
            }
286
        );
287
288
        array_walk(
289
            $files,
290
            static function (SplFileInfo $file) use ($zip) {
291
                if ($file->getFilename() === 'composer.json') {
292
                    $contents            = json_decode(file_get_contents($file->getRealPath()), true);
293
                    $contents['version'] = '1.0.0';
294
295
                    return $zip->addFromString('composer.json', json_encode($contents));
296
                }
297
298
                $zip->addFile(
299
                    $file->getRealPath(),
300
                    substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1)
301
                );
302
            }
303
        );
304
305
        $zip->close();
306
    }
307
308
    private function createArtifact() : void
309
    {
310
        $zip = new ZipArchive();
311
312
        $zip->open($this->tempArtifact . '/test-package-2.0.0.zip', ZipArchive::CREATE);
313
        $zip->addFromString(
314
            'composer.json',
315
            json_encode(
316
                [
317
                    'name'    => 'test/package',
318
                    'version' => '2.0.0',
319
                ],
320
                JSON_PRETTY_PRINT
321
            )
322
        );
323
        $zip->close();
324
    }
325
326
    /**
327
     * @param mixed[] $config
328
     */
329
    private function writeComposerJsonFile(array $config, string $directory) : void
330
    {
331
        file_put_contents(
332
            $directory . '/composer.json',
333
            json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
334
        );
335
    }
336
337
    private function writeSymfonyPackageVersionUsingFile(string $directory) : void
338
    {
339
        file_put_contents(
340
            $directory . '/use-package-versions.php',
341
            <<<'PHP'
342
<?php
343
344
require_once __DIR__ . '/vendor/autoload.php';
345
346
echo \PackageVersions\Versions::getVersion('symfony/process');
347
PHP
348
        );
349
    }
350
351
    private function assertSymfonyPackageVersionsIsUsable(string $directory) : void
352
    {
353
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
354
355
        self::assertSame(0, $exitCode);
356
        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...
357
        self::assertRegExp('/^v?5\\..*\\@[a-f0-9]*$/', $output[0]);
358
    }
359
360
    private function writePackageVersionUsingFile(string $directory) : void
361
    {
362
        file_put_contents(
363
            $directory . '/use-package-versions.php',
364
            <<<'PHP'
365
<?php
366
367
require_once __DIR__ . '/vendor/autoload.php';
368
369
echo \PackageVersions\Versions::getVersion('ocramius/package-versions');
370
PHP
371
        );
372
    }
373
374
    private function assertPackageVersionsIsUsable(string $directory) : void
375
    {
376
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
377
378
        self::assertSame(0, $exitCode);
379
        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...
380
        self::assertRegExp('/^1\\..*\\@[a-f0-9]*$/', $output[0]);
381
    }
382
383
    /**
384
     * @return mixed[]
385
     */
386
    private function execComposerInDir(string $command, string $dir) : array
387
    {
388
        $currentDir = getcwd();
389
        chdir($dir);
390
        exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' ', $output, $exitCode);
391
        $this->assertEquals(0, $exitCode, implode(PHP_EOL, $output));
392
        chdir($currentDir);
393
394
        return $output;
395
    }
396
397
    private function rmDir(string $directory) : void
398
    {
399
        if (! is_dir($directory)) {
400
            unlink($directory);
401
402
            return;
403
        }
404
405
        array_map(
406
            function ($item) use ($directory) : void {
407
                $this->rmDir($directory . '/' . $item);
408
            },
409
            array_filter(
410
                scandir($directory),
411
                static function (string $dirItem) {
412
                    return ! in_array($dirItem, ['.', '..'], true);
413
                }
414
            )
415
        );
416
417
        rmdir($directory);
418
    }
419
}
420