E2EInstallerTest::writePackageVersionUsingFile()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 5
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 5
rs 10
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_get_contents;
21
use function file_put_contents;
22
use function getcwd;
23
use function in_array;
24
use function is_dir;
25
use function is_writable;
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
        $versionsDir = $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions';
212
213
        $versionsFilePath = $versionsDir . '/Versions.php';
214
215
        file_put_contents($versionsFilePath, 'NOT PHP!');
216
217
        chmod($versionsFilePath, 0400);
218
        chmod($versionsDir, 0400);
219
220
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
221
222
        chmod($versionsDir, 0700);
223
        chmod($versionsFilePath, 0600);
224
225
        self::assertSame('NOT PHP!', file_get_contents($versionsFilePath));
226
    }
227
228
    /**
229
     * @group 101
230
     */
231
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
232
    {
233
        $this->createPackageVersionsArtifact();
234
        $this->createArtifact();
235
236
        $this->writeComposerJsonFile(
237
            [
238
                'name'         => 'package-versions/e2e-local',
239
                'require'      => ['ocramius/package-versions' => '1.0.0'],
240
                'repositories' => [
241
                    ['packagist' => false],
242
                    [
243
                        'type' => 'artifact',
244
                        'url' => $this->tempArtifact,
245
                    ],
246
                ],
247
            ],
248
            $this->tempLocalComposerHome
249
        );
250
251
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
252
        self::assertFileExists(
253
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
254
        );
255
256
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
257
        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

257
        self::/** @scrutinizer ignore-call */ 
258
              assertPackageVersionsIsUsable($this->tempLocalComposerHome);
Loading history...
258
    }
259
260
    private function createPackageVersionsArtifact() : void
261
    {
262
        $zip = new ZipArchive();
263
264
        $zip->open($this->tempArtifact . '/ocramius-package-versions-1.0.0.zip', ZipArchive::CREATE);
265
266
        $files = array_filter(
267
            iterator_to_array(new RecursiveIteratorIterator(
268
                new RecursiveCallbackFilterIterator(
269
                    new RecursiveDirectoryIterator(realpath(__DIR__ . '/../../'), RecursiveDirectoryIterator::SKIP_DOTS),
270
                    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

270
                    /** @scrutinizer ignore-type */ static function (SplFileInfo $file, string $key, RecursiveDirectoryIterator $iterator) {
Loading history...
271
                        return $iterator->getSubPathname()[0]  !== '.' && $iterator->getSubPathname() !== 'vendor';
272
                    }
273
                ),
274
                RecursiveIteratorIterator::LEAVES_ONLY
275
            )),
276
            static function (SplFileInfo $file) {
277
                return ! $file->isDir();
278
            }
279
        );
280
281
        array_walk(
282
            $files,
283
            static function (SplFileInfo $file) use ($zip) {
284
                if ($file->getFilename() === 'composer.json') {
285
                    $contents            = json_decode(file_get_contents($file->getRealPath()), true);
286
                    $contents['version'] = '1.0.0';
287
288
                    return $zip->addFromString('composer.json', json_encode($contents));
289
                }
290
291
                $zip->addFile(
292
                    $file->getRealPath(),
293
                    substr($file->getRealPath(), strlen(realpath(__DIR__ . '/../../')) + 1)
294
                );
295
            }
296
        );
297
298
        $zip->close();
299
    }
300
301
    private function createArtifact() : void
302
    {
303
        $zip = new ZipArchive();
304
305
        $zip->open($this->tempArtifact . '/test-package-2.0.0.zip', ZipArchive::CREATE);
306
        $zip->addFromString(
307
            'composer.json',
308
            json_encode(
309
                [
310
                    'name'    => 'test/package',
311
                    'version' => '2.0.0',
312
                ],
313
                JSON_PRETTY_PRINT
314
            )
315
        );
316
        $zip->close();
317
    }
318
319
    /**
320
     * @param mixed[] $config
321
     */
322
    private function writeComposerJsonFile(array $config, string $directory) : void
323
    {
324
        file_put_contents(
325
            $directory . '/composer.json',
326
            json_encode($config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)
327
        );
328
    }
329
330
    private function writePackageVersionUsingFile(string $directory) : void
331
    {
332
        file_put_contents(
333
            $directory . '/use-package-versions.php',
334
            <<<'PHP'
335
<?php
336
337
require_once __DIR__ . '/vendor/autoload.php';
338
339
echo \PackageVersions\Versions::getVersion('ocramius/package-versions');
340
PHP
341
        );
342
    }
343
344
    private function assertPackageVersionsIsUsable(string $directory) : void
345
    {
346
        exec(PHP_BINARY . ' ' . escapeshellarg($directory . '/use-package-versions.php'), $output, $exitCode);
347
348
        self::assertSame(0, $exitCode);
349
        self::assertCount(1, $output);
350
        self::assertMatchesRegularExpression('/^1\\..*\\@[a-f0-9]*$/', $output[0]);
351
    }
352
353
    /**
354
     * @return mixed[]
355
     */
356
    private function execComposerInDir(string $command, string $dir) : array
357
    {
358
        $currentDir = getcwd();
359
        chdir($dir);
360
        exec(__DIR__ . '/../../vendor/bin/composer ' . $command . ' 2> /dev/null', $output, $exitCode);
361
        self::assertEquals(0, $exitCode);
362
        chdir($currentDir);
363
364
        return $output;
365
    }
366
367
    private function rmDir(string $directory) : void
368
    {
369
        if (! is_writable($directory)) {
370
            chmod($directory, 0700);
371
        }
372
373
        if (! is_dir($directory)) {
374
            unlink($directory);
375
376
            return;
377
        }
378
379
        array_map(
380
            function ($item) use ($directory) : void {
381
                $this->rmDir($directory . '/' . $item);
382
            },
383
            array_filter(
384
                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

384
                /** @scrutinizer ignore-type */ scandir($directory),
Loading history...
385
                static function (string $dirItem) {
386
                    return ! in_array($dirItem, ['.', '..'], true);
387
                }
388
            )
389
        );
390
391
        rmdir($directory);
392
    }
393
}
394