Files
.pouch/tests/test_builder_version.py
2026-08-25 15:26:29 +08:00

290 lines
10 KiB
Python

from __future__ import annotations
import os
import stat
import subprocess
import tempfile
import unittest
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = REPO_ROOT / "skills" / "builder" / "scripts"
VERSION_SH = SCRIPTS / "version.sh"
PUBLISH_DOCKER = SCRIPTS / "publish_docker.sh"
VERSION_MK = SCRIPTS / "version.mk"
def git(cwd: Path, *args: str, env: dict[str, str] | None = None) -> str:
merged = os.environ.copy()
if env:
merged.update(env)
merged.setdefault("GIT_AUTHOR_NAME", "Test")
merged.setdefault("GIT_AUTHOR_EMAIL", "test@example.com")
merged.setdefault("GIT_COMMITTER_NAME", "Test")
merged.setdefault("GIT_COMMITTER_EMAIL", "test@example.com")
result = subprocess.run(
["git", *args],
cwd=cwd,
env=merged,
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AssertionError(f"git {args} failed: {result.stderr}")
return result.stdout.strip()
def commit(cwd: Path, message: str) -> str:
git(cwd, "add", "-A")
git(cwd, "commit", "-m", message)
return git(cwd, "rev-parse", "HEAD")
def sha7(cwd: Path) -> str:
return git(cwd, "rev-parse", "HEAD")[:7]
def version_sh(*args: str, cwd: Path, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
merged = os.environ.copy()
if env:
merged.update(env)
return subprocess.run(
["bash", str(VERSION_SH), *args],
cwd=cwd,
env=merged,
text=True,
capture_output=True,
check=False,
)
def init_repo(path: Path) -> None:
git(path, "init", "-b", "main")
git(path, "config", "user.email", "test@example.com")
git(path, "config", "user.name", "Test")
class BuilderVersionTests(unittest.TestCase):
def test_version_sh_is_executable(self) -> None:
mode = VERSION_SH.stat().st_mode
self.assertTrue(mode & stat.S_IXUSR, "version.sh must be executable")
def test_official_on_stable_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "-a", "v1.4.2", "-m", "1.4.2")
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout, "1.4.2\n")
docker = version_sh("--docker", cwd=repo)
self.assertEqual(docker.returncode, 0, docker.stderr)
self.assertEqual(docker.stdout, "1.4.2\n")
def test_test_version_uses_ancestor_not_global_max(self) -> None:
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
repo = root / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "base")
git(repo, "tag", "v1.4.2")
git(repo, "checkout", "-b", "feat/login-v2")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "feature")
(repo / "c").write_text("3\n", encoding="utf-8")
commit(repo, "feature 2")
feature_sha = sha7(repo)
git(repo, "checkout", "main")
(repo / "d").write_text("4\n", encoding="utf-8")
commit(repo, "release")
git(repo, "tag", "v1.4.3")
git(repo, "checkout", "feat/login-v2")
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(
result.stdout.strip(),
f"1.4.2~feat-login-v2.2+g{feature_sha}",
)
docker = version_sh("--docker", cwd=repo)
self.assertEqual(
docker.stdout.strip(),
f"1.4.2-feat-login-v2.2.g{feature_sha}",
)
def test_ignores_prerelease_and_app_tags(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v1.0.0")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "next")
git(repo, "tag", "v1.1.0-rc.1")
git(repo, "tag", "v1.1.0-app-1")
sha = sha7(repo)
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"1.0.0~main.1+g{sha}")
def test_no_stable_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
sha = sha7(repo)
result = version_sh(cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"0.0.0~main+g{sha}")
def test_detached_uses_env_branch(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v0.1.0")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "next")
sha = sha7(repo)
git(repo, "checkout", "--detach", "HEAD")
result = version_sh(cwd=repo, env={"BUILD_BRANCH": "ci_job"})
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), f"0.1.0~ci-job.1+g{sha}")
def test_override_official_rejected_off_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v1.4.2")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "next")
result = version_sh("--version", "1.4.3", cwd=repo)
self.assertEqual(result.returncode, 2)
self.assertIn("looks official", result.stderr)
def test_override_official_ok_on_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v1.4.2")
result = version_sh("--version", "v1.4.2", cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), "1.4.2")
def test_override_test_shaped_accepted(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
result = version_sh("--version", "1.4.2~feat.1+gabc1234", "--docker", cwd=repo)
self.assertEqual(result.returncode, 0, result.stderr)
self.assertEqual(result.stdout.strip(), "1.4.2-feat.1.gabc1234")
def test_version_mk_sets_make_version(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "a").write_text("1\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v2.0.0")
makefile = repo / "Makefile"
makefile.write_text(
"include {mk}\n"
".PHONY: version\n"
"version:\n"
"\t@echo \"$(VERSION)\"\n".format(mk=VERSION_MK),
encoding="utf-8",
)
result = subprocess.run(
["make", "--no-print-directory", "-C", str(repo), "version"],
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)
self.assertEqual(result.stdout.strip(), "2.0.0")
def test_publish_docker_dry_run_uses_derived_tag(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v3.1.4")
result = subprocess.run(
[
"bash",
str(PUBLISH_DOCKER),
"--registry",
"registry.example.com",
"--repository",
"ns/app",
"--dry-run",
],
cwd=repo,
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 0, result.stderr + result.stdout)
self.assertIn("Image: registry.example.com/ns/app:3.1.4", result.stdout)
def test_publish_docker_rejects_official_tag_off_head(self) -> None:
with tempfile.TemporaryDirectory() as temp:
repo = Path(temp) / "repo"
repo.mkdir()
init_repo(repo)
(repo / "Dockerfile").write_text("FROM scratch\n", encoding="utf-8")
commit(repo, "init")
git(repo, "tag", "v1.0.0")
(repo / "b").write_text("2\n", encoding="utf-8")
commit(repo, "next")
result = subprocess.run(
[
"bash",
str(PUBLISH_DOCKER),
"--registry",
"registry.example.com",
"--repository",
"ns/app",
"--tag",
"1.0.1",
"--dry-run",
],
cwd=repo,
text=True,
capture_output=True,
check=False,
)
self.assertEqual(result.returncode, 2)
self.assertIn("looks official", result.stderr)
if __name__ == "__main__":
unittest.main()