Files
pkgmgr/tests/integration/test_publish_containers_release.py
T

239 lines
7.5 KiB
Python
Raw Normal View History

from __future__ import annotations
import json
import os
import subprocess
import tempfile
import unittest
from pathlib import Path
import yaml
REPO_ROOT = Path(__file__).resolve().parents[2]
RESOLVER = (
REPO_ROOT
/ "scripts"
/ "github"
/ "publish-containers"
/ "compute-publish-container-info.sh"
)
WORKFLOW = REPO_ROOT / ".github" / "workflows" / "publish-containers.yml"
GIT_ENV = {
**os.environ,
"GIT_CONFIG_GLOBAL": os.devnull,
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_AUTHOR_NAME": "test",
"GIT_AUTHOR_EMAIL": "test@example.invalid",
"GIT_COMMITTER_NAME": "test",
"GIT_COMMITTER_EMAIL": "test@example.invalid",
}
class TestComputePublishContainerInfo(unittest.TestCase):
def setUp(self) -> None:
self._tmp = tempfile.TemporaryDirectory()
tmp = Path(self._tmp.name)
self.repo = tmp / "repo"
self.repo.mkdir()
self.output = tmp / "github_output"
self.git("init", "-q", "-b", "main")
def tearDown(self) -> None:
self._tmp.cleanup()
def git(self, *args: str) -> str:
return subprocess.run(
["git", *args],
cwd=self.repo,
env=GIT_ENV,
capture_output=True,
text=True,
check=True,
).stdout.strip()
def commit(self, *tags: str) -> str:
self.git("commit", "-q", "--allow-empty", "-m", " ".join(tags) or "untagged")
for tag in tags:
self.git("tag", tag)
return self.git("rev-parse", "HEAD")
def push_main(self) -> None:
self.git("update-ref", "refs/remotes/origin/main", "HEAD")
def resolve(self, workflow_run_sha: str):
self.output.write_text("")
result = subprocess.run(
["bash", str(RESOLVER)],
cwd=self.repo,
env={
**GIT_ENV,
"GITHUB_OUTPUT": str(self.output),
"WORKFLOW_RUN_SHA": workflow_run_sha,
},
capture_output=True,
text=True,
check=False,
)
outputs = dict(
line.split("=", 1) for line in self.output.read_text().splitlines()
)
return result, outputs
def releases(self, workflow_run_sha: str) -> list[dict[str, str]]:
result, outputs = self.resolve(workflow_run_sha)
self.assertEqual(result.returncode, 0, result.stderr)
return json.loads(outputs["releases"])
def test_a_rebuild_with_stable_on_the_newest_release_publishes_it_once(
self,
) -> None:
self.commit("v2.3.0")
newest = self.commit("v2.4.0", "stable")
self.push_main()
self.assertEqual(
self.releases(""),
[{"sha": newest, "version": "2.4.0", "aliases": "latest 2 2.4 stable"}],
)
def test_a_rebuild_with_stable_behind_publishes_the_newest_and_the_stable_release(
self,
) -> None:
self.commit("v2.2.0")
stable = self.commit("v2.3.0", "stable")
newest = self.commit("v2.4.0")
self.commit("v2.5.0-rc1")
self.push_main()
self.assertEqual(
self.releases(""),
[
{"sha": newest, "version": "2.4.0", "aliases": "latest 2 2.4"},
{"sha": stable, "version": "2.3.0", "aliases": "2.3 stable"},
],
)
def test_the_stable_release_takes_no_prefix_a_newer_release_leads(self) -> None:
stable = self.commit("v2.3.0", "stable")
self.commit("v2.3.1")
newest = self.commit("v2.4.0")
self.push_main()
self.assertEqual(
self.releases(""),
[
{"sha": newest, "version": "2.4.0", "aliases": "latest 2 2.4"},
{"sha": stable, "version": "2.3.0", "aliases": "stable"},
],
)
def test_a_rebuild_publishes_the_newest_release_on_main_not_its_tip(
self,
) -> None:
self.commit("v1.9.0")
newest = self.commit("v1.10.0")
self.commit()
self.push_main()
self.git("checkout", "-q", "-b", "side")
self.commit("v3.0.0")
result, outputs = self.resolve("")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
json.loads(outputs["releases"]),
[{"sha": newest, "version": "1.10.0", "aliases": "latest 1 1.10"}],
)
self.assertIn("the stable image stays where it is", result.stdout)
def test_a_workflow_run_publishes_the_commit_it_tagged(self) -> None:
self.commit("v2.3.0")
tagged = self.commit("v2.4.0", "stable")
self.push_main()
self.assertEqual(
self.releases(tagged),
[{"sha": tagged, "version": "2.4.0", "aliases": "latest 2 2.4 stable"}],
)
def test_a_workflow_run_of_a_backport_moves_only_the_prefixes_it_leads(
self,
) -> None:
self.commit("v2.3.0")
self.commit("v2.4.0", "stable")
backport = self.commit("v2.3.1")
self.push_main()
self.assertEqual(
self.releases(backport),
[{"sha": backport, "version": "2.3.1", "aliases": "2.3"}],
)
def test_a_workflow_run_outside_main_publishes_nothing(self) -> None:
self.commit("v2.3.0")
self.push_main()
self.git("checkout", "-q", "-b", "side")
outside = self.commit("v9.0.0")
self.assertEqual(self.releases(outside), [])
def test_a_workflow_run_without_an_x_y_z_tag_publishes_nothing(self) -> None:
self.commit("v2.3.0")
candidate = self.commit("v2.4.0-rc1")
self.push_main()
self.assertEqual(self.releases(candidate), [])
def test_a_rebuild_without_a_release_on_main_fails(self) -> None:
self.commit("v1.0.0-rc1", "stable")
self.push_main()
result, outputs = self.resolve("")
self.assertEqual(result.returncode, 1)
self.assertEqual(outputs, {})
self.assertIn("No vX.Y.Z tag is reachable from origin/main", result.stderr)
class TestPublishContainersWorkflow(unittest.TestCase):
def setUp(self) -> None:
self.workflow = yaml.safe_load(WORKFLOW.read_text())
def test_the_releases_are_rebuilt_daily_at_03_utc_and_on_demand(self) -> None:
triggers = self.workflow[True]
steps = {
step.get("id"): step for step in self.workflow["jobs"]["prepare"]["steps"]
}
self.assertEqual(triggers["schedule"], [{"cron": "0 3 * * *"}])
self.assertIn("workflow_dispatch", triggers)
self.assertEqual(
steps["info"]["run"],
"bash scripts/github/publish-containers/compute-publish-container-info.sh",
)
self.assertEqual(
steps["info"]["env"]["WORKFLOW_RUN_SHA"],
"${{ github.event.workflow_run.head_sha }}",
)
def test_every_resolved_release_is_built_and_merged_with_its_own_tags(
self,
) -> None:
jobs = self.workflow["jobs"]
merge_env = jobs["merge"]["steps"][-1]["env"]
for job in ("build", "merge"):
self.assertEqual(
jobs[job]["strategy"]["matrix"]["release"],
"${{ fromJSON(needs.prepare.outputs.releases) }}",
)
self.assertEqual(
jobs[job]["steps"][0]["with"]["ref"], "${{ matrix.release.sha }}"
)
self.assertEqual(merge_env["VERSION"], "${{ matrix.release.version }}")
self.assertEqual(merge_env["ALIASES"], "${{ matrix.release.aliases }}")
if __name__ == "__main__":
unittest.main()