diff --git a/composer.json b/composer.json index 2d77c3929..5619aac8d 100644 --- a/composer.json +++ b/composer.json @@ -24,6 +24,7 @@ "require": { "bramus/monolog-colored-line-formatter": "^2", "composer/installers": "^1 || ^2", + "composer/semver": "^1 || ^3", "embed/embed": "^3", "league/csv": "^8 || ^9", "m1/env": "^2.1", diff --git a/src/Core/CoreKernel.php b/src/Core/CoreKernel.php index 885fb50a8..d28a98692 100644 --- a/src/Core/CoreKernel.php +++ b/src/Core/CoreKernel.php @@ -509,6 +509,18 @@ class CoreKernel implements Kernel return false; } + /** + * When manifests are discovering files, tests files in modules using the following CI library type will be ignored. + * + * The purpose of this method is to avoid loading PHPUnit test files with incompatible definitions. + * + * @return string[] List of CI types to ignore as defined by `Module`. + */ + protected function getIgnoredCIConfigs(): array + { + return []; + } + /** * Boot all manifests * @@ -517,10 +529,18 @@ class CoreKernel implements Kernel protected function bootManifests($flush) { // Setup autoloader - $this->getClassLoader()->init($this->getIncludeTests(), $flush); + $this->getClassLoader()->init( + $this->getIncludeTests(), + $flush, + $this->getIgnoredCIConfigs() + ); // Find modules - $this->getModuleLoader()->init($this->getIncludeTests(), $flush); + $this->getModuleLoader()->init( + $this->getIncludeTests(), + $flush, + $this->getIgnoredCIConfigs() + ); // Flush config if ($flush) { @@ -538,7 +558,11 @@ class CoreKernel implements Kernel $defaultSet->setProject( ModuleManifest::config()->get('project') ); - $defaultSet->init($this->getIncludeTests(), $flush); + $defaultSet->init( + $this->getIncludeTests(), + $flush, + $this->getIgnoredCIConfigs() + ); } } diff --git a/src/Core/Manifest/ClassLoader.php b/src/Core/Manifest/ClassLoader.php index 809b471c0..f36c2520d 100644 --- a/src/Core/Manifest/ClassLoader.php +++ b/src/Core/Manifest/ClassLoader.php @@ -120,13 +120,14 @@ class ClassLoader * * @param bool $includeTests * @param bool $forceRegen + * @param string[] $ignoredCIConfigs */ - public function init($includeTests = false, $forceRegen = false) + public function init($includeTests = false, $forceRegen = false, array $ignoredCIConfigs = []) { foreach ($this->manifests as $manifest) { /** @var ClassManifest $instance */ $instance = $manifest['instance']; - $instance->init($includeTests, $forceRegen); + $instance->init($includeTests, $forceRegen, $ignoredCIConfigs); } $this->registerAutoloader(); diff --git a/src/Core/Manifest/ClassManifest.php b/src/Core/Manifest/ClassManifest.php index 49b7e3e75..a09fd06d8 100644 --- a/src/Core/Manifest/ClassManifest.php +++ b/src/Core/Manifest/ClassManifest.php @@ -260,8 +260,9 @@ class ClassManifest * * @param bool $includeTests * @param bool $forceRegen + * @param string[] $ignoredCIConfigs */ - public function init($includeTests = false, $forceRegen = false) + public function init($includeTests = false, $forceRegen = false, array $ignoredCIConfigs = []) { $this->cache = $this->buildCache($includeTests); @@ -275,7 +276,7 @@ class ClassManifest } // Build - $this->regenerate($includeTests); + $this->regenerate($includeTests, $ignoredCIConfigs); } /** @@ -500,8 +501,9 @@ class ClassManifest * Completely regenerates the manifest file. * * @param bool $includeTests + * @param string[] $ignoredCIConfigs */ - public function regenerate($includeTests) + public function regenerate($includeTests, array $ignoredCIConfigs = []) { // Reset the manifest so stale info doesn't cause errors. $this->loadState([]); @@ -513,6 +515,7 @@ class ClassManifest 'name_regex' => '/^[^_].*\\.php$/', 'ignore_files' => ['index.php', 'cli-script.php'], 'ignore_tests' => !$includeTests, + 'ignored_ci_configs' => $ignoredCIConfigs, 'file_callback' => function ($basename, $pathname, $depth) use ($includeTests, $finder) { $this->handleFile($basename, $pathname, $includeTests); }, diff --git a/src/Core/Manifest/ManifestFileFinder.php b/src/Core/Manifest/ManifestFileFinder.php index c4c248e23..46fa5ea09 100644 --- a/src/Core/Manifest/ManifestFileFinder.php +++ b/src/Core/Manifest/ManifestFileFinder.php @@ -2,16 +2,18 @@ namespace SilverStripe\Core\Manifest; +use RuntimeException; use SilverStripe\Assets\FileFinder; /** - * An extension to the default file finder with some extra filters to faciliate + * An extension to the default file finder with some extra filters to facilitate * autoload and template manifest generation: * - Only modules with _config.php files are scanned. * - If a _manifest_exclude file is present inside a directory it is ignored. * - Assets and module language directories are ignored. - * - Module tests directories are skipped if the ignore_tests option is not - * set to false. + * - Module tests directories are skipped if either of these conditions is meant: + * - the `ignore_tests` option is not set to false. + * - the module PHP CI configuration matches one of the `ignored_ci_configs` */ class ManifestFileFinder extends FileFinder { @@ -32,7 +34,8 @@ class ManifestFileFinder extends FileFinder 'include_themes' => false, 'ignore_tests' => true, 'min_depth' => 1, - 'ignore_dirs' => ['node_modules'] + 'ignore_dirs' => ['node_modules'], + 'ignored_ci_configs' => [] ]; public function acceptDir($basename, $pathname, $depth) @@ -73,6 +76,14 @@ class ManifestFileFinder extends FileFinder return false; } + // Skip if test dir inside vendor module with unexpected CI Configuration + if ($depth > 3 && $basename === self::TESTS_DIR && $ignoredCIConfig = $this->getOption('ignored_ci_configs')) { + $ciLib = $this->findModuleCIPhpConfiguration($basename, $pathname, $depth); + if (in_array($ciLib, $ignoredCIConfig)) { + return false; + } + } + return parent::acceptDir($basename, $pathname, $depth); } @@ -251,4 +262,40 @@ class ManifestFileFinder extends FileFinder return false; } + + /** + * Find out the root of the current module and read the PHP CI configuration from tho composer file + * + * @param string $basename Name of the current folder + * @param string $pathname Full path the parent folder + * @param string $depth Depth of the current folder + */ + private function findModuleCIPhpConfiguration(string $basename, string $pathname, int $depth): string + { + if ($depth < 1) { + // We went all the way back to the root of the project + return Module::CI_UNKNOWN; + } + + // We pop the current folder and use the next entry the pathname + $newBasename = basename($pathname); + $newPathname = dirname($pathname); + $newDepth = $depth - 1; + + if ($this->isDirectoryModule($newBasename, $newPathname, $newDepth)) { + // We've reached the root of the module folder, we can read the PHP CI config now + $module = new Module($newPathname, $this->upLevels($newPathname, $newDepth)); + $config = $module->getCIConfig(); + + if (empty($config['PHP'])) { + // This should never happen + throw new RuntimeException('Module::getCIConfig() did not return a PHP CI value'); + } + + return $config['PHP']; + } + + // We haven't reach our module root yet ... let's look up one more level + return $this->findModuleCIPhpConfiguration($newBasename, $newPathname, $newDepth); + } } diff --git a/src/Core/Manifest/Module.php b/src/Core/Manifest/Module.php index f06faae1e..23a974fb3 100644 --- a/src/Core/Manifest/Module.php +++ b/src/Core/Manifest/Module.php @@ -2,14 +2,16 @@ namespace SilverStripe\Core\Manifest; +use Composer\Semver\Semver; use Exception; use InvalidArgumentException; +use RuntimeException; use Serializable; use SilverStripe\Core\Path; use SilverStripe\Dev\Deprecation; /** - * Abstraction of a PHP Package. Can be used to retrieve information about SilverStripe modules, and other packages + * Abstraction of a PHP Package. Can be used to retrieve information about Silverstripe CMS modules, and other packages * managed via composer, by reading their `composer.json` file. */ class Module implements Serializable @@ -19,6 +21,23 @@ class Module implements Serializable */ const TRIM_CHARS = ' /\\'; + /** + * Return value of getCIConfig() when module uses PHPUNit 9 + */ + const CI_PHPUNIT_NINE = 'CI_PHPUNIT_NINE'; + + /** + * Return value of getCIConfig() when module uses PHPUNit 5 + */ + const CI_PHPUNIT_FIVE = 'CI_PHPUNIT_FIVE'; + + /** + * Return value of getCIConfig() when module does not use any CI + */ + const CI_UNKNOWN = 'CI_UNKNOWN'; + + + /** * Full directory path to this module with no trailing slash * @@ -64,7 +83,7 @@ class Module implements Serializable * Gets name of this module. Used as unique key and identifier for this module. * * If installed by composer, this will be the full composer name (vendor/name). - * If not insalled by composer this will default to the basedir() + * If not installed by composer this will default to the `basedir()` * * @return string */ @@ -74,7 +93,7 @@ class Module implements Serializable } /** - * Get full composer name. Will be null if no composer.json is available + * Get full composer name. Will be `null` if no composer.json is available * * @return string|null */ @@ -130,7 +149,7 @@ class Module implements Serializable /** * Name of the resource directory where vendor resources should be exposed as defined by the `extra.resources-dir` - * key in the composer file. A blank string will will be returned if the key is undefined. + * key in the composer file. A blank string will be returned if the key is undefined. * * Only applicable when reading the composer file for the main project. * @return string @@ -275,6 +294,120 @@ class Module implements Serializable ->getResource($path) ->exists(); } + + /** + * Determine what configurations the module is using to run various aspects of its CI. THe only aspect + * that is observed is `PHP` + * @return array List of configuration aspects e.g.: `['PHP' => 'CI_PHPUNIT_NINE']` + * @internal + */ + public function getCIConfig(): array + { + return [ + 'PHP' => $this->getPhpCiConfig() + ]; + } + + /** + * Determine what CI Configuration the module uses to test its PHP code. + */ + private function getPhpCiConfig(): string + { + // We don't have any composer data at all + if (empty($this->composerData)) { + return self::CI_UNKNOWN; + } + + // We don't have any dev dependencies + if (empty($this->composerData['require-dev']) || !is_array($this->composerData['require-dev'])) { + return self::CI_UNKNOWN; + } + + // We are assuming a typical setup where the CI lib is defined in require-dev rather than require + $requireDev = $this->composerData['require-dev']; + + // Try to pick which CI we are using based on phpunit constraint + $phpUnitConstraint = $this->requireDevConstraint(['sminnee/phpunit', 'phpunit/phpunit']); + if ($phpUnitConstraint) { + if ($this->constraintSatisfies( + $phpUnitConstraint, + ['5.7.0', '5.0.0', '5.x-dev', '5.7.x-dev'], + 5 + )) { + return self::CI_PHPUNIT_FIVE; + } + if ($this->constraintSatisfies( + $phpUnitConstraint, + ['9.0.0', '9.5.0', '9.x-dev', '9.5.x-dev'], + 9 + )) { + return self::CI_PHPUNIT_NINE; + } + } + + // Try to pick which CI we are using based on recipe-testing constraint + $recipeTestingConstraint = $this->requireDevConstraint(['silverstripe/recipe-testing']); + if ($recipeTestingConstraint) { + if ($this->constraintSatisfies( + $recipeTestingConstraint, + ['1.0.0', '1.1.0', '1.2.0', '1.1.x-dev', '1.2.x-dev', '1.x-dev'], + 1 + )) { + return self::CI_PHPUNIT_FIVE; + } + if ($this->constraintSatisfies( + $recipeTestingConstraint, + ['2.0.0', '2.0.x-dev', '2.x-dev'], + 2 + )) { + return self::CI_PHPUNIT_NINE; + } + } + + return self::CI_UNKNOWN; + } + + /** + * Retrieve the constraint for the first module that is found in the require-dev section + * @param string[] $modules + * @return false|string + */ + private function requireDevConstraint(array $modules) + { + if (empty($this->composerData['require-dev']) || !is_array($this->composerData['require-dev'])) { + return false; + } + + $requireDev = $this->composerData['require-dev']; + foreach ($modules as $module) { + if (isset($requireDev[$module])) { + return $requireDev[$module]; + } + } + + return false; + } + + /** + * Determines if the provided constraint allows at least one of the version provided + */ + private function constraintSatisfies( + string $constraint, + array $possibleVersions, + int $majorVersionFallback + ): bool { + // Let's see of any of our possible versions is allowed by the constraint + if (!empty(Semver::satisfiedBy($possibleVersions, $constraint))) { + return true; + } + + // Let's see if we are using an exact version constraint. e.g. ~1.2.3 or 1.2.3 or ~1.2 or 1.2.* + if (preg_match("/^~?$majorVersionFallback(\.(\d+)|\*){0,2}/", $constraint)) { + return true; + } + + return false; + } } /** diff --git a/src/Core/Manifest/ModuleLoader.php b/src/Core/Manifest/ModuleLoader.php index c5f77d77b..a27fccb51 100644 --- a/src/Core/Manifest/ModuleLoader.php +++ b/src/Core/Manifest/ModuleLoader.php @@ -91,11 +91,12 @@ class ModuleLoader * * @param bool $includeTests * @param bool $forceRegen + * @param string[] $ignoredCIConfigs */ - public function init($includeTests = false, $forceRegen = false) + public function init($includeTests = false, $forceRegen = false, array $ignoredCIConfigs = []) { foreach ($this->manifests as $manifest) { - $manifest->init($includeTests, $forceRegen); + $manifest->init($includeTests, $forceRegen, $ignoredCIConfigs); } } } diff --git a/src/Core/Manifest/ModuleManifest.php b/src/Core/Manifest/ModuleManifest.php index 38acc4324..3aca00e82 100644 --- a/src/Core/Manifest/ModuleManifest.php +++ b/src/Core/Manifest/ModuleManifest.php @@ -121,8 +121,9 @@ class ModuleManifest /** * @param bool $includeTests * @param bool $forceRegen Force the manifest to be regenerated. + * @param string[] $ignoredCIConfigs */ - public function init($includeTests = false, $forceRegen = false) + public function init($includeTests = false, $forceRegen = false, array $ignoredCIConfigs = []) { // build cache from factory if ($this->cacheFactory) { @@ -137,7 +138,7 @@ class ModuleManifest $this->modules = $this->cache->get($this->cacheKey) ?: []; } if (empty($this->modules)) { - $this->regenerate($includeTests); + $this->regenerate($includeTests, $ignoredCIConfigs); } } @@ -162,8 +163,9 @@ class ModuleManifest * Does _not_ build the actual variant * * @param bool $includeTests + * @param string[] $ignoredCIConfigs */ - public function regenerate($includeTests = false) + public function regenerate($includeTests = false, array $ignoredCIConfigs = []) { $this->modules = []; @@ -171,6 +173,7 @@ class ModuleManifest $finder->setOptions([ 'min_depth' => 0, 'ignore_tests' => !$includeTests, + 'ignored_ci_configs' => $ignoredCIConfigs, 'dir_callback' => function ($basename, $pathname, $depth) use ($finder) { if ($finder->isDirectoryModule($basename, $pathname, $depth)) { $this->addModule($pathname); diff --git a/src/Dev/SapphireTest.php b/src/Dev/SapphireTest.php index bca1c2f82..ffcf459a0 100644 --- a/src/Dev/SapphireTest.php +++ b/src/Dev/SapphireTest.php @@ -29,6 +29,7 @@ use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Core\Injector\InjectorLoader; use SilverStripe\Core\Manifest\ClassLoader; +use SilverStripe\Core\Manifest\Module; use SilverStripe\Core\Manifest\ModuleResourceLoader; use SilverStripe\Dev\Constraint\SSListContains; use SilverStripe\Dev\Constraint\SSListContainsOnly; @@ -1010,6 +1011,9 @@ if (class_exists(IsEqualCanonicalizing::class)) { // Test application $kernel = new TestKernel(BASE_PATH); + // PHPUnit 9 only logic to exclude old test still targeting PHPUNit 5.7 + $kernel->setIgnoredCIConfigs([Module::CI_PHPUNIT_FIVE, Module::CI_UNKNOWN]); + if (class_exists(HTTPApplication::class)) { // Mock request $_SERVER['argv'] = ['vendor/bin/phpunit', '/']; diff --git a/src/Dev/TestKernel.php b/src/Dev/TestKernel.php index 67229f5ca..3b4f9541d 100644 --- a/src/Dev/TestKernel.php +++ b/src/Dev/TestKernel.php @@ -9,6 +9,11 @@ use SilverStripe\Core\CoreKernel; */ class TestKernel extends CoreKernel { + + /** @var string[] $ciConfigs */ + private $ciConfigs = []; + + public function __construct($basePath) { $this->setEnvironment(self::DEV); @@ -41,6 +46,22 @@ class TestKernel extends CoreKernel return true; } + + /** + * Set a list of CI configurations that should cause a module's test not to be added to a manifest + * @param string[] $ciConfigs + */ + public function setIgnoredCIConfigs(array $ciConfigs): self + { + $this->ciConfigs = $ciConfigs; + return $this; + } + + protected function getIgnoredCIConfigs(): array + { + return $this->ciConfigs; + } + protected function bootErrorHandling() { // Leave phpunit to capture errors diff --git a/src/View/ThemeManifest.php b/src/View/ThemeManifest.php index 4f36bac11..d68fb8d92 100644 --- a/src/View/ThemeManifest.php +++ b/src/View/ThemeManifest.php @@ -73,8 +73,9 @@ class ThemeManifest implements ThemeList /** * @param bool $includeTests Include tests in the manifest * @param bool $forceRegen Force the manifest to be regenerated. + * @param string[] $ignoredCIConfigs */ - public function init($includeTests = false, $forceRegen = false) + public function init($includeTests = false, $forceRegen = false, array $ignoredCIConfigs = []) { // build cache from factory if ($this->cacheFactory) { @@ -87,7 +88,7 @@ class ThemeManifest implements ThemeList if (!$forceRegen && $this->cache && ($data = $this->cache->get($this->cacheKey))) { $this->themes = $data; } else { - $this->regenerate($includeTests); + $this->regenerate($includeTests, $ignoredCIConfigs); } } @@ -129,14 +130,16 @@ class ThemeManifest implements ThemeList * Regenerates the manifest by scanning the base path. * * @param bool $includeTests + * @param string[] $ignoredCIConfigs */ - public function regenerate($includeTests = false) + public function regenerate($includeTests = false, array $ignoredCIConfigs = []) { $finder = new ManifestFileFinder(); $finder->setOptions([ 'include_themes' => false, 'ignore_dirs' => ['node_modules', THEMES_DIR], 'ignore_tests' => !$includeTests, + 'ignored_ci_configs' => $ignoredCIConfigs, 'dir_callback' => [$this, 'handleDirectory'] ]); diff --git a/tests/php/Core/Manifest/ManifestFileFinderTest.php b/tests/php/Core/Manifest/ManifestFileFinderTest.php index 7c0909b10..f39a1bbc1 100644 --- a/tests/php/Core/Manifest/ManifestFileFinderTest.php +++ b/tests/php/Core/Manifest/ManifestFileFinderTest.php @@ -4,6 +4,7 @@ namespace SilverStripe\Core\Tests\Manifest; use SilverStripe\Core\Manifest\ManifestFileFinder; use SilverStripe\Dev\SapphireTest; +use SilverStripe\Core\Manifest\Module; /** * Tests for the {@link ManifestFileFinder} class. @@ -55,6 +56,8 @@ class ManifestFileFinderTest extends SapphireTest [ 'module/module.txt', 'vendor/myvendor/thismodule/module.txt', + 'vendor/myvendor/phpunit5module/code/logic.txt', + 'vendor/myvendor/phpunit9module/code/logic.txt', ] ); } @@ -75,6 +78,56 @@ class ManifestFileFinderTest extends SapphireTest 'vendor/myvendor/thismodule/module.txt', 'vendor/myvendor/thismodule/tests/tests.txt', 'vendor/myvendor/thismodule/code/tests/tests2.txt', + 'vendor/myvendor/phpunit5module/code/logic.txt', + 'vendor/myvendor/phpunit5module/tests/phpunit5tests.txt', + 'vendor/myvendor/phpunit9module/code/logic.txt', + 'vendor/myvendor/phpunit9module/tests/phpunit9tests.txt', + ] + ); + } + + public function testIgnorePHPUnit5Tests() + { + $finder = new ManifestFileFinder(); + $finder->setOption('name_regex', '/\.txt$/'); + $finder->setOption('ignore_tests', false); + $finder->setOption('ignored_ci_configs', [Module::CI_PHPUNIT_FIVE]); + + $this->assertFinderFinds( + $finder, + null, + [ + 'module/module.txt', + 'module/tests/tests.txt', + 'module/code/tests/tests2.txt', + 'vendor/myvendor/thismodule/module.txt', + 'vendor/myvendor/thismodule/tests/tests.txt', + 'vendor/myvendor/thismodule/code/tests/tests2.txt', + 'vendor/myvendor/phpunit5module/code/logic.txt', + 'vendor/myvendor/phpunit9module/code/logic.txt', + 'vendor/myvendor/phpunit9module/tests/phpunit9tests.txt', + ] + ); + } + + public function testIgnoreNonePHPUnit9Tests() + { + $finder = new ManifestFileFinder(); + $finder->setOption('name_regex', '/\.txt$/'); + $finder->setOption('ignore_tests', false); + $finder->setOption('ignored_ci_configs', [Module::CI_PHPUNIT_FIVE, Module::CI_UNKNOWN]); + + $this->assertFinderFinds( + $finder, + null, + [ + 'module/module.txt', + 'module/tests/tests.txt', + 'module/code/tests/tests2.txt', + 'vendor/myvendor/thismodule/module.txt', + 'vendor/myvendor/phpunit5module/code/logic.txt', + 'vendor/myvendor/phpunit9module/code/logic.txt', + 'vendor/myvendor/phpunit9module/tests/phpunit9tests.txt', ] ); } @@ -92,6 +145,8 @@ class ManifestFileFinderTest extends SapphireTest 'module/module.txt', 'themes/themes.txt', 'vendor/myvendor/thismodule/module.txt', + 'vendor/myvendor/phpunit5module/code/logic.txt', + 'vendor/myvendor/phpunit9module/code/logic.txt', ] ); } diff --git a/tests/php/Core/Manifest/ModuleTest.php b/tests/php/Core/Manifest/ModuleTest.php index b62a28941..f4ea2db5a 100644 --- a/tests/php/Core/Manifest/ModuleTest.php +++ b/tests/php/Core/Manifest/ModuleTest.php @@ -21,4 +21,53 @@ class ModuleTest extends SapphireTest $module = new Module($path, $path); $this->assertEquals('customised-resources-dir', $module->getResourcesDir()); } + + /** + * @dataProvider ciConfigProvider + * @param string $fixture The folder containing our test composer file + * @param string $expectedPhpConfig + */ + public function testGetCIConfig($fixture, $expectedPhpConfig) + { + $path = __DIR__ . '/fixtures/phpunit-detection/' . $fixture; + $module = new Module($path, $path); + $this->assertEquals( + $expectedPhpConfig, + $module->getCIConfig()['PHP'], + 'PHP config is set to ' . $expectedPhpConfig + ); + } + + public function ciConfigProvider() + { + return [ + 'empty require-dev' => ['empty-require-dev', Module::CI_UNKNOWN], + 'no require-dev' => ['no-require-dev', Module::CI_UNKNOWN], + 'older version of phpunit' => ['old-phpunit', Module::CI_UNKNOWN], + 'phpunit between 5 and 9' => ['inbetween-phpunit', Module::CI_UNKNOWN], + 'phpunit beyond 9' => ['future-phpunit', Module::CI_UNKNOWN], + + 'phpunit 5.0' => ['phpunit-five-zero', Module::CI_PHPUNIT_FIVE], + 'phpunit 5.7' => ['phpunit-five-seven', Module::CI_PHPUNIT_FIVE], + 'phpunit 5 exact version' => ['phpunit-five-exact-version', Module::CI_PHPUNIT_FIVE], + 'phpunit 5 tilde' => ['phpunit-five-tilde', Module::CI_PHPUNIT_FIVE], + 'sminnee 5.7' => ['sminnee-five-seven', Module::CI_PHPUNIT_FIVE], + 'sminnee 5' => ['sminnee-five-seven', Module::CI_PHPUNIT_FIVE], + 'sminnee 5 star' => ['sminnee-five-star', Module::CI_PHPUNIT_FIVE], + + 'phpunit 9' => ['phpunit-nine', Module::CI_PHPUNIT_NINE], + 'phpunit 9.5' => ['phpunit-nine-five', Module::CI_PHPUNIT_NINE], + 'future phpunit 9' => ['phpunit-nine-x', Module::CI_PHPUNIT_NINE], + 'phpunit 9 exact version' => ['phpunit-nine-exact', Module::CI_PHPUNIT_NINE], + + 'recipe-testing 1' => ['recipe-testing-one', Module::CI_PHPUNIT_FIVE], + 'recipe-testing 1.x' => ['recipe-testing-one-x', Module::CI_PHPUNIT_FIVE], + 'recipe-testing 1.2.x' => ['recipe-testing-one-two-x', Module::CI_PHPUNIT_FIVE], + 'recipe-testing 1 with stability flag' => ['recipe-testing-one-flag', Module::CI_PHPUNIT_FIVE], + + 'recipe-testing 2' => ['recipe-testing-two', Module::CI_PHPUNIT_NINE], + 'recipe-testing 2.x' => ['recipe-testing-two-x', Module::CI_PHPUNIT_NINE], + 'recipe-testing 2 exact' => ['recipe-testing-two-x', Module::CI_PHPUNIT_NINE], + ]; + } } diff --git a/tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/phpunit5module/_config.php b/tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/phpunit5module/_config.php new file mode 100644 index 000000000..b3d9bbc7f --- /dev/null +++ b/tests/php/Core/Manifest/fixtures/manifestfilefinder/vendor/myvendor/phpunit5module/_config.php @@ -0,0 +1 @@ +