ci: build releases from declared Git commits (Closes #1044)
This commit is contained in:
1 parent
55156f45c9
commit
2082fbd5eb
5 files changed
+156
-44
No files matched your search
@@ -41,7 +41,10 @@ def test_release_promotion_waits_for_tests_and_bundle():
|
||||
assert "needs: [lint, build-release, browser-journey]" in release
|
||||
assert "github.event_name == 'push'" in release
|
||||
assert "actions/download-artifact@v3" in release
|
||||
assert 'python3 scripts/verify_release.py --input-dir dist --commit "$TARGET"' in release
|
||||
assert (
|
||||
'python3 scripts/verify_release.py --input-dir dist --commit "$TARGET" --repository .'
|
||||
in release
|
||||
)
|
||||
assert release.index("sha256sum -c") < release.index("curl --fail-with-body")
|
||||
|
||||
|
||||
|
||||
@@ -20,7 +20,24 @@ def _fixture(root: Path) -> None:
|
||||
(root / "README.md").write_text("# Stackchain\n")
|
||||
|
||||
|
||||
def _build(source: Path, output: Path) -> subprocess.CompletedProcess[str]:
|
||||
def _commit_fixture(source: Path) -> str:
|
||||
subprocess.run(["git", "init", "-q", str(source)], check=True)
|
||||
subprocess.run(["git", "-C", str(source), "config", "user.name", "Release Test"], check=True)
|
||||
subprocess.run(
|
||||
["git", "-C", str(source), "config", "user.email", "release-test@example.invalid"],
|
||||
check=True,
|
||||
)
|
||||
subprocess.run(["git", "-C", str(source), "add", "."], check=True)
|
||||
subprocess.run(["git", "-C", str(source), "commit", "-qm", "fixture"], check=True)
|
||||
return subprocess.run(
|
||||
["git", "-C", str(source), "rev-parse", "HEAD"],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
).stdout.strip()
|
||||
|
||||
|
||||
def _build(source: Path, output: Path, commit: str) -> subprocess.CompletedProcess[str]:
|
||||
return subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
@@ -30,7 +47,7 @@ def _build(source: Path, output: Path) -> subprocess.CompletedProcess[str]:
|
||||
"--output-dir",
|
||||
str(output),
|
||||
"--commit",
|
||||
"a" * 40,
|
||||
commit,
|
||||
"--source-date-epoch",
|
||||
"1720000000",
|
||||
],
|
||||
@@ -40,14 +57,20 @@ def _build(source: Path, output: Path) -> subprocess.CompletedProcess[str]:
|
||||
)
|
||||
|
||||
|
||||
def test_release_bundle_is_reproducible(tmp_path):
|
||||
def test_release_bundle_is_reproducible_from_declared_commit(tmp_path):
|
||||
source = tmp_path / "source"
|
||||
_fixture(source)
|
||||
commit = _commit_fixture(source)
|
||||
|
||||
first = _build(source, tmp_path / "first")
|
||||
second = _build(source, tmp_path / "second")
|
||||
|
||||
first = _build(source, tmp_path / "first", commit)
|
||||
assert first.returncode == 0, first.stderr
|
||||
|
||||
(source / "src" / "__pycache__").mkdir()
|
||||
(source / "src" / "__pycache__" / "main.cpython-311.pyc").write_bytes(b"bytecode")
|
||||
(source / "src" / "untracked-secret.txt").write_text("must not ship")
|
||||
(source / "src" / "main.py").write_text("print('dirty worktree')\n")
|
||||
second = _build(source, tmp_path / "second", commit)
|
||||
|
||||
assert second.returncode == 0, second.stderr
|
||||
first_archive = next((tmp_path / "first").glob("*.tar.gz"))
|
||||
second_archive = next((tmp_path / "second").glob("*.tar.gz"))
|
||||
@@ -55,7 +78,7 @@ def test_release_bundle_is_reproducible(tmp_path):
|
||||
|
||||
manifest = json.loads(next((tmp_path / "first").glob("*.manifest.json")).read_text())
|
||||
checksum = next((tmp_path / "first").glob("*.sha256")).read_text().split()[0]
|
||||
assert manifest["commit"] == "a" * 40
|
||||
assert manifest["commit"] == commit
|
||||
assert manifest["artifact"]["sha256"] == checksum
|
||||
assert checksum == hashlib.sha256(first_archive.read_bytes()).hexdigest()
|
||||
assert sorted(manifest["files"]) == [
|
||||
@@ -74,19 +97,40 @@ def test_release_bundle_is_reproducible(tmp_path):
|
||||
"requirements.txt",
|
||||
"src/main.py",
|
||||
]
|
||||
assert embedded["commit"] == "a" * 40
|
||||
assert embedded["commit"] == commit
|
||||
assert embedded["files"] == manifest["files"]
|
||||
|
||||
|
||||
def test_release_bundle_rejects_unknown_commit(tmp_path):
|
||||
source = tmp_path / "source"
|
||||
_fixture(source)
|
||||
_commit_fixture(source)
|
||||
|
||||
built = _build(source, tmp_path / "dist", "a" * 40)
|
||||
|
||||
assert built.returncode != 0
|
||||
assert "declared commit is unavailable" in built.stderr
|
||||
|
||||
|
||||
def test_release_bundle_verifier_enforces_integrity(tmp_path):
|
||||
source = tmp_path / "source"
|
||||
output = tmp_path / "dist"
|
||||
_fixture(source)
|
||||
built = _build(source, output)
|
||||
commit = _commit_fixture(source)
|
||||
built = _build(source, output, commit)
|
||||
assert built.returncode == 0, built.stderr
|
||||
|
||||
valid = subprocess.run(
|
||||
[sys.executable, str(VERIFIER), "--input-dir", str(output), "--commit", "a" * 40],
|
||||
[
|
||||
sys.executable,
|
||||
str(VERIFIER),
|
||||
"--input-dir",
|
||||
str(output),
|
||||
"--commit",
|
||||
commit,
|
||||
"--repository",
|
||||
str(source),
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
@@ -96,9 +140,18 @@ def test_release_bundle_verifier_enforces_integrity(tmp_path):
|
||||
archive = next(output.glob("*.tar.gz"))
|
||||
archive.write_bytes(archive.read_bytes() + b"tampered")
|
||||
tampered = subprocess.run(
|
||||
[sys.executable, str(VERIFIER), "--input-dir", str(output), "--commit", "a" * 40],
|
||||
[
|
||||
sys.executable,
|
||||
str(VERIFIER),
|
||||
"--input-dir",
|
||||
str(output),
|
||||
"--commit",
|
||||
commit,
|
||||
"--repository",
|
||||
str(source),
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=False,
|
||||
)
|
||||
assert tampered.returncode != 0
|
||||
assert tampered.returncode != 0
|
||||
Reference in new issue
Block a user