Files
OneCAD-Draft/scripts/test_release.py
T

164 lines
8.6 KiB
Python

"""Run with python3 scripts/test_release.py; uses only local temporary repositories."""
from contextlib import redirect_stdout
from datetime import datetime, timezone
import io
import json
import os
from pathlib import Path
import subprocess
import tempfile
import textwrap
import unittest
from unittest.mock import patch
import release
class ReleaseTests(unittest.TestCase):
def test_web_source_selection(self):
workflow = (Path(__file__).resolve().parents[1] / ".github/workflows/pages.yml").read_text()
source = workflow.split(" - name: Resolve release source\n", 1)[1]
script = textwrap.dedent(source.split(" run: |\n", 1)[1].split("\n - uses:", 1)[0])
mock_gh = 'gh() { if [ "$1" = release ]; then echo v2026.37; else echo release-sha; fi; }\n'
cases = (
({}, "release-sha", None),
({"RELEASE_TAG": "v2026.37", "RELEASE_COMMIT": "release-sha"}, "release-sha", None),
({"RELEASE_COMMIT": "wrong-sha"}, None, "Release tag and commit do not match"),
({"BUILD_MAIN": "true"}, "main-sha", None),
({"BUILD_MAIN": "true", "GITHUB_REF": "refs/heads/other"}, None, "Web hotfixes must run on main"),
)
with tempfile.TemporaryDirectory() as temp:
output = Path(temp) / "output"
for overrides, commit, error in cases:
with self.subTest(overrides=overrides):
output.write_text("")
result = subprocess.run(["bash", "-e", "-c", mock_gh + script], text=True, capture_output=True, env={
**os.environ, "GITHUB_REPOSITORY": "owner/repo", "GITHUB_REF": "refs/heads/main",
"GITHUB_SHA": "main-sha", "GITHUB_OUTPUT": str(output),
"RELEASE_TAG": "", "RELEASE_COMMIT": "", "BUILD_MAIN": "false", **overrides,
})
if error:
self.assertNotEqual(result.returncode, 0)
self.assertIn(error, result.stdout)
self.assertEqual(output.read_text(), "")
else:
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(output.read_text(), f"tag=v2026.37\ncommit={commit}\n")
def test_build_metadata(self):
with tempfile.TemporaryDirectory() as temp:
binary = str(Path(temp) / "build-script")
subprocess.run(["rustc", str(Path(__file__).resolve().parents[1] / "build.rs"), "-o", binary], check=True)
for cargo in ("0.9.8", "2026.9.0", "2026.35.0", "2026.53.0", "2027.1.0"):
result = subprocess.check_output([binary], text=True, env={**os.environ, "CARGO_PKG_VERSION": cargo})
self.assertIn(f"cargo:rustc-env=OCS_APP_VERSION={release.display_version(cargo)}\n", result)
def test_calendar_versions(self):
self.assertEqual(release.versions("v2026.35"), {
"version": "2026.35", "cargo": "2026.35.0", "msi": "26.35.0", "tag": "v2026.35",
})
self.assertEqual(release.versions("2026.09")["cargo"], "2026.9.0")
self.assertEqual(release.display_version("2026.9.0"), "2026.09")
self.assertEqual(release.display_version("0.9.8"), "0.9.8")
self.assertEqual(datetime(2027, 1, 3, tzinfo=timezone.utc).strftime("v%G.%V"), "v2026.53")
for value in ("2026.00", "2026.54", "2027.53", "2026.9", "2026.35.0", "bad", "0.09.8"):
with self.assertRaises(ValueError, msg=value):
release.versions(value)
def test_release_commit_push_and_retry(self):
original_run = release.run
releases = {"v0.9.8": {"name": "v0.9.8", "body": "Previous notes", "isDraft": False}}
latest = "v0.9.8"
fail_create = False
def run(*args):
nonlocal latest, fail_create
if args[0] != "gh":
return original_run(*args)
if args[1:3] == ("release", "list"):
return json.dumps([{"tagName": tag, "isDraft": False} for tag in releases])
if args[1:3] == ("release", "create"):
if fail_create:
fail_create = False
raise RuntimeError("Temporary release API failure")
latest = args[3]
releases[latest] = {
"name": args[args.index("--title") + 1],
"body": Path(args[args.index("--notes-file") + 1]).read_text(encoding="utf-8"),
"isDraft": False,
}
return ""
self.assertEqual(args[1:3], ("release", "view"))
if args[3] == "--json":
return json.dumps({"tagName": latest})
return json.dumps(releases[args[3]])
with tempfile.TemporaryDirectory() as temp:
temp = Path(temp)
root = temp / "checkout"
root.mkdir()
previous_directory = Path.cwd()
os.chdir(root)
try:
original_run("git", "init", "-b", "main")
original_run("git", "config", "user.name", "Release test")
original_run("git", "config", "user.email", "test@example.invalid")
original_run("git", "init", "--bare", "-b", "main", str(temp / "origin.git"))
original_run("git", "remote", "add", "origin", str(temp / "origin.git"))
Path("Cargo.toml").write_text('[package]\nname = "OpenCADStudio"\nversion = "0.9.8"\n')
Path("Cargo.lock").write_text('version = 4\n[[package]]\nname = "OpenCADStudio"\nversion = "0.9.8"\n')
original_run("git", "add", ".")
original_run("git", "commit", "-m", "Initial release")
original_run("git", "tag", "v0.9.8")
original_run("git", "push", "origin", "main", "--tags")
with patch.object(release, "run", run), patch.object(release, "datetime") as clock, patch.dict(os.environ, {
"GITHUB_REPOSITORY": "owner/repo", "GITHUB_REF": "refs/heads/main", "GITHUB_OUTPUT": str(temp / "output"),
}):
clock.now.return_value = datetime(2026, 8, 30, 12, tzinfo=timezone.utc)
def prepare(publish):
result = io.StringIO()
with redirect_stdout(result):
release.prepare(publish)
return result.getvalue()
self.assertIn("ready=false", prepare(True))
Path("feature").write_text("web release source")
original_run("git", "add", "feature")
original_run("git", "commit", "-m", "Add web release synchronization")
preview = prepare(False)
self.assertIn("Add web release synchronization", preview)
self.assertEqual(release.cargo_version(), "0.9.8")
self.assertEqual(original_run("git", "tag", "--list", "v2026.35"), "")
self.assertIn("ready=true", prepare(True))
sha = original_run("git", "rev-parse", "HEAD")
self.assertEqual(release.cargo_version(), "2026.35.0")
self.assertIn('version = "2026.35.0"', Path("Cargo.lock").read_text())
self.assertEqual(original_run("git", "log", "-1", "--format=%s"), "Release v2026.35")
self.assertEqual(original_run("git", "rev-parse", "origin/main"), sha)
self.assertEqual(original_run("git", "rev-parse", "v2026.35^{commit}"), sha)
self.assertEqual(original_run("git", "status", "--porcelain"), "")
self.assertIn(f"commit={sha}", prepare(True))
clock.now.return_value = datetime(2026, 9, 6, 12, tzinfo=timezone.utc)
self.assertIn("ready=false", prepare(True))
Path("feature").write_text("next change")
original_run("git", "add", "feature")
original_run("git", "commit", "-m", "Fix release retry")
fail_create = True
with self.assertRaises(RuntimeError):
prepare(True)
sha = original_run("git", "rev-parse", "HEAD")
self.assertIn(f"commit={sha}", prepare(True))
self.assertEqual(original_run("git", "rev-parse", "HEAD"), sha)
self.assertEqual(releases["v2026.36"]["name"], "2026.36")
finally:
os.chdir(previous_directory)
if __name__ == "__main__":
unittest.main()