From 766884bcc5667f75ebaf6c341381839caf7465e9 Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Tue, 5 Jul 2022 13:26:26 +1200 Subject: [PATCH] FIX Use max of PHP 8.0 for x.10 recipes --- consts.php | 46 ++++++++++- job_creator.php | 121 ++++++++++++++++++--------- tests/JobCreatorTest.php | 173 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 297 insertions(+), 43 deletions(-) diff --git a/consts.php b/consts.php index 4ae9a91..88df39f 100644 --- a/consts.php +++ b/consts.php @@ -23,6 +23,14 @@ '8.0', '8.1', ], + '5.0' => [ + '8.1', + '8.2', + ], + '5' => [ + '8.1', + '8.2', + ], ]; const DB_MYSQL_57 = 'mysql57'; @@ -31,7 +39,7 @@ const DB_PGSQL = 'pgsql'; // Used when determining the version of installer to used. Intentionally doesn't include recipes -const LOCKSTEPED_REPOS = [ +const LOCKSTEPPED_REPOS = [ 'silverstripe-admin', 'silverstripe-asset-admin', 'silverstripe-assets', @@ -48,7 +56,7 @@ ]; // Repositories that do not require silverstripe/installer to be explicitly added as a dependency for testing -const NO_INSTALLER_REPOS = [ +const NO_INSTALLER_LOCKSTEPPED_REPOS = [ // these are/include recipe-cms or recipe-core, so we don't want to composer require installer // in .travis.yml they used the 'self' provision rather than 'standard' 'recipe-authoring-tools', @@ -63,6 +71,40 @@ 'recipe-reporting-tools', 'recipe-services', 'silverstripe-installer', +]; + +const NO_INSTALLER_UNLOCKSTEPPED_REPOS = [ // vendor-plugin is not a recipe, though we also do not want installer 'vendor-plugin' ]; + +const CMS_TO_REPO_MAJOR_VERSIONS = [ + '4' => [ + 'recipe-authoring-tools' => '1', + 'recipe-blog' => '1', + 'recipe-ccl' => '2', + 'recipe-cms' => '4', + 'recipe-collaboration' => '1', + 'recipe-content-blocks' => '2', + 'recipe-core' => '4', + 'recipe-form-building' => '1', + 'recipe-kitchen-sink' => '4', + 'recipe-reporting-tools' => '1', + 'recipe-services' => '1', + 'silverstripe-installer' => '4', + ], + '5' => [ + 'recipe-authoring-tools' => '2', + 'recipe-blog' => '2', + 'recipe-ccl' => '3', + 'recipe-cms' => '5', + 'recipe-collaboration' => '2', + 'recipe-content-blocks' => '3', + 'recipe-core' => '5', + 'recipe-form-building' => '2', + 'recipe-kitchen-sink' => '5', + 'recipe-reporting-tools' => '2', + 'recipe-services' => '2', + 'silverstripe-installer' => '5', + ], +]; diff --git a/job_creator.php b/job_creator.php index f1bc70e..0709ce1 100644 --- a/job_creator.php +++ b/job_creator.php @@ -2,28 +2,27 @@ class JobCreator { - private $installerVersion = null; - - private $parentBranch = null; + public string $branch = ''; + + public string $githubRepository = ''; + + private string $installerVersion = ''; + + private string $parentBranch = ''; /** * Get the correct version of silverstripe/installer to include for the given repository and branch */ - public function getInstallerVersion(string $githubRepository, string $branch): string + public function getInstallerVersion(): string { - $repo = explode('/', $githubRepository)[1]; - if (in_array($repo, NO_INSTALLER_REPOS)) { + $repo = explode('/', $this->githubRepository)[1]; + if (in_array($repo, NO_INSTALLER_LOCKSTEPPED_REPOS) || in_array($repo, NO_INSTALLER_UNLOCKSTEPPED_REPOS)) { return ''; } - // e.g. pulls/4.10/some-bugfix or pulls/4/some-feature - // for push events to the creative-commoners account - if (preg_match('#^pulls/([0-9\.]+)/#', $branch, $matches)) { - $branch = $matches[1]; - } - // e.g. 4.10-release - $branch = preg_replace('#^([0-9\.]+)-release$#', '$1', $branch); + $branch = $this->getCleanedBranch(); + $cmsMajor = $this->getCmsMajorFromBranch(); // module is a lockstepped repo - if (in_array($repo, LOCKSTEPED_REPOS) && is_numeric($branch)) { + if (in_array($repo, LOCKSTEPPED_REPOS) && is_numeric($branch)) { // e.g. ['4', '11'] $portions = explode('.', $branch); if (count($portions) == 1) { @@ -38,8 +37,9 @@ public function getInstallerVersion(string $githubRepository, string $branch): s } // use the latest minor version of installer $installerVersions = array_keys(INSTALLER_TO_PHP_VERSIONS); - // remove '4' version - $installerVersions = array_diff($installerVersions, ['4']); + $installerVersions = array_filter($installerVersions, fn($version) => substr($version, 0, 1) === $cmsMajor); + // remove major versions + $installerVersions = array_diff($installerVersions, ['4', '5', '6']); // get the minor portions of the verisons e.g. [9, 10, 11] $minorPortions = array_map(fn($portions) => (int) explode('.', $portions)[1], $installerVersions); sort($minorPortions); @@ -48,14 +48,11 @@ public function getInstallerVersion(string $githubRepository, string $branch): s public function createJob(int $phpIndex, array $opts): array { - $installerKey = str_replace('.x-dev', '', $this->installerVersion); - $installerKey = $installerKey ?: '4'; - $phpVersions = INSTALLER_TO_PHP_VERSIONS[$installerKey]; $default = [ # ensure there's a default value for all possible return keys # this allows us to use `if [[ "${{ matrix.key }}" == "true" ]]; then` in gha-ci/ci.yml 'installer_version' => $this->installerVersion, - 'php' => $phpVersions[$phpIndex] ?? $phpVersions[count($phpVersions) - 1], + 'php' => $this->getPhpVersion($phpIndex), 'db' => DB_MYSQL_57, 'composer_require_extra' => '', 'composer_args' => '', @@ -71,8 +68,61 @@ public function createJob(int $phpIndex, array $opts): array ]; return array_merge($default, $opts); } + + private function getPhpVersion(int $phpIndex): string + { + $key = str_replace('.x-dev', '', $this->installerVersion); + $repo = explode('/', $this->githubRepository)[1]; + if (in_array($repo, NO_INSTALLER_LOCKSTEPPED_REPOS)) { + $cmsMajor = $this->getCmsMajorFromBranch(); + $branch = $this->getCleanedBranch(); + if (preg_match('#^[1-9]$#', $branch)) { + $key = $cmsMajor; + } elseif (preg_match('#^[1-9]\.([0-9]+)$#', $branch, $matches)) { + $key = sprintf('%d.%d', $cmsMajor, $matches[1]); + } + } + $phpVersions = INSTALLER_TO_PHP_VERSIONS[$key] ?? INSTALLER_TO_PHP_VERSIONS['4']; + return $phpVersions[$phpIndex] ?? $phpVersions[count($phpVersions) - 1]; + } + + private function getCmsMajorFromBranch(): string + { + $branch = $this->getCleanedBranch(); + $repo = explode('/', $this->githubRepository)[1]; + $branchMajor = ''; + if (preg_match('#^[1-9]$#', $branch)) { + $branchMajor = $branch; + } elseif (preg_match('#^([1-9])\.[0-9]+$#', $branch, $matches)) { + $branchMajor = $matches[1]; + } + foreach (array_keys(CMS_TO_REPO_MAJOR_VERSIONS) as $cmsMajor) { + if (isset(CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$repo])) { + if (CMS_TO_REPO_MAJOR_VERSIONS[$cmsMajor][$repo] === $branchMajor) { + return $cmsMajor; + } + } + } + // For CMS 5 support on pull-requests, will probably need to file_get_contents composer.json to see + // the version of framework, or other, required in composer.json + // For now, assuming CMS 4 should should be acceptable + return '4'; + } + + private function getCleanedBranch(): string + { + $branch = $this->branch; + // e.g. pulls/4.10/some-bugfix or pulls/4/some-feature + // for push events to the creative-commoners account + if (preg_match('#^pulls/([0-9\.]+)/#', $branch, $matches)) { + $branch = $matches[1]; + } + // e.g. 4.10-release + $branch = preg_replace('#^([0-9\.]+)-release$#', '$1', $branch); + return $branch; + } - private function parseBoolValue(mixed $value): bool + private function parseBoolValue($value): bool { return ($value === true || $value === 'true'); } @@ -81,8 +131,7 @@ private function createPhpunitJobs( array $matrix, bool $simpleMatrix, string $suite, - array $run, - string $githubRepository + array $run ): array { if ($simpleMatrix) { $matrix['include'][] = $this->createJob(0, [ @@ -103,7 +152,7 @@ private function createPhpunitJobs( // this same mysql pdo test is also created for the phpcoverage job, so only add it here if // not creating a phpcoverage job. // note: phpcoverage also runs unit tests - if (!$this->doRunPhpCoverage($run, $githubRepository)) { + if (!$this->doRunPhpCoverage($run)) { $matrix['include'][] = $this->createJob(2, [ 'db' => DB_MYSQL_57_PDO, 'phpunit' => true, @@ -119,10 +168,10 @@ private function createPhpunitJobs( return $matrix; } - private function doRunPhpCoverage(array $run, string $githubRepository): bool + private function doRunPhpCoverage(array $run): bool { // always run on silverstripe account, unless phpcoverage_force_off is set to true - if (preg_match('#^silverstripe/#', $githubRepository)) { + if (preg_match('#^silverstripe/#', $this->githubRepository)) { return !$run['phpcoverage_force_off']; } return $run['phpcoverage']; @@ -131,8 +180,7 @@ private function doRunPhpCoverage(array $run, string $githubRepository): bool private function buildDynamicMatrix( array $matrix, array $run, - bool $simpleMatrix, - string $githubRepository + bool $simpleMatrix ): array { if ($run['phpunit'] && (file_exists('phpunit.xml') || file_exists('phpunit.xml.dist'))) { $dom = new DOMDocument(); @@ -145,21 +193,21 @@ private function buildDynamicMatrix( continue; } $suite = $testsuite->getAttribute('name'); - $matrix = $this->createPhpunitJobs($matrix, $simpleMatrix, $suite, $run, $githubRepository); + $matrix = $this->createPhpunitJobs($matrix, $simpleMatrix, $suite, $run); } // phpunit.xml has no defined testsuites, or only defaults a "Default" if (count($matrix['include']) == 0) { - $matrix = $this->createPhpunitJobs($matrix, $simpleMatrix, 'all', $run, $githubRepository); + $matrix = $this->createPhpunitJobs($matrix, $simpleMatrix, 'all', $run); } } // skip phpcs on silverstripe-installer which include sample file for use in projects - if ($run['phplinting'] && (file_exists('phpcs.xml') || file_exists('phpcs.xml.dist')) && !preg_match('#/silverstripe-installer$#', $githubRepository)) { + if ($run['phplinting'] && (file_exists('phpcs.xml') || file_exists('phpcs.xml.dist')) && !preg_match('#/silverstripe-installer$#', $this->githubRepository)) { $matrix['include'][] = $this->createJob(0, [ 'phplinting' => true ]); } // phpcoverage also runs unit tests - if ($this->doRunPhpCoverage($run, $githubRepository)) { + if ($this->doRunPhpCoverage($run, $this->githubRepository)) { if ($simpleMatrix) { $matrix['include'][] = $this->createJob(0, [ 'phpcoverage' => true @@ -221,8 +269,7 @@ public function createJson(string $yml): string // $myRef will either be a branch for push (i.e cron) and pull-request (target branch), or a semver tag $myRef = $inputs['github_my_ref']; $isTag = preg_match('#^[0-9]+\.[0-9]+\.[0-9]+$#', $myRef, $m); - $branch = $isTag ? sprintf('%d.%d', $m[1], $m[2]) : $myRef; - + $this->branch = $isTag ? sprintf('%d.%d', $m[1], $m[2]) : $myRef; // parent branch is a best attempt to get the parent branch of the branch via bash // it's used for working out the version of installer to use on github push events @@ -230,8 +277,8 @@ public function createJson(string $yml): string $this->parentBranch = $inputs['parent_branch']; } - $githubRepository = $inputs['github_repository']; - $this->installerVersion = $this->getInstallerVersion($githubRepository, $branch); + $this->githubRepository = $inputs['github_repository']; + $this->installerVersion = $this->getInstallerVersion(); $run = []; $extraJobs = []; @@ -265,7 +312,7 @@ public function createJson(string $yml): string $matrix = ['include' => []]; if ($dynamicMatrix) { - $matrix = $this->buildDynamicMatrix($matrix, $run, $simpleMatrix, $githubRepository); + $matrix = $this->buildDynamicMatrix($matrix, $run, $simpleMatrix); } // extra jobs diff --git a/tests/JobCreatorTest.php b/tests/JobCreatorTest.php index 55b7888..435d507 100644 --- a/tests/JobCreatorTest.php +++ b/tests/JobCreatorTest.php @@ -4,6 +4,64 @@ class JobCreatorTest extends TestCase { + /** + * @dataProvider provideCreateJob + */ + public function testCreateJob( + string $githubRepository, + string $branch, + int $phpIndex, + array $opts, + array $expected + ): void { + $creator = new JobCreator(); + $creator->githubRepository = $githubRepository; + $creator->branch = $branch; + $actual = $creator->createJob($phpIndex, $opts); + foreach ($expected as $key => $expectedVal) { + $this->assertSame($expectedVal, $actual[$key]); + } + } + + public function provideCreateJob(): array + { + return [ + // general test + ['myaccount/silverstripe-framework', '4', 0, ['phpunit' => true], [ + 'installer_version' => '', + 'php' => '7.4', + 'db' => DB_MYSQL_57, + 'composer_require_extra' => '', + 'composer_args' => '', + 'name_suffix' => '', + 'phpunit' => true, + 'phpunit_suite' => 'all', + 'phplinting' => false, + 'phpcoverage' => false, + 'endtoend' => false, + 'endtoend_suite' => 'root', + 'endtoend_config' => '', + 'js' => false, + ]], + // test that NO_INSTALLER_LOCKSTEPPED_REPOS base max PHP version from $branch + ['myaccount/silverstripe-installer', '4.10', 99, [], [ + 'php' => max(INSTALLER_TO_PHP_VERSIONS['4.10']) + ]], + ['myaccount/silverstripe-installer', '4.11', 99, [], [ + 'php' => max(INSTALLER_TO_PHP_VERSIONS['4.11']) + ]], + ['myaccount/silverstripe-installer', '4', 99, [], [ + 'php' => max(INSTALLER_TO_PHP_VERSIONS['4']) + ]], + ['myaccount/silverstripe-installer', '5.0', 99, [], [ + 'php' => max(INSTALLER_TO_PHP_VERSIONS['5.0']) + ]], + ['myaccount/silverstripe-installer', '5', 99, [], [ + 'php' => max(INSTALLER_TO_PHP_VERSIONS['5']) + ]], + ]; + } + /** * @dataProvider provideGetInstallerVersion */ @@ -13,13 +71,16 @@ public function testGetInstallerVersion( string $expected ): void { $creator = new JobCreator(); - $actual = $creator->getInstallerVersion($githubRepository, $branch); + $creator->githubRepository = $githubRepository; + $creator->branch = $branch; + $actual = $creator->getInstallerVersion(); $this->assertSame($expected, $actual); } - private function getLatestInstallerVersion(): string + private function getLatestInstallerVersion(string $cmsMajor): string { $versions = array_keys(INSTALLER_TO_PHP_VERSIONS); + $versions = array_filter($versions, fn($version) => substr($version, 0, 1) === $cmsMajor); natsort($versions); $versions = array_reverse($versions); return $versions[0]; @@ -27,7 +88,7 @@ private function getLatestInstallerVersion(): string public function provideGetInstallerVersion(): array { - $latest = $this->getLatestInstallerVersion() . '.x-dev'; + $latest = $this->getLatestInstallerVersion('4') . '.x-dev'; return [ // no-installer repo ['myaccount/recipe-cms', '4', ''], @@ -68,6 +129,110 @@ public function provideGetInstallerVersion(): array ]; } + /** + * @dataProvider provideCreateJson + */ + public function testCreateJson(string $yml, array $expected) + { + if (!function_exists('yaml_parse')) { + $this->markTestSkipped('yaml extension is not installed'); + } + $creator = new JobCreator(); + $json = json_decode($creator->createJson($yml)); + for ($i = 0; $i < count($expected); $i++) { + foreach ($expected[$i] as $key => $expectedVal) { + $this->assertSame($expectedVal, $json->include[$i]->$key); + } + } + } + + public function provideCreateJson(): array + { + return [ + // general test + [ + implode("\n", [ + $this->getGenericYml(), + << '4.11.x-dev', + 'php' => '7.4', + 'db' => DB_MYSQL_57, + 'composer_require_extra' => '', + 'composer_args' => '--prefer-lowest', + 'name_suffix' => '', + 'phpunit' => 'true', + 'phpunit_suite' => 'all', + 'phplinting' => 'false', + 'phpcoverage' => 'false', + 'endtoend' => 'false', + 'endtoend_suite' => 'root', + 'endtoend_config' => '', + 'js' => 'false', + 'name' => '7.4 prf-low mysql57 phpunit all', + ], + [ + 'installer_version' => '4.11.x-dev', + 'php' => '8.0', + 'db' => DB_PGSQL, + 'composer_require_extra' => '', + 'composer_args' => '', + 'name_suffix' => '', + 'phpunit' => 'true', + 'phpunit_suite' => 'all', + 'phplinting' => 'false', + 'phpcoverage' => 'false', + 'endtoend' => 'false', + 'endtoend_suite' => 'root', + 'endtoend_config' => '', + 'js' => 'false', + 'name' => '8.0 pgsql phpunit all', + ], + [ + 'installer_version' => '4.11.x-dev', + 'php' => '8.1', + 'db' => DB_MYSQL_57_PDO, + 'composer_require_extra' => '', + 'composer_args' => '', + 'name_suffix' => '', + 'phpunit' => 'true', + 'phpunit_suite' => 'all', + 'phplinting' => 'false', + 'phpcoverage' => 'false', + 'endtoend' => 'false', + 'endtoend_suite' => 'root', + 'endtoend_config' => '', + 'js' => 'false', + 'name' => '8.1 mysql57pdo phpunit all', + ], + [ + 'installer_version' => '4.11.x-dev', + 'php' => '8.1', + 'db' => DB_MYSQL_80, + 'composer_require_extra' => '', + 'composer_args' => '', + 'name_suffix' => '', + 'phpunit' => 'true', + 'phpunit_suite' => 'all', + 'phplinting' => 'false', + 'phpcoverage' => 'false', + 'endtoend' => 'false', + 'endtoend_suite' => 'root', + 'endtoend_config' => '', + 'js' => 'false', + 'name' => '8.1 mysql80 phpunit all', + ], + ] + ], + ]; + } + /** * @dataProvider provideParentBranch */ @@ -96,7 +261,7 @@ private function getGenericYml(): string public function provideParentBranch(): array { - $latest = $this->getLatestInstallerVersion() . '.x-dev'; + $latest = $this->getLatestInstallerVersion('4') . '.x-dev'; return [ [ implode("\n", [