Passed
Pull Request — master (#144)
by
unknown
01:40
created

testOnReadonlyFilesystemDoesNotGenerateClasses()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 50
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 28
c 1
b 0
f 0
nc 3
nop 0
dl 0
loc 50
rs 9.472
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 chmod;
18
use function escapeshellarg;
19
use function exec;
20
use function file_exists;
21
use function file_get_contents;
22
use function file_put_contents;
23
use function getcwd;
24
use function in_array;
25
use function is_dir;
26
use function iterator_to_array;
27
use function json_decode;
28
use function json_encode;
29
use function mkdir;
30
use function putenv;
31
use function realpath;
32
use function rmdir;
33
use function scandir;
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
43
/**
44
 * @coversNothing
45
 */
46
class E2EInstallerTest extends TestCase
47
{
48
    private string $tempGlobalComposerHome;
49
50
    private string $tempLocalComposerHome;
51
52
    private string $tempArtifact;
53
54
    protected function setUp() : void
55
    {
56
        $this->tempGlobalComposerHome = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/global';
57
        $this->tempLocalComposerHome  = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/local';
58
        $this->tempArtifact           = sys_get_temp_dir() . '/' . uniqid('InstallerTest', true) . '/artifacts';
59
        mkdir($this->tempGlobalComposerHome, 0700, true);
60
        mkdir($this->tempLocalComposerHome, 0700, true);
61
        mkdir($this->tempArtifact, 0700, true);
62
63
        putenv('COMPOSER_HOME=' . $this->tempGlobalComposerHome);
64
    }
65
66
    protected function tearDown() : void
67
    {
68
        $this->rmDir($this->tempGlobalComposerHome);
69
        $this->rmDir($this->tempLocalComposerHome);
70
        $this->rmDir($this->tempArtifact);
71
72
        putenv('COMPOSER_HOME');
73
    }
74
75
    public function testGloballyInstalledPluginDoesNotGenerateVersionsForLocalProject() : void
76
    {
77
        $this->createPackageVersionsArtifact();
78
79
        $this->writeComposerJsonFile(
80
            [
81
                'name'         => 'package-versions/e2e-global',
82
                'require'      => ['ocramius/package-versions' => '1.0.0'],
83
                'repositories' => [
84
                    ['packagist' => false],
85
                    [
86
                        'type' => 'artifact',
87
                        'url' => $this->tempArtifact,
88
                    ],
89
                ],
90
            ],
91
            $this->tempGlobalComposerHome
92
        );
93
94
        $this->execComposerInDir('global update', $this->tempGlobalComposerHome);
95
96
        $this->createArtifact();
97
        $this->writeComposerJsonFile(
98
            [
99
                'name'         => 'package-versions/e2e-local',
100
                'require'      => ['test/package' => '2.0.0'],
101
                'repositories' => [
102
                    ['packagist' => false],
103
                    [
104
                        'type' => 'artifact',
105
                        'url' => $this->tempArtifact,
106
                    ],
107
                ],
108
            ],
109
            $this->tempLocalComposerHome
110
        );
111
112
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
113
        self::assertFileDoesNotExist(
114
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
115
        );
116
    }
117
118
    public function testRemovingPluginDoesNotAttemptToGenerateVersions() : void
119
    {
120
        $this->createPackageVersionsArtifact();
121
        $this->createArtifact();
122
123
        $this->writeComposerJsonFile(
124
            [
125
                'name'         => 'package-versions/e2e-local',
126
                'require'      => [
127
                    'test/package' => '2.0.0',
128
                    'ocramius/package-versions' => '1.0.0',
129
                ],
130
                'repositories' => [
131
                    ['packagist' => false],
132
                    [
133
                        'type' => 'artifact',
134
                        'url' => $this->tempArtifact,
135
                    ],
136
                ],
137
            ],
138
            $this->tempLocalComposerHome
139
        );
140
141
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
142
        self::assertFileExists(
143
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
144
        );
145
146
        $this->execComposerInDir('remove ocramius/package-versions', $this->tempLocalComposerHome);
147
148
        self::assertFileDoesNotExist(
149
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
150
        );
151
    }
152
153
    /**
154
     * @group #41
155
     * @group #46
156
     */
157
    public function testRemovingPluginWithNoDevDoesNotAttemptToGenerateVersions() : void
158
    {
159
        $this->createPackageVersionsArtifact();
160
        $this->createArtifact();
161
162
        $this->writeComposerJsonFile(
163
            [
164
                'name'         => 'package-versions/e2e-local',
165
                'require-dev'      => ['ocramius/package-versions' => '1.0.0'],
166
                'repositories' => [
167
                    ['packagist' => false],
168
                    [
169
                        'type' => 'artifact',
170
                        'url' => $this->tempArtifact,
171
                    ],
172
                ],
173
            ],
174
            $this->tempLocalComposerHome
175
        );
176
177
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
178
        self::assertFileExists(
179
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
180
        );
181
182
        $this->execComposerInDir('install --no-dev', $this->tempLocalComposerHome);
183
184
        self::assertFileDoesNotExist(
185
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
186
        );
187
    }
188
189
    public function testOnReadonlyFilesystemDoesNotGenerateClasses() : void
190
    {
191
        $this->createPackageVersionsArtifact();
192
        $this->createArtifact();
193
194
        $this->writeComposerJsonFile(
195
            [
196
                'name'         => 'package-versions/e2e-local',
197
                'require-dev'  => ['ocramius/package-versions' => '1.0.0'],
198
                'repositories' => [
199
                    ['packagist' => false],
200
                    [
201
                        'type' => 'artifact',
202
                        'url' => $this->tempArtifact,
203
                    ],
204
                ],
205
            ],
206
            $this->tempLocalComposerHome
207
        );
208
209
        $this->execComposerInDir('install', $this->tempLocalComposerHome);
210
211
        unlink(
212
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
213
        );
214
215
        chmod(
216
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions',
217
            0440
218
        );
219
220
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
221
222
        self::assertFileDoesNotExist(
223
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
224
        );
225
226
        $files = new RecursiveDirectoryIterator(
227
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions',
228
        );
229
230
        foreach ($files as $file) {
231
            $path = $file->getRealPath();
232
            if (! $path || ! file_exists($path)) {
233
                continue;
234
            }
235
236
            chmod(
237
                $path,
238
                0700
239
            );
240
        }
241
    }
242
243
    /**
244
     * @group 101
245
     */
246
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
247
    {
248
        $this->createPackageVersionsArtifact();
249
        $this->createArtifact();
250
251
        $this->writeComposerJsonFile(
252
            [
253
                'name'         => 'package-versions/e2e-local',
254
                'require'      => ['ocramius/package-versions' => '1.0.0'],
255
                'repositories' => [
256
                    ['packagist' => false],
257
                    [
258
                        'type' => 'artifact',
259
                        'url' => $this->tempArtifact,
260
                    ],
261
                ],
262
            ],
263
            $this->tempLocalComposerHome
264
        );
265
266
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
267
        self::assertFileExists(
268
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
269
        );
270
271
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
272
        self::assertPackageVersionsIsUsable($this->tempLocalComposerHome);
0 ignored issues
show
Bug Best Practice introduced by
The method PackageVersionsTest\E2EI...ckageVersionsIsUsable() is not static, but was called statically. ( Ignorable by Annotation )

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

272
        self::/** @scrutinizer ignore-call */ 
273
              assertPackageVersionsIsUsable($this->tempLocalComposerHome);
Loading history...
273
    }
274
275
    private function createPackageVersionsArtifact() : void
276
    {
277
        $zip = new ZipArchive();
278
279
        $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE);
280
281
        $files = array_filter(
282
            iterator_to_array(new RecursiveIteratorIterator(
283
                new RecursiveCallbackFilterIterator(
284
                    new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS),
285
                    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

285
                    /** @scrutinizer ignore-type */ static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
Loading history...
286
                        return $iterator->getSubPathname()[0]  !== '.' && $iterator->getSubPathname() !== 'vendor';
287
                    }
288
                ),
289
                RecursiveIteratorIterator::LEAVES_ONLY
290
            )),
291
            static function (SplFileInfo $file) {
292
                return ! $file->isDir();
293
            }
294
        );
