290 lines
10 KiB
Python
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()
|