From c05a0f4be00e35fe4f3997cfaa5f2e353947a6f7 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 9 Jun 2026 10:48:00 +0200 Subject: [PATCH 1/3] test(release-tools): add golden-file snapshot tests for milestone + tagger Signed-off-by: skjnldsv --- tools/release/phpunit.xml.dist | 2 + tools/release/tests/MilestoneSnapshotTest.php | 109 ++++++++++++++++++ .../tests/Support/MatchesSnapshots.php | 38 ++++++ tools/release/tests/TaggerSnapshotTest.php | 91 +++++++++++++++ .../tests/snapshots/milestones/due-dates.snap | 3 + .../snapshots/milestones/first-beta.snap | 2 + .../snapshots/milestones/first-stable.snap | 3 + .../snapshots/milestones/missing-next.snap | 4 + .../snapshots/milestones/multi-repo.snap | 6 + .../snapshots/milestones/patch-release.snap | 4 + .../snapshots/milestones/prerelease-noop.snap | 1 + .../tests/snapshots/tagger/dry-run.snap | 4 + .../release/tests/snapshots/tagger/force.snap | 6 + .../release/tests/snapshots/tagger/mixed.snap | 10 ++ 14 files changed, 283 insertions(+) create mode 100644 tools/release/tests/MilestoneSnapshotTest.php create mode 100644 tools/release/tests/Support/MatchesSnapshots.php create mode 100644 tools/release/tests/TaggerSnapshotTest.php create mode 100644 tools/release/tests/snapshots/milestones/due-dates.snap create mode 100644 tools/release/tests/snapshots/milestones/first-beta.snap create mode 100644 tools/release/tests/snapshots/milestones/first-stable.snap create mode 100644 tools/release/tests/snapshots/milestones/missing-next.snap create mode 100644 tools/release/tests/snapshots/milestones/multi-repo.snap create mode 100644 tools/release/tests/snapshots/milestones/patch-release.snap create mode 100644 tools/release/tests/snapshots/milestones/prerelease-noop.snap create mode 100644 tools/release/tests/snapshots/tagger/dry-run.snap create mode 100644 tools/release/tests/snapshots/tagger/force.snap create mode 100644 tools/release/tests/snapshots/tagger/mixed.snap diff --git a/tools/release/phpunit.xml.dist b/tools/release/phpunit.xml.dist index d10c471de14..e0f8f992291 100644 --- a/tools/release/phpunit.xml.dist +++ b/tools/release/phpunit.xml.dist @@ -19,6 +19,7 @@ tests/MilestoneUpdaterTest.php + tests/MilestoneSnapshotTest.php @@ -27,6 +28,7 @@ tests/RepoTaggerTest.php + tests/TaggerSnapshotTest.php diff --git a/tools/release/tests/MilestoneSnapshotTest.php b/tools/release/tests/MilestoneSnapshotTest.php new file mode 100644 index 00000000000..2f5e213c9a2 --- /dev/null +++ b/tools/release/tests/MilestoneSnapshotTest.php @@ -0,0 +1,109 @@ + + * expected" feel as the bash harness, so a behaviour change shows up as a + * readable snapshot diff. Update with UPDATE_SNAPSHOTS=1. + */ +final class MilestoneSnapshotTest extends TestCase +{ + use MatchesSnapshots; + + private const SERVER = 'nextcloud/server'; + private const ACTIVITY = 'nextcloud/activity'; + + private function snapshot(FakeGitHubApi $api): string + { + return $api->journal === [] ? "(no changes)" : implode("\n", $api->journal); + } + + public function testPatchRelease(): void + { + $api = new FakeGitHubApi(); + $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.4', 'open', 2); + $api->seedMilestone(self::SERVER, 11, 'Nextcloud 33.0.5'); + $api->seedIssue(self::SERVER, 100, 10); + $api->seedIssue(self::SERVER, 101, 10); + + (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER]); + $this->assertMatchesSnapshot('milestones/patch-release', $this->snapshot($api)); + } + + public function testFirstStable(): void + { + $api = new FakeGitHubApi(); + $api->seedMilestone(self::SERVER, 20, 'Nextcloud 34'); + (new MilestoneUpdater($api))->run(Version::fromTag('v34.0.0'), [self::SERVER]); + $this->assertMatchesSnapshot('milestones/first-stable', $this->snapshot($api)); + } + + public function testFirstBeta(): void + { + $api = new FakeGitHubApi(); + (new MilestoneUpdater($api))->run(Version::fromTag('v35.0.0beta1'), [self::SERVER, self::ACTIVITY]); + $this->assertMatchesSnapshot('milestones/first-beta', $this->snapshot($api)); + } + + public function testMissingNext(): void + { + $api = new FakeGitHubApi(); + $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.4', 'open', 1); + $api->seedIssue(self::SERVER, 100, 10); + (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER]); + $this->assertMatchesSnapshot('milestones/missing-next', $this->snapshot($api)); + } + + public function testDueDates(): void + { + $api = new FakeGitHubApi(); + $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.4'); + $api->seedMilestone(self::SERVER, 11, 'Nextcloud 33.0.5', 'open', 0, '2026-06-25T00:00:00Z'); + $api->seedMilestone(self::SERVER, 12, 'Nextcloud 33.0.6'); + (new MilestoneUpdater($api))->run( + Version::fromTag('v33.0.4'), + [self::SERVER], + '2026-07-02T00:00:00Z', + '2026-08-27T00:00:00Z', + ); + $this->assertMatchesSnapshot('milestones/due-dates', $this->snapshot($api)); + } + + public function testMultiRepo(): void + { + $api = new FakeGitHubApi(); + foreach ([self::SERVER, self::ACTIVITY] as $i => $repo) { + $base = ($i + 1) * 10; + $api->seedMilestone($repo, $base, 'Nextcloud 33.0.4', 'open', 1); + $api->seedMilestone($repo, $base + 1, 'Nextcloud 33.0.5'); + $api->seedIssue($repo, 500 + $i, $base); + } + (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER, self::ACTIVITY]); + $this->assertMatchesSnapshot('milestones/multi-repo', $this->snapshot($api)); + } + + public function testPrereleaseNoop(): void + { + $api = new FakeGitHubApi(); + $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.2'); + (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.2rc1'), [self::SERVER]); + $this->assertMatchesSnapshot('milestones/prerelease-noop', $this->snapshot($api)); + } +} diff --git a/tools/release/tests/Support/MatchesSnapshots.php b/tools/release/tests/Support/MatchesSnapshots.php new file mode 100644 index 00000000000..22567523ac8 --- /dev/null +++ b/tools/release/tests/Support/MatchesSnapshots.php @@ -0,0 +1,38 @@ +.snap`. Missing snapshots are written on first run; + * re-run with `UPDATE_SNAPSHOTS=1` to regenerate after an intended change. + */ +trait MatchesSnapshots +{ + protected function assertMatchesSnapshot(string $name, string $actual): void + { + $file = __DIR__ . '/../snapshots/' . $name . '.snap'; + $actual = rtrim($actual, "\n") . "\n"; + + if (getenv('UPDATE_SNAPSHOTS') === '1' || !is_file($file)) { + if (!is_dir(dirname($file))) { + mkdir(dirname($file), 0o777, true); + } + file_put_contents($file, $actual); + $this->addToAssertionCount(1); + return; + } + + $this->assertSame( + file_get_contents($file), + $actual, + "Snapshot '{$name}' differs. Re-run with UPDATE_SNAPSHOTS=1 if the change is intended.", + ); + } +} diff --git a/tools/release/tests/TaggerSnapshotTest.php b/tools/release/tests/TaggerSnapshotTest.php new file mode 100644 index 00000000000..7fc386f42b2 --- /dev/null +++ b/tools/release/tests/TaggerSnapshotTest.php @@ -0,0 +1,91 @@ +repo, $r->status, $r->branch, $r->detail); + } + $lines[] = ''; + $lines[] = 'journal:'; + foreach ($api->journal as $j) { + $lines[] = ' ' . $j; + } + return implode("\n", $lines); + } + + public function testMixedRun(): void + { + $api = new FakeGitHubApi(); + // a normal app on the release branch + $api->seedBranch('nextcloud/activity', 'stable34', 'sha-activity', true); + // an app where the tag already exists (will skip without force) + $api->seedBranch('nextcloud/notes', 'stable34', 'sha-notes', true); + $api->seedTag('nextcloud/notes', 'v34.0.1', 'old-notes'); + // an app with no stable34, only a default branch (fallback) + $api->seedBranch('nextcloud/photos', 'main', 'sha-photos', true); + // the server repo, tag exists - must never be recreated even with force + $api->seedBranch('nextcloud/server', 'stable34', 'sha-server', true); + $api->seedTag('nextcloud/server', 'v34.0.1', 'old-server'); + // a repo with no branch at all (fails) + // (nextcloud/ghost: nothing seeded) + + $repos = ['nextcloud/activity', 'nextcloud/notes', 'nextcloud/photos', 'nextcloud/server', 'nextcloud/ghost']; + $tagger = new RepoTagger($api); + $results = array_map( + static fn (string $repo) => $tagger->tag($repo, 'stable34', 'v34.0.1', false), + $repos, + ); + + $this->assertMatchesSnapshot('tagger/mixed', $this->render($results, $api)); + } + + public function testForceRun(): void + { + $api = new FakeGitHubApi(); + $api->seedBranch('nextcloud/activity', 'stable34', 'sha-activity', true); + $api->seedTag('nextcloud/activity', 'v34.0.1', 'old'); + $api->seedBranch('nextcloud/server', 'stable34', 'sha-server', true); + $api->seedTag('nextcloud/server', 'v34.0.1', 'old-server'); + + $tagger = new RepoTagger($api); + $results = [ + $tagger->tag('nextcloud/activity', 'stable34', 'v34.0.1', true), + $tagger->tag('nextcloud/server', 'stable34', 'v34.0.1', true), + ]; + $this->assertMatchesSnapshot('tagger/force', $this->render($results, $api)); + } + + public function testDryRun(): void + { + $api = new FakeGitHubApi(); + $api->seedBranch('nextcloud/activity', 'stable34', 'sha-activity', true); + $results = [(new RepoTagger($api, dryRun: true))->tag('nextcloud/activity', 'stable34', 'v34.0.1', false)]; + $this->assertMatchesSnapshot('tagger/dry-run', $this->render($results, $api)); + } +} diff --git a/tools/release/tests/snapshots/milestones/due-dates.snap b/tools/release/tests/snapshots/milestones/due-dates.snap new file mode 100644 index 00000000000..02771fa9d89 --- /dev/null +++ b/tools/release/tests/snapshots/milestones/due-dates.snap @@ -0,0 +1,3 @@ +setdue nextcloud/server 11 2026-07-02T00:00:00Z +close nextcloud/server 10 +setdue nextcloud/server 12 2026-08-27T00:00:00Z diff --git a/tools/release/tests/snapshots/milestones/first-beta.snap b/tools/release/tests/snapshots/milestones/first-beta.snap new file mode 100644 index 00000000000..5d98dff71e7 --- /dev/null +++ b/tools/release/tests/snapshots/milestones/first-beta.snap @@ -0,0 +1,2 @@ +create nextcloud/server Nextcloud 36 due=- +create nextcloud/activity Nextcloud 36 due=- diff --git a/tools/release/tests/snapshots/milestones/first-stable.snap b/tools/release/tests/snapshots/milestones/first-stable.snap new file mode 100644 index 00000000000..a2b4789a472 --- /dev/null +++ b/tools/release/tests/snapshots/milestones/first-stable.snap @@ -0,0 +1,3 @@ +create nextcloud/server Nextcloud 34.0.1 due=- +close nextcloud/server 20 +create nextcloud/server Nextcloud 34.0.2 due=- diff --git a/tools/release/tests/snapshots/milestones/missing-next.snap b/tools/release/tests/snapshots/milestones/missing-next.snap new file mode 100644 index 00000000000..6b42607169b --- /dev/null +++ b/tools/release/tests/snapshots/milestones/missing-next.snap @@ -0,0 +1,4 @@ +create nextcloud/server Nextcloud 33.0.5 due=- +move nextcloud/server 100 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/multi-repo.snap b/tools/release/tests/snapshots/milestones/multi-repo.snap new file mode 100644 index 00000000000..90113f324ab --- /dev/null +++ b/tools/release/tests/snapshots/milestones/multi-repo.snap @@ -0,0 +1,6 @@ +move nextcloud/server 500 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- +move nextcloud/activity 501 21 +close nextcloud/activity 20 +create nextcloud/activity Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/patch-release.snap b/tools/release/tests/snapshots/milestones/patch-release.snap new file mode 100644 index 00000000000..0dcaa7f195a --- /dev/null +++ b/tools/release/tests/snapshots/milestones/patch-release.snap @@ -0,0 +1,4 @@ +move nextcloud/server 100 11 +move nextcloud/server 101 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/prerelease-noop.snap b/tools/release/tests/snapshots/milestones/prerelease-noop.snap new file mode 100644 index 00000000000..d81c6d1b89f --- /dev/null +++ b/tools/release/tests/snapshots/milestones/prerelease-noop.snap @@ -0,0 +1 @@ +(no changes) diff --git a/tools/release/tests/snapshots/tagger/dry-run.snap b/tools/release/tests/snapshots/tagger/dry-run.snap new file mode 100644 index 00000000000..9bcb9e8f71b --- /dev/null +++ b/tools/release/tests/snapshots/tagger/dry-run.snap @@ -0,0 +1,4 @@ +results: + nextcloud/activity OK stable34 would create v34.0.1 @ sha-activity + +journal: diff --git a/tools/release/tests/snapshots/tagger/force.snap b/tools/release/tests/snapshots/tagger/force.snap new file mode 100644 index 00000000000..c30f8eda849 --- /dev/null +++ b/tools/release/tests/snapshots/tagger/force.snap @@ -0,0 +1,6 @@ +results: + nextcloud/activity OK stable34 recreated v34.0.1 + nextcloud/server SKIPPED stable34 already tagged + +journal: + retag nextcloud/activity v34.0.1 sha-activity force=true diff --git a/tools/release/tests/snapshots/tagger/mixed.snap b/tools/release/tests/snapshots/tagger/mixed.snap new file mode 100644 index 00000000000..d29c007178a --- /dev/null +++ b/tools/release/tests/snapshots/tagger/mixed.snap @@ -0,0 +1,10 @@ +results: + nextcloud/activity OK stable34 created v34.0.1 + nextcloud/notes SKIPPED stable34 already tagged + nextcloud/photos OK main created v34.0.1 + nextcloud/server SKIPPED stable34 already tagged + nextcloud/ghost FAILED no branch found + +journal: + tag nextcloud/activity v34.0.1 sha-activity + tag nextcloud/photos v34.0.1 sha-photos From ce81f1e279ac11b2e3759b3becc87d867c1a4344 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 9 Jun 2026 14:17:23 +0200 Subject: [PATCH 2/3] test(release-tools): render snapshots as plain English for readability Signed-off-by: skjnldsv --- tools/release/tests/MilestoneSnapshotTest.php | 3 +- tools/release/tests/Support/Journal.php | 45 +++++++++++++++++++ tools/release/tests/TaggerSnapshotTest.php | 14 +++--- .../tests/snapshots/milestones/due-dates.snap | 6 +-- .../snapshots/milestones/first-beta.snap | 4 +- .../snapshots/milestones/first-stable.snap | 6 +-- .../snapshots/milestones/missing-next.snap | 8 ++-- .../snapshots/milestones/multi-repo.snap | 12 ++--- .../snapshots/milestones/patch-release.snap | 8 ++-- .../tests/snapshots/tagger/dry-run.snap | 7 +-- .../release/tests/snapshots/tagger/force.snap | 10 ++--- .../release/tests/snapshots/tagger/mixed.snap | 18 ++++---- 12 files changed, 95 insertions(+), 46 deletions(-) create mode 100644 tools/release/tests/Support/Journal.php diff --git a/tools/release/tests/MilestoneSnapshotTest.php b/tools/release/tests/MilestoneSnapshotTest.php index 2f5e213c9a2..39f43504a49 100644 --- a/tools/release/tests/MilestoneSnapshotTest.php +++ b/tools/release/tests/MilestoneSnapshotTest.php @@ -9,6 +9,7 @@ use Nextcloud\ReleaseTools\MilestoneUpdater; use Nextcloud\ReleaseTools\Tests\Support\FakeGitHubApi; +use Nextcloud\ReleaseTools\Tests\Support\Journal; use Nextcloud\ReleaseTools\Tests\Support\MatchesSnapshots; use Nextcloud\ReleaseTools\Version; use PHPUnit\Framework\TestCase; @@ -32,7 +33,7 @@ final class MilestoneSnapshotTest extends TestCase private function snapshot(FakeGitHubApi $api): string { - return $api->journal === [] ? "(no changes)" : implode("\n", $api->journal); + return Journal::render($api->journal); } public function testPatchRelease(): void diff --git a/tools/release/tests/Support/Journal.php b/tools/release/tests/Support/Journal.php new file mode 100644 index 00000000000..c023d705348 --- /dev/null +++ b/tools/release/tests/Support/Journal.php @@ -0,0 +1,45 @@ + $lines */ + public static function render(array $lines): string + { + if ($lines === []) { + return '(no changes)'; + } + return implode("\n", array_map(self::describe(...), $lines)); + } + + public static function describe(string $line): string + { + $p = explode("\t", $line); + return match ($p[0]) { + 'create' => sprintf( + 'create milestone "%s" in %s%s', + $p[2], + $p[1], + ($p[3] ?? 'due=-') === 'due=-' ? '' : ' (due ' . substr($p[3], 4) . ')', + ), + 'close' => sprintf('close milestone #%s in %s', $p[2], $p[1]), + 'setdue' => sprintf('set due date of milestone #%s to %s in %s', $p[2], $p[3], $p[1]), + 'move' => sprintf('move issue #%s to milestone #%s in %s', $p[2], $p[3], $p[1]), + 'tag' => sprintf('tag %s as %s (at %s)', $p[1], $p[2], $p[3]), + 'retag' => sprintf('re-tag %s as %s (at %s, %s)', $p[1], $p[2], $p[3], $p[4]), + default => $line, + }; + } +} diff --git a/tools/release/tests/TaggerSnapshotTest.php b/tools/release/tests/TaggerSnapshotTest.php index 7fc386f42b2..c1029e0a0eb 100644 --- a/tools/release/tests/TaggerSnapshotTest.php +++ b/tools/release/tests/TaggerSnapshotTest.php @@ -9,6 +9,7 @@ use Nextcloud\ReleaseTools\RepoTagger; use Nextcloud\ReleaseTools\Tests\Support\FakeGitHubApi; +use Nextcloud\ReleaseTools\Tests\Support\Journal; use Nextcloud\ReleaseTools\Tests\Support\MatchesSnapshots; use PHPUnit\Framework\TestCase; @@ -27,15 +28,16 @@ final class TaggerSnapshotTest extends TestCase private function render(array $results, FakeGitHubApi $api): string { - $lines = ['results:']; + $lines = ['Results:']; foreach ($results as $r) { - $lines[] = sprintf(" %s\t%s\t%s\t%s", $r->repo, $r->status, $r->branch, $r->detail); + $branch = $r->branch !== '' ? " on {$r->branch}" : ''; + $lines[] = sprintf(' %s: %s%s (%s)', $r->repo, $r->status, $branch, $r->detail); } $lines[] = ''; - $lines[] = 'journal:'; - foreach ($api->journal as $j) { - $lines[] = ' ' . $j; - } + $lines[] = 'Tags written:'; + $lines[] = $api->journal === [] + ? ' (none)' + : ' ' . implode("\n ", explode("\n", Journal::render($api->journal))); return implode("\n", $lines); } diff --git a/tools/release/tests/snapshots/milestones/due-dates.snap b/tools/release/tests/snapshots/milestones/due-dates.snap index 02771fa9d89..153afa2e74e 100644 --- a/tools/release/tests/snapshots/milestones/due-dates.snap +++ b/tools/release/tests/snapshots/milestones/due-dates.snap @@ -1,3 +1,3 @@ -setdue nextcloud/server 11 2026-07-02T00:00:00Z -close nextcloud/server 10 -setdue nextcloud/server 12 2026-08-27T00:00:00Z +set due date of milestone #11 to 2026-07-02T00:00:00Z in nextcloud/server +close milestone #10 in nextcloud/server +set due date of milestone #12 to 2026-08-27T00:00:00Z in nextcloud/server diff --git a/tools/release/tests/snapshots/milestones/first-beta.snap b/tools/release/tests/snapshots/milestones/first-beta.snap index 5d98dff71e7..8abca48aac1 100644 --- a/tools/release/tests/snapshots/milestones/first-beta.snap +++ b/tools/release/tests/snapshots/milestones/first-beta.snap @@ -1,2 +1,2 @@ -create nextcloud/server Nextcloud 36 due=- -create nextcloud/activity Nextcloud 36 due=- +create milestone "Nextcloud 36" in nextcloud/server +create milestone "Nextcloud 36" in nextcloud/activity diff --git a/tools/release/tests/snapshots/milestones/first-stable.snap b/tools/release/tests/snapshots/milestones/first-stable.snap index a2b4789a472..941d4406300 100644 --- a/tools/release/tests/snapshots/milestones/first-stable.snap +++ b/tools/release/tests/snapshots/milestones/first-stable.snap @@ -1,3 +1,3 @@ -create nextcloud/server Nextcloud 34.0.1 due=- -close nextcloud/server 20 -create nextcloud/server Nextcloud 34.0.2 due=- +create milestone "Nextcloud 34.0.1" in nextcloud/server +close milestone #20 in nextcloud/server +create milestone "Nextcloud 34.0.2" in nextcloud/server diff --git a/tools/release/tests/snapshots/milestones/missing-next.snap b/tools/release/tests/snapshots/milestones/missing-next.snap index 6b42607169b..61700ea0a88 100644 --- a/tools/release/tests/snapshots/milestones/missing-next.snap +++ b/tools/release/tests/snapshots/milestones/missing-next.snap @@ -1,4 +1,4 @@ -create nextcloud/server Nextcloud 33.0.5 due=- -move nextcloud/server 100 11 -close nextcloud/server 10 -create nextcloud/server Nextcloud 33.0.6 due=- +create milestone "Nextcloud 33.0.5" in nextcloud/server +move issue #100 to milestone #11 in nextcloud/server +close milestone #10 in nextcloud/server +create milestone "Nextcloud 33.0.6" in nextcloud/server diff --git a/tools/release/tests/snapshots/milestones/multi-repo.snap b/tools/release/tests/snapshots/milestones/multi-repo.snap index 90113f324ab..51fa88a7b14 100644 --- a/tools/release/tests/snapshots/milestones/multi-repo.snap +++ b/tools/release/tests/snapshots/milestones/multi-repo.snap @@ -1,6 +1,6 @@ -move nextcloud/server 500 11 -close nextcloud/server 10 -create nextcloud/server Nextcloud 33.0.6 due=- -move nextcloud/activity 501 21 -close nextcloud/activity 20 -create nextcloud/activity Nextcloud 33.0.6 due=- +move issue #500 to milestone #11 in nextcloud/server +close milestone #10 in nextcloud/server +create milestone "Nextcloud 33.0.6" in nextcloud/server +move issue #501 to milestone #21 in nextcloud/activity +close milestone #20 in nextcloud/activity +create milestone "Nextcloud 33.0.6" in nextcloud/activity diff --git a/tools/release/tests/snapshots/milestones/patch-release.snap b/tools/release/tests/snapshots/milestones/patch-release.snap index 0dcaa7f195a..88cf40689ee 100644 --- a/tools/release/tests/snapshots/milestones/patch-release.snap +++ b/tools/release/tests/snapshots/milestones/patch-release.snap @@ -1,4 +1,4 @@ -move nextcloud/server 100 11 -move nextcloud/server 101 11 -close nextcloud/server 10 -create nextcloud/server Nextcloud 33.0.6 due=- +move issue #100 to milestone #11 in nextcloud/server +move issue #101 to milestone #11 in nextcloud/server +close milestone #10 in nextcloud/server +create milestone "Nextcloud 33.0.6" in nextcloud/server diff --git a/tools/release/tests/snapshots/tagger/dry-run.snap b/tools/release/tests/snapshots/tagger/dry-run.snap index 9bcb9e8f71b..f2f3f5e5b0e 100644 --- a/tools/release/tests/snapshots/tagger/dry-run.snap +++ b/tools/release/tests/snapshots/tagger/dry-run.snap @@ -1,4 +1,5 @@ -results: - nextcloud/activity OK stable34 would create v34.0.1 @ sha-activity +Results: + nextcloud/activity: OK on stable34 (would create v34.0.1 @ sha-activity) -journal: +Tags written: + (none) diff --git a/tools/release/tests/snapshots/tagger/force.snap b/tools/release/tests/snapshots/tagger/force.snap index c30f8eda849..b905517b1af 100644 --- a/tools/release/tests/snapshots/tagger/force.snap +++ b/tools/release/tests/snapshots/tagger/force.snap @@ -1,6 +1,6 @@ -results: - nextcloud/activity OK stable34 recreated v34.0.1 - nextcloud/server SKIPPED stable34 already tagged +Results: + nextcloud/activity: OK on stable34 (recreated v34.0.1) + nextcloud/server: SKIPPED on stable34 (already tagged) -journal: - retag nextcloud/activity v34.0.1 sha-activity force=true +Tags written: + re-tag nextcloud/activity as v34.0.1 (at sha-activity, force=true) diff --git a/tools/release/tests/snapshots/tagger/mixed.snap b/tools/release/tests/snapshots/tagger/mixed.snap index d29c007178a..5d9fc872d4c 100644 --- a/tools/release/tests/snapshots/tagger/mixed.snap +++ b/tools/release/tests/snapshots/tagger/mixed.snap @@ -1,10 +1,10 @@ -results: - nextcloud/activity OK stable34 created v34.0.1 - nextcloud/notes SKIPPED stable34 already tagged - nextcloud/photos OK main created v34.0.1 - nextcloud/server SKIPPED stable34 already tagged - nextcloud/ghost FAILED no branch found +Results: + nextcloud/activity: OK on stable34 (created v34.0.1) + nextcloud/notes: SKIPPED on stable34 (already tagged) + nextcloud/photos: OK on main (created v34.0.1) + nextcloud/server: SKIPPED on stable34 (already tagged) + nextcloud/ghost: FAILED (no branch found) -journal: - tag nextcloud/activity v34.0.1 sha-activity - tag nextcloud/photos v34.0.1 sha-photos +Tags written: + tag nextcloud/activity as v34.0.1 (at sha-activity) + tag nextcloud/photos as v34.0.1 (at sha-photos) From cfb39428481f88426c314045abe5f72823b7d118 Mon Sep 17 00:00:00 2001 From: skjnldsv Date: Tue, 9 Jun 2026 14:24:07 +0200 Subject: [PATCH 3/3] test(release-tools): snapshots use standard tab format with explanatory comment headers Signed-off-by: skjnldsv --- tools/release/tests/MilestoneSnapshotTest.php | 18 ++++---- tools/release/tests/Support/Journal.php | 42 +++++++------------ tools/release/tests/TaggerSnapshotTest.php | 22 +++++----- .../tests/snapshots/milestones/due-dates.snap | 8 ++-- .../snapshots/milestones/first-beta.snap | 6 ++- .../snapshots/milestones/first-stable.snap | 8 ++-- .../snapshots/milestones/missing-next.snap | 10 +++-- .../snapshots/milestones/multi-repo.snap | 14 ++++--- .../snapshots/milestones/patch-release.snap | 10 +++-- .../snapshots/milestones/prerelease-noop.snap | 2 + .../tests/snapshots/tagger/dry-run.snap | 9 ++-- .../release/tests/snapshots/tagger/force.snap | 11 ++--- .../release/tests/snapshots/tagger/mixed.snap | 19 +++++---- 13 files changed, 91 insertions(+), 88 deletions(-) diff --git a/tools/release/tests/MilestoneSnapshotTest.php b/tools/release/tests/MilestoneSnapshotTest.php index 39f43504a49..1f170d34a12 100644 --- a/tools/release/tests/MilestoneSnapshotTest.php +++ b/tools/release/tests/MilestoneSnapshotTest.php @@ -31,9 +31,9 @@ final class MilestoneSnapshotTest extends TestCase private const SERVER = 'nextcloud/server'; private const ACTIVITY = 'nextcloud/activity'; - private function snapshot(FakeGitHubApi $api): string + private function snapshot(string $description, FakeGitHubApi $api): string { - return Journal::render($api->journal); + return Journal::snapshot($description, Journal::MILESTONE_LEGEND, $api->journal); } public function testPatchRelease(): void @@ -45,7 +45,7 @@ public function testPatchRelease(): void $api->seedIssue(self::SERVER, 101, 10); (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER]); - $this->assertMatchesSnapshot('milestones/patch-release', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/patch-release', $this->snapshot('Patch release v33.0.4: move open issues to 33.0.5, close 33.0.4, open 33.0.6', $api)); } public function testFirstStable(): void @@ -53,14 +53,14 @@ public function testFirstStable(): void $api = new FakeGitHubApi(); $api->seedMilestone(self::SERVER, 20, 'Nextcloud 34'); (new MilestoneUpdater($api))->run(Version::fromTag('v34.0.0'), [self::SERVER]); - $this->assertMatchesSnapshot('milestones/first-stable', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/first-stable', $this->snapshot('First stable v34.0.0: close the Nextcloud 34 milestone, open 34.0.1 and 34.0.2', $api)); } public function testFirstBeta(): void { $api = new FakeGitHubApi(); (new MilestoneUpdater($api))->run(Version::fromTag('v35.0.0beta1'), [self::SERVER, self::ACTIVITY]); - $this->assertMatchesSnapshot('milestones/first-beta', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/first-beta', $this->snapshot('First beta v35.0.0beta1: open the next major milestone Nextcloud 36 in each repo', $api)); } public function testMissingNext(): void @@ -69,7 +69,7 @@ public function testMissingNext(): void $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.4', 'open', 1); $api->seedIssue(self::SERVER, 100, 10); (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER]); - $this->assertMatchesSnapshot('milestones/missing-next', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/missing-next', $this->snapshot('Patch v33.0.4 when 33.0.5 is missing: create it first, then move/close/open 33.0.6', $api)); } public function testDueDates(): void @@ -84,7 +84,7 @@ public function testDueDates(): void '2026-07-02T00:00:00Z', '2026-08-27T00:00:00Z', ); - $this->assertMatchesSnapshot('milestones/due-dates', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/due-dates', $this->snapshot('Patch v33.0.4 with due dates: set 33.0.5 and 33.0.6 due dates, close 33.0.4', $api)); } public function testMultiRepo(): void @@ -97,7 +97,7 @@ public function testMultiRepo(): void $api->seedIssue($repo, 500 + $i, $base); } (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.4'), [self::SERVER, self::ACTIVITY]); - $this->assertMatchesSnapshot('milestones/multi-repo', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/multi-repo', $this->snapshot('Patch v33.0.4 across two repos', $api)); } public function testPrereleaseNoop(): void @@ -105,6 +105,6 @@ public function testPrereleaseNoop(): void $api = new FakeGitHubApi(); $api->seedMilestone(self::SERVER, 10, 'Nextcloud 33.0.2'); (new MilestoneUpdater($api))->run(Version::fromTag('v33.0.2rc1'), [self::SERVER]); - $this->assertMatchesSnapshot('milestones/prerelease-noop', $this->snapshot($api)); + $this->assertMatchesSnapshot('milestones/prerelease-noop', $this->snapshot('Non-first-beta pre-release v33.0.2rc1: nothing happens', $api)); } } diff --git a/tools/release/tests/Support/Journal.php b/tools/release/tests/Support/Journal.php index c023d705348..4770de138dc 100644 --- a/tools/release/tests/Support/Journal.php +++ b/tools/release/tests/Support/Journal.php @@ -8,38 +8,24 @@ namespace Nextcloud\ReleaseTools\Tests\Support; /** - * Renders FakeGitHubApi's tab-separated mutation journal into plain English, - * so the committed .snap files read as a description of what a release does - * rather than as terse tab columns. The raw journal stays the source of truth - * for the assertion-based tests; this is only for the snapshots. + * Builds the .snap body for the milestone/tagger snapshot tests: the raw + * tab-separated mutation journal (the standard, stable format the assertion + * tests also use), preceded by comment lines that say which scenario produced + * it and what the columns mean - so a .snap file is understandable on its own. */ final class Journal { - /** @param list $lines */ - public static function render(array $lines): string - { - if ($lines === []) { - return '(no changes)'; - } - return implode("\n", array_map(self::describe(...), $lines)); - } + public const MILESTONE_LEGEND = + 'columns (tab): -- ' + . 'create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms'; + + public const TAG_LEGEND = + 'tags (tab): -- tag | retag adds force='; - public static function describe(string $line): string + /** @param list $lines raw journal lines */ + public static function snapshot(string $description, string $legend, array $lines): string { - $p = explode("\t", $line); - return match ($p[0]) { - 'create' => sprintf( - 'create milestone "%s" in %s%s', - $p[2], - $p[1], - ($p[3] ?? 'due=-') === 'due=-' ? '' : ' (due ' . substr($p[3], 4) . ')', - ), - 'close' => sprintf('close milestone #%s in %s', $p[2], $p[1]), - 'setdue' => sprintf('set due date of milestone #%s to %s in %s', $p[2], $p[3], $p[1]), - 'move' => sprintf('move issue #%s to milestone #%s in %s', $p[2], $p[3], $p[1]), - 'tag' => sprintf('tag %s as %s (at %s)', $p[1], $p[2], $p[3]), - 'retag' => sprintf('re-tag %s as %s (at %s, %s)', $p[1], $p[2], $p[3], $p[4]), - default => $line, - }; + $body = $lines === [] ? '(no changes)' : implode("\n", $lines); + return "# {$description}\n# {$legend}\n{$body}"; } } diff --git a/tools/release/tests/TaggerSnapshotTest.php b/tools/release/tests/TaggerSnapshotTest.php index c1029e0a0eb..9e9d932bd80 100644 --- a/tools/release/tests/TaggerSnapshotTest.php +++ b/tools/release/tests/TaggerSnapshotTest.php @@ -26,18 +26,18 @@ final class TaggerSnapshotTest extends TestCase { use MatchesSnapshots; - private function render(array $results, FakeGitHubApi $api): string + private function render(string $description, array $results, FakeGitHubApi $api): string { - $lines = ['Results:']; + $lines = [ + "# {$description}", + '# results (tab): ', + '# ' . Journal::TAG_LEGEND, + ]; foreach ($results as $r) { - $branch = $r->branch !== '' ? " on {$r->branch}" : ''; - $lines[] = sprintf(' %s: %s%s (%s)', $r->repo, $r->status, $branch, $r->detail); + $lines[] = sprintf("%s\t%s\t%s\t%s", $r->repo, $r->status, $r->branch === '' ? '-' : $r->branch, $r->detail); } $lines[] = ''; - $lines[] = 'Tags written:'; - $lines[] = $api->journal === [] - ? ' (none)' - : ' ' . implode("\n ", explode("\n", Journal::render($api->journal))); + $lines[] = $api->journal === [] ? '(no tags written)' : implode("\n", $api->journal); return implode("\n", $lines); } @@ -64,7 +64,7 @@ public function testMixedRun(): void $repos, ); - $this->assertMatchesSnapshot('tagger/mixed', $this->render($results, $api)); + $this->assertMatchesSnapshot('tagger/mixed', $this->render('Tag v34.0.1 across a mixed set: new, already-tagged, default-branch fallback, server (immutable), and a repo with no branch', $results, $api)); } public function testForceRun(): void @@ -80,7 +80,7 @@ public function testForceRun(): void $tagger->tag('nextcloud/activity', 'stable34', 'v34.0.1', true), $tagger->tag('nextcloud/server', 'stable34', 'v34.0.1', true), ]; - $this->assertMatchesSnapshot('tagger/force', $this->render($results, $api)); + $this->assertMatchesSnapshot('tagger/force', $this->render('Tag v34.0.1 with --force: a normal repo is recreated, the server repo is still skipped', $results, $api)); } public function testDryRun(): void @@ -88,6 +88,6 @@ public function testDryRun(): void $api = new FakeGitHubApi(); $api->seedBranch('nextcloud/activity', 'stable34', 'sha-activity', true); $results = [(new RepoTagger($api, dryRun: true))->tag('nextcloud/activity', 'stable34', 'v34.0.1', false)]; - $this->assertMatchesSnapshot('tagger/dry-run', $this->render($results, $api)); + $this->assertMatchesSnapshot('tagger/dry-run', $this->render('Tag v34.0.1 in dry-run: reports what it would do, writes nothing', $results, $api)); } } diff --git a/tools/release/tests/snapshots/milestones/due-dates.snap b/tools/release/tests/snapshots/milestones/due-dates.snap index 153afa2e74e..cc9be4a82a0 100644 --- a/tools/release/tests/snapshots/milestones/due-dates.snap +++ b/tools/release/tests/snapshots/milestones/due-dates.snap @@ -1,3 +1,5 @@ -set due date of milestone #11 to 2026-07-02T00:00:00Z in nextcloud/server -close milestone #10 in nextcloud/server -set due date of milestone #12 to 2026-08-27T00:00:00Z in nextcloud/server +# Patch v33.0.4 with due dates: set 33.0.5 and 33.0.6 due dates, close 33.0.4 +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +setdue nextcloud/server 11 2026-07-02T00:00:00Z +close nextcloud/server 10 +setdue nextcloud/server 12 2026-08-27T00:00:00Z diff --git a/tools/release/tests/snapshots/milestones/first-beta.snap b/tools/release/tests/snapshots/milestones/first-beta.snap index 8abca48aac1..4db44a776ca 100644 --- a/tools/release/tests/snapshots/milestones/first-beta.snap +++ b/tools/release/tests/snapshots/milestones/first-beta.snap @@ -1,2 +1,4 @@ -create milestone "Nextcloud 36" in nextcloud/server -create milestone "Nextcloud 36" in nextcloud/activity +# First beta v35.0.0beta1: open the next major milestone Nextcloud 36 in each repo +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +create nextcloud/server Nextcloud 36 due=- +create nextcloud/activity Nextcloud 36 due=- diff --git a/tools/release/tests/snapshots/milestones/first-stable.snap b/tools/release/tests/snapshots/milestones/first-stable.snap index 941d4406300..7e938682c64 100644 --- a/tools/release/tests/snapshots/milestones/first-stable.snap +++ b/tools/release/tests/snapshots/milestones/first-stable.snap @@ -1,3 +1,5 @@ -create milestone "Nextcloud 34.0.1" in nextcloud/server -close milestone #20 in nextcloud/server -create milestone "Nextcloud 34.0.2" in nextcloud/server +# First stable v34.0.0: close the Nextcloud 34 milestone, open 34.0.1 and 34.0.2 +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +create nextcloud/server Nextcloud 34.0.1 due=- +close nextcloud/server 20 +create nextcloud/server Nextcloud 34.0.2 due=- diff --git a/tools/release/tests/snapshots/milestones/missing-next.snap b/tools/release/tests/snapshots/milestones/missing-next.snap index 61700ea0a88..3cbfc1e9642 100644 --- a/tools/release/tests/snapshots/milestones/missing-next.snap +++ b/tools/release/tests/snapshots/milestones/missing-next.snap @@ -1,4 +1,6 @@ -create milestone "Nextcloud 33.0.5" in nextcloud/server -move issue #100 to milestone #11 in nextcloud/server -close milestone #10 in nextcloud/server -create milestone "Nextcloud 33.0.6" in nextcloud/server +# Patch v33.0.4 when 33.0.5 is missing: create it first, then move/close/open 33.0.6 +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +create nextcloud/server Nextcloud 33.0.5 due=- +move nextcloud/server 100 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/multi-repo.snap b/tools/release/tests/snapshots/milestones/multi-repo.snap index 51fa88a7b14..93f37134f4d 100644 --- a/tools/release/tests/snapshots/milestones/multi-repo.snap +++ b/tools/release/tests/snapshots/milestones/multi-repo.snap @@ -1,6 +1,8 @@ -move issue #500 to milestone #11 in nextcloud/server -close milestone #10 in nextcloud/server -create milestone "Nextcloud 33.0.6" in nextcloud/server -move issue #501 to milestone #21 in nextcloud/activity -close milestone #20 in nextcloud/activity -create milestone "Nextcloud 33.0.6" in nextcloud/activity +# Patch v33.0.4 across two repos +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +move nextcloud/server 500 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- +move nextcloud/activity 501 21 +close nextcloud/activity 20 +create nextcloud/activity Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/patch-release.snap b/tools/release/tests/snapshots/milestones/patch-release.snap index 88cf40689ee..e7667157165 100644 --- a/tools/release/tests/snapshots/milestones/patch-release.snap +++ b/tools/release/tests/snapshots/milestones/patch-release.snap @@ -1,4 +1,6 @@ -move issue #100 to milestone #11 in nextcloud/server -move issue #101 to milestone #11 in nextcloud/server -close milestone #10 in nextcloud/server -create milestone "Nextcloud 33.0.6" in nextcloud/server +# Patch release v33.0.4: move open issues to 33.0.5, close 33.0.4, open 33.0.6 +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms +move nextcloud/server 100 11 +move nextcloud/server 101 11 +close nextcloud/server 10 +create nextcloud/server Nextcloud 33.0.6 due=- diff --git a/tools/release/tests/snapshots/milestones/prerelease-noop.snap b/tools/release/tests/snapshots/milestones/prerelease-noop.snap index d81c6d1b89f..48fb0f2bd1c 100644 --- a/tools/release/tests/snapshots/milestones/prerelease-noop.snap +++ b/tools/release/tests/snapshots/milestones/prerelease-noop.snap @@ -1 +1,3 @@ +# Non-first-beta pre-release v33.0.2rc1: nothing happens +# columns (tab): -- create: title, due= | setdue: #ms, date | move: #issue, #ms | close: #ms (no changes) diff --git a/tools/release/tests/snapshots/tagger/dry-run.snap b/tools/release/tests/snapshots/tagger/dry-run.snap index f2f3f5e5b0e..834c71c706a 100644 --- a/tools/release/tests/snapshots/tagger/dry-run.snap +++ b/tools/release/tests/snapshots/tagger/dry-run.snap @@ -1,5 +1,6 @@ -Results: - nextcloud/activity: OK on stable34 (would create v34.0.1 @ sha-activity) +# Tag v34.0.1 in dry-run: reports what it would do, writes nothing +# results (tab): +# tags (tab): -- tag | retag adds force= +nextcloud/activity OK stable34 would create v34.0.1 @ sha-activity -Tags written: - (none) +(no tags written) diff --git a/tools/release/tests/snapshots/tagger/force.snap b/tools/release/tests/snapshots/tagger/force.snap index b905517b1af..c96adf11cae 100644 --- a/tools/release/tests/snapshots/tagger/force.snap +++ b/tools/release/tests/snapshots/tagger/force.snap @@ -1,6 +1,7 @@ -Results: - nextcloud/activity: OK on stable34 (recreated v34.0.1) - nextcloud/server: SKIPPED on stable34 (already tagged) +# Tag v34.0.1 with --force: a normal repo is recreated, the server repo is still skipped +# results (tab): +# tags (tab): -- tag | retag adds force= +nextcloud/activity OK stable34 recreated v34.0.1 +nextcloud/server SKIPPED stable34 already tagged -Tags written: - re-tag nextcloud/activity as v34.0.1 (at sha-activity, force=true) +retag nextcloud/activity v34.0.1 sha-activity force=true diff --git a/tools/release/tests/snapshots/tagger/mixed.snap b/tools/release/tests/snapshots/tagger/mixed.snap index 5d9fc872d4c..8ecda7c0511 100644 --- a/tools/release/tests/snapshots/tagger/mixed.snap +++ b/tools/release/tests/snapshots/tagger/mixed.snap @@ -1,10 +1,11 @@ -Results: - nextcloud/activity: OK on stable34 (created v34.0.1) - nextcloud/notes: SKIPPED on stable34 (already tagged) - nextcloud/photos: OK on main (created v34.0.1) - nextcloud/server: SKIPPED on stable34 (already tagged) - nextcloud/ghost: FAILED (no branch found) +# Tag v34.0.1 across a mixed set: new, already-tagged, default-branch fallback, server (immutable), and a repo with no branch +# results (tab): +# tags (tab): -- tag | retag adds force= +nextcloud/activity OK stable34 created v34.0.1 +nextcloud/notes SKIPPED stable34 already tagged +nextcloud/photos OK main created v34.0.1 +nextcloud/server SKIPPED stable34 already tagged +nextcloud/ghost FAILED - no branch found -Tags written: - tag nextcloud/activity as v34.0.1 (at sha-activity) - tag nextcloud/photos as v34.0.1 (at sha-photos) +tag nextcloud/activity v34.0.1 sha-activity +tag nextcloud/photos v34.0.1 sha-photos