diff --git a/composer.json b/composer.json index 25d4a32..256d351 100644 --- a/composer.json +++ b/composer.json @@ -28,7 +28,7 @@ "phpstan/phpstan": "^2.1.2", "phpstan/phpdoc-parser": "^1.30.1", "bnf/phpstan-psr-container": "^1.1.0", - "phpunit/phpunit": "9.6.22", + "phpunit/phpunit": "^9.6.33", "phpstan/phpstan-phpunit": "^2.0.4", "phpstan/phpstan-symfony": "^2.0.2", "phpstan/phpstan-deprecation-rules": "^2.0.1" diff --git a/src/Composer/Repository/FixturePathRepository.php b/src/Composer/Repository/FixturePathRepository.php index 6154a8c..67d0089 100644 --- a/src/Composer/Repository/FixturePathRepository.php +++ b/src/Composer/Repository/FixturePathRepository.php @@ -172,7 +172,7 @@ protected function initialize(): void // copy symlink/relative options to transport options $package['transport-options'] = array_intersect_key($this->options, ['symlink' => true, 'relative' => true]); // use the version provided as option if available - if (isset($package['name'], $this->options['versions'][$package['name']])) { + if (isset($package['name']) && is_string($package['name']) && isset($this->options['versions'][$package['name']])) { $package['version'] = $this->options['versions'][$package['name']]; } diff --git a/src/Plugin/Config.php b/src/Plugin/Config.php index cc235c0..7e8eccb 100644 --- a/src/Plugin/Config.php +++ b/src/Plugin/Config.php @@ -142,10 +142,21 @@ protected function processFixtureExtensionPaths(array $paths, int $flags = 0): a $relativePaths = $this->isRelativePathsFlag($flags); $returnPaths = []; foreach ($paths as $path => $selection) { - $path = rtrim(($relativePaths ? $path : $this->realpath($path)), '/\\'); - $returnPaths[$path] = $selection; + if ($relativePaths) { + $path = $this->normalizePath((string)$path); + if ($path === '') { + // Keep the current directory, an empty url is not a valid fixture-path repository url. + $path = '.'; + } + } else { + $path = $this->realpath((string)$path); + } + // Different notations of the same path must not drop the selection of the former ones. + $returnPaths[$path] = isset($returnPaths[$path]) + ? array_values(array_unique(array_merge($returnPaths[$path], $selection))) + : $selection; } - return $paths; + return $returnPaths; } /** @@ -155,7 +166,7 @@ protected function processFixtureExtensionPaths(array $paths, int $flags = 0): a */ private function isRelativePathsFlag(int $flags): bool { - return ($flags & self::FLAG_PATHS_RELATIVE) === 1; + return ($flags & self::FLAG_PATHS_RELATIVE) !== 0; } /** @@ -165,10 +176,10 @@ private function isRelativePathsFlag(int $flags): bool */ private function realpath(string $path): string { + $path = $this->normalizePath($path); if ($path === '') { return $this->baseDir; } - $path = $this->normalizePath($path); if ($path[0] === '/' || (!empty($path[1]) && $path[1] === ':')) { return $path; } @@ -224,8 +235,6 @@ private static function handleRootPackageExtraConfig(IOInterface $io, RootPackag if (empty($fixtureExtensionPaths)) { return $rootPackageExtraConfig; } - $basePath = '/fake/root'; - $config = new self($basePath); $validPaths = []; foreach ($fixtureExtensionPaths as $path => $selection) { if (!is_array($selection)) { diff --git a/tests/Unit/Plugin/ConfigTest.php b/tests/Unit/Plugin/ConfigTest.php index 29e47b7..abb2997 100644 --- a/tests/Unit/Plugin/ConfigTest.php +++ b/tests/Unit/Plugin/ConfigTest.php @@ -116,6 +116,16 @@ public static function realpathDataSets(): \Generator 'path' => '/', 'expectedPath' => '/', ]; + yield 'Current directory path returns baseDir' => [ + 'baseDir' => '/fake/root', + 'path' => './', + 'expectedPath' => '/fake/root', + ]; + yield 'Relative path is prefixed with baseDir and trimmed' => [ + 'baseDir' => '/fake/root', + 'path' => 'some/relative/path/', + 'expectedPath' => '/fake/root/some/relative/path', + ]; } /** @@ -193,7 +203,8 @@ public static function loadCreatesConfigWithExpectedPathsDataSets(): \Generator { yield 'Empty extra config returns empty array' => [ 'extraConfig' => [], - 'expectedPaths' => [], + 'expectedRelativePaths' => [], + 'expectedAbsolutePaths' => [], 'expectedOutput' => '', ]; yield 'Other extra configuration are kept' => [ @@ -202,7 +213,8 @@ public static function loadCreatesConfigWithExpectedPathsDataSets(): \Generator 'extension-key' => 'some_extension_key', ], ], - 'expectedPaths' => [], + 'expectedRelativePaths' => [], + 'expectedAbsolutePaths' => [], 'expectedOutput' => '', ]; yield 'Non-array extra->sbuerk/fixture-packages/paths value is removed' => [ @@ -211,7 +223,8 @@ public static function loadCreatesConfigWithExpectedPathsDataSets(): \Generator 'paths' => false, ], ], - 'expectedPaths' => [], + 'expectedRelativePaths' => [], + 'expectedAbsolutePaths' => [], 'expectedOutput' => 'extra->sbuerk/fixture-packages/paths must be an array, "boolean" given.' . PHP_EOL, ]; yield 'Non-array extra->sbuerk/fixture-packages/paths value is removed keeping other settings' => [ @@ -221,16 +234,148 @@ public static function loadCreatesConfigWithExpectedPathsDataSets(): \Generator 'paths' => false, ], ], - 'expectedPaths' => [], + 'expectedRelativePaths' => [], + 'expectedAbsolutePaths' => [], 'expectedOutput' => 'extra->sbuerk/fixture-packages/paths must be an array, "boolean" given.' . PHP_EOL, ]; + yield 'Path without trailing slash' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + 'Tests/Functional/Fixtures/Extensions/*' => ['autoload'], + ], + ], + ], + 'expectedRelativePaths' => [ + 'Tests/Functional/Fixtures/Extensions/*' => ['autoload'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/Tests/Functional/Fixtures/Extensions/*' => ['autoload'], + ], + 'expectedOutput' => '', + ]; + yield 'Path with trailing slash is trimmed' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + 'Tests/Functional/Fixtures/Extensions/' => ['autoload'], + ], + ], + ], + 'expectedRelativePaths' => [ + 'Tests/Functional/Fixtures/Extensions' => ['autoload'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/Tests/Functional/Fixtures/Extensions' => ['autoload'], + ], + 'expectedOutput' => '', + ]; + yield 'Path with trailing backslash is trimmed' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + 'Tests\\Functional\\Fixtures\\Extensions\\' => ['autoload-dev'], + ], + ], + ], + 'expectedRelativePaths' => [ + 'Tests/Functional/Fixtures/Extensions' => ['autoload-dev'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/Tests/Functional/Fixtures/Extensions' => ['autoload-dev'], + ], + 'expectedOutput' => '', + ]; + yield 'Path list syntax with trailing slash is trimmed' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + 'Tests/Functional/Fixtures/Extensions/*/', + ], + ], + ], + 'expectedRelativePaths' => [ + 'Tests/Functional/Fixtures/Extensions/*' => ['autoload'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/Tests/Functional/Fixtures/Extensions/*' => ['autoload'], + ], + 'expectedOutput' => '', + ]; + yield 'Path outside project is kept relative or resolved against base dir' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + '../other-project/Fixtures/*/' => ['autoload', 'autoload-dev'], + ], + ], + ], + 'expectedRelativePaths' => [ + '../other-project/Fixtures/*' => ['autoload', 'autoload-dev'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/../other-project/Fixtures/*' => ['autoload', 'autoload-dev'], + ], + 'expectedOutput' => '', + ]; + yield 'Current directory path is kept as current directory or resolved to base dir' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + './' => ['autoload'], + ], + ], + ], + 'expectedRelativePaths' => [ + '.' => ['autoload'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root' => ['autoload'], + ], + 'expectedOutput' => '', + ]; + yield 'Selections of paths normalized to the same path are merged' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + 'Tests/Fixtures/' => ['autoload'], + 'Tests/Fixtures' => ['autoload-dev', 'autoload'], + '/fake/root/Tests/Fixtures' => ['autoload-dev'], + ], + ], + ], + 'expectedRelativePaths' => [ + 'Tests/Fixtures' => ['autoload', 'autoload-dev'], + '/fake/root/Tests/Fixtures' => ['autoload-dev'], + ], + 'expectedAbsolutePaths' => [ + '/fake/root/Tests/Fixtures' => ['autoload', 'autoload-dev'], + ], + 'expectedOutput' => '', + ]; + yield 'Absolute path is kept absolute and trimmed' => [ + 'extraConfig' => [ + 'sbuerk/fixture-packages' => [ + 'paths' => [ + '/some/absolute/Fixtures/' => ['autoload'], + ], + ], + ], + 'expectedRelativePaths' => [ + '/some/absolute/Fixtures' => ['autoload'], + ], + 'expectedAbsolutePaths' => [ + '/some/absolute/Fixtures' => ['autoload'], + ], + 'expectedOutput' => '', + ]; } /** * @dataProvider loadCreatesConfigWithExpectedPathsDataSets * @test */ - public function loadCreatesConfigWithExpectedPaths(array $extraConfig, array $expectedPaths, string $expectedOutput): void + public function loadCreatesConfigWithExpectedPaths(array $extraConfig, array $expectedRelativePaths, array $expectedAbsolutePaths, string $expectedOutput): void { $rootPackage = new RootPackage('fake/package', '1.0.0', '1.0.0.0'); $rootPackage->setExtra($extraConfig); @@ -241,7 +386,13 @@ public function loadCreatesConfigWithExpectedPaths(array $extraConfig, array $ex $bufferedIO = new BufferIO(); $config = Config::load($composer, $bufferedIO); self::assertInstanceOf(Config::class, $config); - self::assertSame($expectedPaths, $config->paths(Config::FLAG_PATHS_RELATIVE)); + self::assertSame($expectedRelativePaths, $config->paths(Config::FLAG_PATHS_RELATIVE)); + self::assertSame($expectedRelativePaths, $config->get('paths', Config::FLAG_PATHS_RELATIVE)); + self::assertSame(['config' => ['paths' => $expectedRelativePaths]], $config->all(Config::FLAG_PATHS_RELATIVE)); + self::assertSame($expectedAbsolutePaths, $config->paths()); + self::assertSame($expectedAbsolutePaths, $config->paths(Config::FLAG_PATHS_DEFAULT)); + self::assertSame($expectedAbsolutePaths, $config->get('paths')); + self::assertSame(['config' => ['paths' => $expectedAbsolutePaths]], $config->all()); self::assertSame($expectedOutput, $bufferedIO->getOutput()); } }