295
296
        array_walk(
297
            $files,
298
            static function (SplFileInfo $file) use ($zip) {
299
                if ($file->getFilename() === 'composer.json') {
300
                    $contents            = json_decode(file_get_contents($file->getRealPath()), true);
301
                    $contents['version'] = '1.0.0';
302
303
                    return $zip->addFromString('composer.json', json_encode($contents));
304
                }
305
306
                $zip->addFile(
307
                    $file->getRealPath(),
308
                    substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1)
309
                );
310
            }
311
        );
312
313
        $zip->close();
314
    }
315
316
    private function createArtifact() : void
317
    {
318
        $zip = new ZipArchive();
319
320
        $zip->open($this->tempArtifact . '/test-package-2.0.0.zip', ZipArchive::CREATE);
321
        $zip->addFromString(
322
            'composer.json',
323
            json_encode(
324
                [
325
                    'name'    => 'test/package',
326
                    'version' => '2.0.0',
327
                ],
328
                JSON_PRETTY_PRINT
329
            )
330
        );
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 writePackageVersionUsingFile(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('ocramius/package-versions');
355
PHP
356
        );
357
    }
358
359
    private function assertPackageVersionsIsUsable(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::assertMatchesRegularExpression('/^1\\..*\\@[a-f0-9]*$/', $output[0]);
366
    }
367
368
    /**
369
     * @return mixed[]
370
     */
371
    private function execComposerInDir(string $command, string $dir) : array
372
    {
373
        $currentDir = getcwd();
374
        chdir($dir);
375
        exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' 2> /dev/null', $output, $exitCode);
376
        self::assertEquals(0, $exitCode);
377
        chdir($currentDir);
378
379
        return $output;
380
    }
381
382
    private function rmDir(string $directory) : void
383
    {
384
        if (! is_dir($directory)) {
385
            unlink($directory);
386
387
            return;
388
        }
389
390
        array_map(
391
            function ($item) use ($directory) : void {
392
                $this->rmDir($directory . '/' . $item);
393
            },
394
            array_filter(
395
                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

395
                /** @scrutinizer ignore-type */ scandir($directory),
Loading history...
396
                static function (string $dirItem) {
397
                    return ! in_array($dirItem, ['.', '..'], true);
398
                }
399
            )
400
        );
401
402
        rmdir($directory);
403
    }
404
}
405