Files
pulse/scripts/release_control/release_snapshot_test.py
rcourtman b64709e7b7 Publish reviewed release snapshots independently of branch tips
Continuous development must not change the source of an admitted release.
Allow the workflow to run at the qualified preparation PR head after its
normal merge, verifying exact source and workflow identity, canonical PR
provenance, and ancestry in the governed release line. Later branch commits
remain outside that release. Document the immutable-candidate contract and
verify source workflow compatibility before qualification.

Validation: snapshot identity and workflow contract tests passed, including
wrong-head, wrong-base, fork, unmerged and unbound dispatch rejection. The
existing release workflow promotion policy test also passed.
2026-09-07 19:30:27 +01:00

86 lines
4.1 KiB
Python

"""Release provenance stays bound to the reviewed commit as its train advances."""
from copy import deepcopy
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch
import release_snapshot as snapshot
class SnapshotIdentityTest(unittest.TestCase):
def setUp(self):
self.sha = 'a' * 40
self.merge = 'b' * 40
self.ref = 'refs/heads/release-candidate/packet-1'
self.pr = {
'state': 'closed', 'merged': True, 'merge_commit_sha': self.merge,
'head': {'sha': self.sha, 'ref': 'release-candidate/packet-1', 'repo': {'full_name': 'rcourtman/Pulse'}},
'base': {'ref': 'release/v6.4', 'repo': {'full_name': 'rcourtman/Pulse'}},
}
def verify(self, pr):
return snapshot.reviewed_merge(pr, repository='rcourtman/Pulse', ref=self.ref,
branch='release/v6.4', sha=self.sha)
def test_merged_snapshot_identity(self):
self.assertEqual(self.merge, self.verify(self.pr))
self.assertEqual('release/v6.4', snapshot.source_branch(self.ref, 'release/v6.4', '42'))
self.assertEqual('main', snapshot.source_branch('refs/heads/main', '', ''))
def test_unreviewed_or_retargeted_identity_is_rejected(self):
changes = [('merged', False), ('state', 'open'), ('merge_commit_sha', ''),
('head.sha', 'c' * 40), ('head.ref', 'release-candidate/other'),
('base.ref', 'main'), ('head.repo.full_name', 'someone/Pulse'),
('base.repo.full_name', 'someone/Pulse')]
for path, value in changes:
with self.subTest(path=path):
pr = deepcopy(self.pr)
target = pr
fields = path.split('.')
for field in fields[:-1]:
target = target[field]
target[fields[-1]] = value
with self.assertRaises(ValueError):
self.verify(pr)
def test_unbound_dispatch_inputs_are_rejected(self):
for args in [(self.ref, '', '42'), (self.ref, 'main', ''),
(self.ref, 'feature/other', '42'), (self.ref, 'main', '../42'),
('refs/heads/main', 'main', '42'), ('refs/tags/v6.4.4-beta.1', '', '')]:
with self.subTest(args=args), self.assertRaises(ValueError):
snapshot.source_branch(*args)
def test_source_workflow_supports_snapshot_inputs_before_qualification(self):
path = Path(__file__).resolve().parents[2] / '.github/workflows/create-release.yml'
snapshot.check_workflow(path)
with tempfile.TemporaryDirectory() as raw:
old = Path(raw) / 'workflow.yml'
old.write_text(path.read_text().replace('release_pull_request:', 'removed_input:', 1))
with self.assertRaises(ValueError):
snapshot.check_workflow(old)
def test_workflow_verifies_ancestry_without_requiring_current_tip(self):
with tempfile.TemporaryDirectory() as raw:
output = Path(raw) / 'output'
env = {'GITHUB_REF': self.ref, 'RELEASE_SOURCE_BRANCH': 'release/v6.4',
'RELEASE_PULL_REQUEST': '42', 'GITHUB_REPOSITORY': 'rcourtman/Pulse',
'GITHUB_SHA': self.sha, 'GITHUB_WORKFLOW_SHA': self.sha,
'GITHUB_OUTPUT': str(output)}
with patch.dict(os.environ, env), patch('sys.argv', ['release_snapshot.py']), \
patch.object(subprocess, 'check_output', return_value=json.dumps(self.pr)), \
patch.object(subprocess, 'run') as run:
snapshot.main()
self.assertEqual('source_branch=release/v6.4\n', output.read_text())
commands = [call.args[0] for call in run.call_args_list]
self.assertIn(['git', 'merge-base', '--is-ancestor', self.sha, self.merge], commands)
self.assertIn(['git', 'merge-base', '--is-ancestor', self.merge, 'FETCH_HEAD'], commands)
self.assertFalse(any('reset' in command or 'checkout' in command for command in commands))
if __name__ == '__main__':
unittest.main()