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

E2EInstallerTest::writeComposerJsonFile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
c 0
b 0
f 0
nc 1
nop 2
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
219
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
220
221
        chmod($versionsFilePath, 0600);
222
223
        self::assertSame('NOT PHP!', file_get_contents($versionsFilePath));
224
225
        chmod($versionsDir, 0400);
226
227
        $this->execComposerInDir('update', $this->tempLocalComposerHome);
228
229
        chmod($versionsDir, 0700);
230
231
        self::assertSame('NOT PHP!', file_get_contents($versionsFilePath));
232
    }
233
234
    /**
235
     * @group 101
236
     */
237
    public function testInstallingPluginWithNoScriptsLeadsToUsableVersionsClass() : void
238
    {
239
        $this->createPackageVersionsArtifact();
240
        $this->createArtifact();
241
242
        $this->writeComposerJsonFile(
243
            [
244
                'name'         => 'package-versions/e2e-local',
245
                'require'      => ['ocramius/package-versions' => '1.0.0'],
246
                'repositories' => [
247
                    ['packagist' => false],
248
                    [
249
                        'type' => 'artifact',
250
                        'url' => $this->tempArtifact,
251
                    ],
252
                ],
253
            ],
254
            $this->tempLocalComposerHome
255
        );
256
257
        $this->execComposerInDir('install --no-scripts', $this->tempLocalComposerHome);
258
        self::assertFileExists(
259
            $this->tempLocalComposerHome . '/vendor/ocramius/package-versions/src/PackageVersions/Versions.php'
260
        );
261
262
        $this->writePackageVersionUsingFile($this->tempLocalComposerHome);
263
        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

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

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

390
                /** @scrutinizer ignore-type */ scandir($directory),
Loading history...
391
                static function (string $dirItem) {
392
                    return ! in_array($dirItem, ['.', '..'], true);
393
                }
394
            )
395
        );
396
397
        rmdir($directory);
398
    }
399
}
400