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
![]() |
|||||
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
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
![]() |
|||||
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
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
![]() |
|||||
385 | static function (string $dirItem) { |
||||
386 | return ! in_array($dirItem, ['.', '..'], true); |
||||
387 | } |
||||
388 | ) |
||||
389 | ); |
||||
390 | |||||
391 | rmdir($directory); |
||||
392 | } |
||||
393 | } |
||||
394 |