Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,26 @@ jobs:
if: matrix.php != '8.1'
run: phpunit -c tests/phpunit.xml.dist --testsuite unit

# the Joomla!/PHP/database combinations live in tests/ci/matrix.json, a file
# which the joomla-versions workflow can change (a workflow file it can't)
matrix:
name: Test matrix
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.read.outputs.matrix }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- id: read
run: |
echo "matrix=$(jq -c '{include: .include}' tests/ci/matrix.json)" >> "$GITHUB_OUTPUT"

integration:
name: Joomla ${{ matrix.joomla }}, ${{ matrix.db }}, PHP ${{ matrix.php }}
needs: matrix
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- { joomla: 5.4.8, php: '8.2', db: 'mysql:8.0' }
- { joomla: 5.4.8, php: '8.2', db: 'mariadb:10.11' }
- { joomla: 5.4.8, php: '8.2', db: 'postgres:12' }
- { joomla: 6.1.3, php: '8.4', db: 'mysql:8.4' }
- { joomla: 6.1.3, php: '8.4', db: 'mariadb:11.4' }
- { joomla: 6.1.3, php: '8.4', db: 'postgres:17' }
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
env:
JOOMLA_VERSION: ${{ matrix.joomla }}
DB_HOST: 127.0.0.1
Expand Down
9 changes: 6 additions & 3 deletions .github/workflows/joomla-versions.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
name: Joomla versions

# The integration tests of ci.yml run on one Joomla! version per major version
# (the matrix), each with a SHA-256 in tests/ci/joomla-checksums.sha256. Dependabot
# (the matrix in tests/ci/matrix.json), each with a SHA-256 in
# tests/ci/joomla-checksums.sha256. The matrix is a data file, not part of the
# workflow file, because the token of a workflow can't push changes to workflow
# files (GitHub requires a separate `workflows` permission it can't have). Dependabot
# can't follow those, so this weekly job looks at the releases of joomla/joomla-cms:
#
# - a newer patch release of a version in the matrix: a pull request that raises
Expand Down Expand Up @@ -52,7 +55,7 @@ jobs:
known="$(gh issue list --state all --search "in:title Joomla $line" --json number,title \
| jq -r --arg t "$title" 'map(select(.title == $t)) | .[0].number // empty')"
if [ -z "$known" ]; then
url="$(gh issue create --title "$title" --body "Joomla! $version (line $line) is released and newer than the versions the CI matrix tests. To support it: raise or extend the matrix in \`.github/workflows/ci.yml\` (and in com_bfstop), add the package's SHA-256 to \`tests/ci/joomla-checksums.sha256\` (the file says how to check it), and see what the tests say. Opened by the weekly job $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID; it doesn't ask again for this line.")"
url="$(gh issue create --title "$title" --body "Joomla! $version (line $line) is released and newer than the versions the CI matrix tests. To support it: raise or extend the matrix in \`tests/ci/matrix.json\` (and in com_bfstop's \`ci.yml\`), add the package's SHA-256 to \`tests/ci/joomla-checksums.sha256\` (the file says how to check it), and see what the tests say. Opened by the weekly job $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID; it doesn't ask again for this line.")"
# the label is a nicety: the repository may not have it
gh issue edit "$url" --add-label dependencies || true
fi
Expand Down Expand Up @@ -107,7 +110,7 @@ jobs:
git commit -a -q -m "CI: Joomla $versions"
# nothing new since the last run (the matrix and checksum file are what differs)?
if git fetch -q origin joomla-versions 2> /dev/null \
&& git diff --quiet FETCH_HEAD HEAD -- .github/workflows/ci.yml tests/ci/joomla-checksums.sha256; then
&& git diff --quiet FETCH_HEAD HEAD -- tests/ci/matrix.json tests/ci/joomla-checksums.sha256; then
echo "The branch joomla-versions has these versions already."
exit 0
fi
Expand Down
9 changes: 6 additions & 3 deletions tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,14 @@ branch that was merged and not deleted is therefore ignored, not tested against.

### Joomla versions of the CI matrix

The matrix runs one Joomla version per major version, each with its SHA-256 in
`tests/ci/joomla-checksums.sha256`. Dependabot can't follow those, so
The matrix runs one Joomla version per major version. It is `tests/ci/matrix.json`
(which `ci.yml` reads in a first job), each version with its SHA-256 in
`tests/ci/joomla-checksums.sha256`; raise a version in both files, the lint
checks that they agree. Dependabot can't follow those, so
`.github/workflows/joomla-versions.yml` looks at the releases of
`joomla/joomla-cms` every week (and on demand: Actions, "Joomla versions", Run
workflow):
workflow - not "Re-run" of an old run, which uses the workflow file of that
run):

- a newer **patch release** of a version in the matrix gets a pull request
(branch `joomla-versions`) that raises the matrix and adds the checksum. The
Expand Down
34 changes: 22 additions & 12 deletions tests/Unit/JoomlaUpdatesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
*/
class JoomlaUpdatesTest extends TestCase
{
private const Workflow = " matrix:\n include:\n"
." - { joomla: 5.4.8, php: '8.2', db: 'mysql:8.0' }\n"
." - { joomla: 5.4.8, php: '8.2', db: 'postgres:12' }\n"
." - { joomla: 6.1.3, php: '8.4', db: 'mysql:8.4' }\n";
private const Matrix = "{\n \"_\": \"note\",\n \"include\": [\n"
." { \"joomla\": \"5.4.8\", \"php\": \"8.2\", \"db\": \"mysql:8.0\" },\n"
." { \"joomla\": \"5.4.8\", \"php\": \"8.2\", \"db\": \"postgres:12\" },\n"
." { \"joomla\": \"6.1.3\", \"php\": \"8.4\", \"db\": \"mysql:8.4\" }\n"
." ]\n}\n";

private const Hash8 = '2c9dc7e6f451c83c43bbb7eb5c9348cb889ba802b72915c5bbd899f9d2de3f3d';
private const Hash13 = '184f8c582cde5981693de7c28547c6e834c48c50cb377c7b8421bbfd33bbdf6f';
Expand Down Expand Up @@ -54,7 +55,14 @@ private static function release($tag, $options = array())

public function testMatrixVersions()
{
$this->assertSame(array('5.4.8', '6.1.3'), matrixVersions(self::Workflow));
$this->assertSame(array('5.4.8', '6.1.3'), matrixVersions(self::Matrix));
}

public function testMatrixVersionsOfUnusableFiles()
{
$this->assertSame(array(), matrixVersions('not json'));
$this->assertSame(array(), matrixVersions('{"include": "x"}'));
$this->assertSame(array('5.4.8'), matrixVersions('{"include": [{"joomla": "5.4.8"}, {"joomla": "5.x"}, {"php": "8.2"}, 7]}'));
}

public function testStableReleasesIgnoreDraftsPrereleasesOddTagsAndMissingPackages()
Expand Down Expand Up @@ -100,10 +108,12 @@ public function testNothingToDoWhenCurrent()

public function testApplyReplacesTheVersionAndAddsTheChecksum()
{
list($workflow, $checksums) = apply(self::Workflow, self::Checksums, '5.4.8', '5.4.9', self::Hash9);
$this->assertSame(0, substr_count($workflow, '5.4.8'));
$this->assertSame(2, substr_count($workflow, 'joomla: 5.4.9,'));
$this->assertSame(1, substr_count($workflow, 'joomla: 6.1.3,'));
list($matrix, $checksums) = apply(self::Matrix, self::Checksums, '5.4.8', '5.4.9', self::Hash9);
$this->assertSame(0, substr_count($matrix, '5.4.8'));
$this->assertSame(2, substr_count($matrix, '"joomla": "5.4.9"'));
$this->assertSame(1, substr_count($matrix, '"joomla": "6.1.3"'));
$this->assertSame(strtr(self::Matrix, array('5.4.8' => '5.4.9')), $matrix, 'nothing else changes, formatting included');
$this->assertIsArray(json_decode($matrix, true), 'still JSON');

$lines = explode("\n", rtrim($checksums));
$this->assertContains(self::Hash8.' Joomla_5.4.8-Stable-Full_Package.tar.gz', $lines, 'the old entry stays');
Expand All @@ -120,8 +130,8 @@ public function testApplyReplacesTheVersionAndAddsTheChecksum()

public function testApplyKeepsWhatLintAndInstallerRead()
{
list($workflow, $checksums) = apply(self::Workflow, self::Checksums, '6.1.3', '6.1.4', self::Hash9);
$this->assertSame(array('5.4.8', '6.1.4'), matrixVersions($workflow));
list($matrix, $checksums) = apply(self::Matrix, self::Checksums, '6.1.3', '6.1.4', self::Hash9);
$this->assertSame(array('5.4.8', '6.1.4'), matrixVersions($matrix));
foreach (explode("\n", rtrim($checksums)) as $line)
{
$this->assertTrue($line === '' || $line[0] === '#'
Expand All @@ -133,7 +143,7 @@ public function testApplyKeepsWhatLintAndInstallerRead()
public function testApplyRefusesWhatItCannotDoSafely($from, $to, $hash, $checksums = self::Checksums)
{
$this->expectException(InvalidArgumentException::class);
apply(self::Workflow, $checksums, $from, $to, $hash);
apply(self::Matrix, $checksums, $from, $to, $hash);
}

public static function badApplications()
Expand Down
2 changes: 1 addition & 1 deletion tests/ci/joomla-checksums.sha256
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SHA-256 of the Joomla! full packages the integration tests install
# (tests/ci/download-joomla.sh refuses a package that doesn't match). The same
# versions have to be listed here as in the matrix of .github/workflows/ci.yml
# versions have to be listed here as in the matrix tests/ci/matrix.json
# (checked by tests/lint/check-joomla-checksums.php). The workflow of com_bfstop
# installs through the same script, so a version it uses has to be listed here.
#
Expand Down
38 changes: 24 additions & 14 deletions tests/ci/joomla-updates.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
* @copyright (C) Bernhard Froehler
* @license GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
**/
// Finds newer Joomla! releases for the matrix of .github/workflows/ci.yml and
// applies them; used by .github/workflows/joomla-versions.yml.
// Finds newer Joomla! releases for the matrix of the integration tests
// (tests/ci/matrix.json, read by .github/workflows/ci.yml) and applies them;
// used by .github/workflows/joomla-versions.yml.
//
// php tests/ci/joomla-updates.php plan RELEASES.json
// RELEASES.json is the answer of the GitHub API for
Expand All @@ -24,11 +25,20 @@

const JoomlaUpdatesPackage = 'Joomla_%s-Stable-Full_Package.tar.gz';

/** @return string[] the Joomla! versions of the matrix, ascending */
function matrixVersions($workflow)
/** @return string[] the Joomla! versions of the matrix (the text of tests/ci/matrix.json), ascending */
function matrixVersions($matrixJson)
{
preg_match_all('/\bjoomla:\s*(\d+\.\d+\.\d+)\b/', $workflow, $matches);
$versions = array_values(array_unique($matches[1]));
$matrix = json_decode($matrixJson, true);
$entries = is_array($matrix) && is_array($matrix['include'] ?? null) ? $matrix['include'] : array();
$versions = array();
foreach ($entries as $entry)
{
if (is_array($entry) && isset($entry['joomla']) && preg_match('/^\d+\.\d+\.\d+$/', $entry['joomla']))
{
$versions[$entry['joomla']] = true;
}
}
$versions = array_keys($versions);
usort($versions, 'version_compare');
return $versions;
}
Expand Down Expand Up @@ -104,8 +114,8 @@ function plan(array $matrix, array $stable)
return array('matrix' => $matrix, 'bumps' => $bumps, 'new_lines' => $newLines);
}

/** @return array(string, string) workflow and checksum file with $to instead of $from */
function apply($workflow, $checksums, $from, $to, $sha256)
/** @return array(string, string) matrix and checksum file with $to instead of $from */
function apply($matrixJson, $checksums, $from, $to, $sha256)
{
foreach (array($from, $to) as $version)
{
Expand All @@ -122,7 +132,7 @@ function apply($workflow, $checksums, $from, $to, $sha256)
{
throw new InvalidArgumentException("$to is not a newer release of the $from line");
}
$workflow = preg_replace('/\bjoomla:(\s*)'.preg_quote($from, '/').'\b/', 'joomla:${1}'.$to, $workflow, -1, $count);
$matrixJson = preg_replace('/("joomla":\s*")'.preg_quote($from, '/').'(")/', '${1}'.$to.'${2}', $matrixJson, -1, $count);
if ($count === 0)
{
throw new InvalidArgumentException("Joomla $from is not in the matrix");
Expand All @@ -144,13 +154,13 @@ function apply($workflow, $checksums, $from, $to, $sha256)
$note = "# $to taken from the GitHub release asset by the joomla-versions workflow;\n"
."# compare with downloads.joomla.org/cms/joomla$major/".str_replace('.', '-', $to);
$checksums = preg_replace('/^(?!#)/m', $note."\n", $checksums, 1);
return array($workflow, $checksums);
return array($matrixJson, $checksums);
}

function main(array $argv)
{
$root = dirname(__DIR__, 2);
$workflowFile = $root.'/.github/workflows/ci.yml';
$matrixFile = $root.'/tests/ci/matrix.json';
$checksumFile = $root.'/tests/ci/joomla-checksums.sha256';
$command = $argv[1] ?? '';
if ($command === 'plan' && count($argv) === 3)
Expand All @@ -161,22 +171,22 @@ function main(array $argv)
fwrite(STDERR, "$argv[2] is not a list of releases\n");
return 2;
}
echo json_encode(plan(matrixVersions(file_get_contents($workflowFile)), stableReleases($releases)), JSON_PRETTY_PRINT)."\n";
echo json_encode(plan(matrixVersions(file_get_contents($matrixFile)), stableReleases($releases)), JSON_PRETTY_PRINT)."\n";
return 0;
}
if ($command === 'apply' && count($argv) === 5)
{
try
{
list($workflow, $checksums) = apply(file_get_contents($workflowFile), file_get_contents($checksumFile),
list($matrix, $checksums) = apply(file_get_contents($matrixFile), file_get_contents($checksumFile),
$argv[2], $argv[3], $argv[4]);
}
catch (InvalidArgumentException $e)
{
fwrite(STDERR, $e->getMessage()."\n");
return 1;
}
file_put_contents($workflowFile, $workflow);
file_put_contents($matrixFile, $matrix);
file_put_contents($checksumFile, $checksums);
return 0;
}
Expand Down
11 changes: 11 additions & 0 deletions tests/ci/matrix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"_": "Matrix of the integration jobs in .github/workflows/ci.yml. The Joomla versions need a SHA-256 in tests/ci/joomla-checksums.sha256 (checked by tests/lint/check-joomla-checksums.php). .github/workflows/joomla-versions.yml raises the patch versions; keep one entry per line.",
"include": [
{ "joomla": "5.4.8", "php": "8.2", "db": "mysql:8.0" },
{ "joomla": "5.4.8", "php": "8.2", "db": "mariadb:10.11" },
{ "joomla": "5.4.8", "php": "8.2", "db": "postgres:12" },
{ "joomla": "6.1.3", "php": "8.4", "db": "mysql:8.4" },
{ "joomla": "6.1.3", "php": "8.4", "db": "mariadb:11.4" },
{ "joomla": "6.1.3", "php": "8.4", "db": "postgres:17" }
]
}
39 changes: 32 additions & 7 deletions tests/lint/check-joomla-checksums.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
// The integration tests only install a Joomla! package whose SHA-256 is in
// tests/ci/joomla-checksums.sha256 (see tests/ci/download-joomla.sh). This
// checks that the file is well-formed and lists every Joomla! version of the
// matrix of .github/workflows/ci.yml, so that raising a version without its
// checksum fails here, not in half a dozen integration jobs.
// matrix of the integration tests (tests/ci/matrix.json, read by
// .github/workflows/ci.yml; also checked for being well-formed), so that
// raising a version without its checksum fails here, not in half a dozen
// integration jobs.
//
// Exits with status 1 on an error.

Expand Down Expand Up @@ -41,24 +43,47 @@
}

$used = array();
preg_match_all('/\bjoomla:\s*(\d+\.\d+\.\d+)\b/', file_get_contents($root.'/.github/workflows/ci.yml'), $matches);
foreach (array_unique($matches[1]) as $version)
$matrix = json_decode(file_get_contents($root.'/tests/ci/matrix.json'), true);
if (!is_array($matrix) || !is_array($matrix['include'] ?? null) || count($matrix['include']) === 0)
{
echo "ERROR: tests/ci/matrix.json is not JSON with a non-empty \"include\" list\n";
++$errors;
$matrix = array('include' => array());
}
foreach ($matrix['include'] as $number => $entry)
{
$where = "tests/ci/matrix.json: entry ".($number + 1);
foreach (array('joomla', 'php', 'db') as $key)
{
if (!is_array($entry) || !isset($entry[$key]) || !is_string($entry[$key]) || $entry[$key] === '')
{
echo "ERROR: $where has no \"$key\" (string)\n";
++$errors;
continue 2;
}
}
$version = $entry['joomla'];
if (!preg_match('/^\d+\.\d+\.\d+$/', $version))
{
echo "ERROR: $where: \"$version\" is not a Joomla version like 5.4.8\n";
++$errors;
continue;
}
$used[$version] = true;
if (!isset($listed[$version]))
{
echo "ERROR: Joomla $version is in the matrix of .github/workflows/ci.yml, but has no checksum in tests/ci/joomla-checksums.sha256\n";
echo "ERROR: Joomla $version is in tests/ci/matrix.json, but has no checksum in tests/ci/joomla-checksums.sha256\n";
++$errors;
}
}
if (count($used) === 0)
{
echo "ERROR: no Joomla version found in the matrix of .github/workflows/ci.yml\n";
echo "ERROR: no Joomla version found in tests/ci/matrix.json\n";
++$errors;
}
foreach (array_diff_key($listed, $used) as $version => $unused)
{
echo "NOTE: Joomla $version has a checksum, but is not in the matrix of ci.yml\n";
echo "NOTE: Joomla $version has a checksum, but is not in tests/ci/matrix.json\n";
}
echo count($used)." version(s) of the matrix checked against ".count($listed)." checksum(s), $errors error(s)\n";
exit($errors > 0 ? 1 : 0);
Loading