Verify legacy tag in fresh-clone gate
This commit is contained in:
parent
d2bb95fe61
commit
97f3b6b1ae
2 changed files with 137 additions and 1 deletions
|
|
@ -1,11 +1,21 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
import unittest
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from tools.milestone5_fresh_clone import (
|
from tools.milestone5_fresh_clone import (
|
||||||
COMMIT_PATTERN,
|
COMMIT_PATTERN,
|
||||||
|
EXPECTED_LEGACY_TAG_COMMIT,
|
||||||
|
EXPECTED_LEGACY_TAG_OBJECT,
|
||||||
EXPECTED_ORIGINS,
|
EXPECTED_ORIGINS,
|
||||||
|
LEGACY_MIGRATION_TAG,
|
||||||
PUBLIC_REPOSITORY,
|
PUBLIC_REPOSITORY,
|
||||||
|
ROOT,
|
||||||
|
FreshCloneError,
|
||||||
|
obtain_legacy_tag,
|
||||||
|
validate_legacy_tag,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -24,6 +34,77 @@ class Milestone5FreshCloneTests(unittest.TestCase):
|
||||||
with self.subTest(invalid=invalid):
|
with self.subTest(invalid=invalid):
|
||||||
self.assertIsNone(COMMIT_PATTERN.fullmatch(invalid))
|
self.assertIsNone(COMMIT_PATTERN.fullmatch(invalid))
|
||||||
|
|
||||||
|
def test_frozen_legacy_tag_identity_is_exact(self) -> None:
|
||||||
|
self.assertEqual("v1.0.0", LEGACY_MIGRATION_TAG)
|
||||||
|
self.assertEqual(
|
||||||
|
EXPECTED_LEGACY_TAG_OBJECT,
|
||||||
|
subprocess.run(
|
||||||
|
["git", "rev-parse", LEGACY_MIGRATION_TAG],
|
||||||
|
cwd=ROOT,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip(),
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
EXPECTED_LEGACY_TAG_COMMIT,
|
||||||
|
subprocess.run(
|
||||||
|
["git", "rev-parse", f"{LEGACY_MIGRATION_TAG}^{{commit}}"],
|
||||||
|
cwd=ROOT,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip(),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_tagless_clone_obtains_and_verifies_only_frozen_legacy_tag(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory_name:
|
||||||
|
clone = Path(directory_name) / "clone"
|
||||||
|
subprocess.run(
|
||||||
|
["git", "clone", "--no-tags", str(ROOT), str(clone)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
initial_tags = subprocess.run(
|
||||||
|
["git", "tag", "--list"],
|
||||||
|
cwd=clone,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.splitlines()
|
||||||
|
self.assertEqual([], initial_tags)
|
||||||
|
|
||||||
|
_, tag_object, tag_commit = obtain_legacy_tag(clone)
|
||||||
|
|
||||||
|
self.assertEqual(EXPECTED_LEGACY_TAG_OBJECT, tag_object)
|
||||||
|
self.assertEqual(EXPECTED_LEGACY_TAG_COMMIT, tag_commit)
|
||||||
|
fetched_tags = subprocess.run(
|
||||||
|
["git", "tag", "--list"],
|
||||||
|
cwd=clone,
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.splitlines()
|
||||||
|
self.assertEqual([LEGACY_MIGRATION_TAG], fetched_tags)
|
||||||
|
|
||||||
|
def test_legacy_tag_validation_rejects_substitution(self) -> None:
|
||||||
|
valid = {
|
||||||
|
"object_type": "tag",
|
||||||
|
"tag_object": EXPECTED_LEGACY_TAG_OBJECT,
|
||||||
|
"tag_commit": EXPECTED_LEGACY_TAG_COMMIT,
|
||||||
|
}
|
||||||
|
invalid_cases = (
|
||||||
|
("lightweight tag", {"object_type": "commit"}),
|
||||||
|
("moved tag object", {"tag_object": "a" * 40}),
|
||||||
|
("moved tag commit", {"tag_commit": "b" * 40}),
|
||||||
|
)
|
||||||
|
for label, replacement in invalid_cases:
|
||||||
|
with self.subTest(label=label):
|
||||||
|
values = valid | replacement
|
||||||
|
with self.assertRaises(FreshCloneError):
|
||||||
|
validate_legacy_tag(**values)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ EXPECTED_ORIGINS = {
|
||||||
}
|
}
|
||||||
COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$")
|
COMMIT_PATTERN = re.compile(r"^[0-9a-f]{40}$")
|
||||||
COMMAND_TIMEOUT_SECONDS = 30 * 60
|
COMMAND_TIMEOUT_SECONDS = 30 * 60
|
||||||
|
LEGACY_MIGRATION_TAG = "v1.0.0"
|
||||||
|
LEGACY_MIGRATION_TAG_REF = f"refs/tags/{LEGACY_MIGRATION_TAG}"
|
||||||
|
EXPECTED_LEGACY_TAG_OBJECT = "2d7d306a37da89f1c860c7f0be161c45386acf61"
|
||||||
|
EXPECTED_LEGACY_TAG_COMMIT = "593c173b453236a6872d0a4e88e7a51a67a21cde"
|
||||||
|
|
||||||
|
|
||||||
class FreshCloneError(RuntimeError):
|
class FreshCloneError(RuntimeError):
|
||||||
|
|
@ -77,6 +81,50 @@ def _digest(value: str) -> str:
|
||||||
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
return hashlib.sha256(value.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def validate_legacy_tag(*, object_type: str, tag_object: str, tag_commit: str) -> None:
|
||||||
|
if object_type != "tag":
|
||||||
|
raise FreshCloneError(f"{LEGACY_MIGRATION_TAG} is not an annotated tag")
|
||||||
|
if tag_object != EXPECTED_LEGACY_TAG_OBJECT:
|
||||||
|
raise FreshCloneError(
|
||||||
|
f"{LEGACY_MIGRATION_TAG} tag object does not match the frozen release"
|
||||||
|
)
|
||||||
|
if tag_commit != EXPECTED_LEGACY_TAG_COMMIT:
|
||||||
|
raise FreshCloneError(f"{LEGACY_MIGRATION_TAG} commit does not match the frozen release")
|
||||||
|
|
||||||
|
|
||||||
|
def obtain_legacy_tag(
|
||||||
|
clone: Path,
|
||||||
|
) -> tuple[subprocess.CompletedProcess[str], str, str]:
|
||||||
|
fetch = _run(
|
||||||
|
[
|
||||||
|
"git",
|
||||||
|
"fetch",
|
||||||
|
"--no-tags",
|
||||||
|
"origin",
|
||||||
|
f"{LEGACY_MIGRATION_TAG_REF}:{LEGACY_MIGRATION_TAG_REF}",
|
||||||
|
],
|
||||||
|
cwd=clone,
|
||||||
|
)
|
||||||
|
object_type = _run(
|
||||||
|
["git", "cat-file", "-t", LEGACY_MIGRATION_TAG_REF],
|
||||||
|
cwd=clone,
|
||||||
|
).stdout.strip()
|
||||||
|
tag_object = _run(
|
||||||
|
["git", "rev-parse", LEGACY_MIGRATION_TAG_REF],
|
||||||
|
cwd=clone,
|
||||||
|
).stdout.strip()
|
||||||
|
tag_commit = _run(
|
||||||
|
["git", "rev-parse", f"{LEGACY_MIGRATION_TAG_REF}^{{commit}}"],
|
||||||
|
cwd=clone,
|
||||||
|
).stdout.strip()
|
||||||
|
validate_legacy_tag(
|
||||||
|
object_type=object_type,
|
||||||
|
tag_object=tag_object,
|
||||||
|
tag_commit=tag_commit,
|
||||||
|
)
|
||||||
|
return fetch, tag_object, tag_commit
|
||||||
|
|
||||||
|
|
||||||
def run_fresh_clone_gate(commit: str) -> dict[str, object]:
|
def run_fresh_clone_gate(commit: str) -> dict[str, object]:
|
||||||
"""Clone the public successor anonymously and run its complete release gate."""
|
"""Clone the public successor anonymously and run its complete release gate."""
|
||||||
|
|
||||||
|
|
@ -95,6 +143,7 @@ def run_fresh_clone_gate(commit: str) -> dict[str, object]:
|
||||||
checked_out = _run(["git", "rev-parse", "HEAD"], cwd=clone).stdout.strip()
|
checked_out = _run(["git", "rev-parse", "HEAD"], cwd=clone).stdout.strip()
|
||||||
if checked_out != commit:
|
if checked_out != commit:
|
||||||
raise FreshCloneError("Anonymous clone did not check out the requested commit")
|
raise FreshCloneError("Anonymous clone did not check out the requested commit")
|
||||||
|
tag_fetch, tag_object, tag_commit = obtain_legacy_tag(clone)
|
||||||
fsck = _run(["git", "fsck", "--full"], cwd=clone)
|
fsck = _run(["git", "fsck", "--full"], cwd=clone)
|
||||||
environment = dict(os.environ)
|
environment = dict(os.environ)
|
||||||
environment["UV_LINK_MODE"] = "copy"
|
environment["UV_LINK_MODE"] = "copy"
|
||||||
|
|
@ -111,15 +160,21 @@ def run_fresh_clone_gate(commit: str) -> dict[str, object]:
|
||||||
).stdout.strip()
|
).stdout.strip()
|
||||||
elapsed_ms = round((time.perf_counter_ns() - started) / 1_000_000, 3)
|
elapsed_ms = round((time.perf_counter_ns() - started) / 1_000_000, 3)
|
||||||
return {
|
return {
|
||||||
"schema_version": 1,
|
"schema_version": 2,
|
||||||
"repository": PUBLIC_REPOSITORY,
|
"repository": PUBLIC_REPOSITORY,
|
||||||
"authentication": "anonymous_https",
|
"authentication": "anonymous_https",
|
||||||
"commit": commit,
|
"commit": commit,
|
||||||
|
"legacy_migration_tag": {
|
||||||
|
"name": LEGACY_MIGRATION_TAG,
|
||||||
|
"annotated_tag_object": tag_object,
|
||||||
|
"commit": tag_commit,
|
||||||
|
},
|
||||||
"clean_after_gate": True,
|
"clean_after_gate": True,
|
||||||
"version_surface": version,
|
"version_surface": version,
|
||||||
"elapsed_ms": elapsed_ms,
|
"elapsed_ms": elapsed_ms,
|
||||||
"logs": {
|
"logs": {
|
||||||
"clone_sha256": _digest(clone_result.stdout + clone_result.stderr),
|
"clone_sha256": _digest(clone_result.stdout + clone_result.stderr),
|
||||||
|
"legacy_tag_fetch_sha256": _digest(tag_fetch.stdout + tag_fetch.stderr),
|
||||||
"fsck_sha256": _digest(fsck.stdout + fsck.stderr),
|
"fsck_sha256": _digest(fsck.stdout + fsck.stderr),
|
||||||
"sync_sha256": _digest(sync.stdout + sync.stderr),
|
"sync_sha256": _digest(sync.stdout + sync.stderr),
|
||||||
"npm_sha256": _digest(npm.stdout + npm.stderr),
|
"npm_sha256": _digest(npm.stdout + npm.stderr),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue