117 lines
5.5 KiB
Python
117 lines
5.5 KiB
Python
"""Canary policy on real Git refs; GitHub publication stays an inert boundary."""
|
|
from datetime import datetime, timezone
|
|
from types import SimpleNamespace
|
|
import subprocess
|
|
|
|
import pytest
|
|
|
|
from tests.scripts.test_release_tags import release, release_repo # noqa: F401
|
|
|
|
|
|
@pytest.fixture
|
|
def canary_repo(tmp_path, monkeypatch, release_repo):
|
|
git = release_repo
|
|
remote = tmp_path / 'remote.git'
|
|
git('init', '--bare', '-q', str(remote))
|
|
git('remote', 'add', 'origin', 'https://github.com/fixture/release')
|
|
|
|
calls = []
|
|
actual_run = subprocess.run
|
|
|
|
def run(argv, *args, **kwargs):
|
|
if argv[0] != 'gh':
|
|
if argv[:2] == ['git', 'push']:
|
|
argv = ['git', '-c', f'url.{remote.as_uri()}.insteadOf=https://github.com/fixture/release', *argv[1:]]
|
|
return actual_run(argv, *args, **kwargs)
|
|
calls.append(argv)
|
|
if argv[1:3] == ['release', 'create']:
|
|
assert git('--git-dir', str(remote), 'rev-parse', argv[3] + '^{commit}') == git('rev-parse', 'HEAD')
|
|
elif argv[1:3] == ['workflow', 'run']:
|
|
assert any(c[1:3] == ['release', 'create'] for c in calls)
|
|
else:
|
|
assert argv[1:3] == ['repo', 'view']
|
|
return subprocess.CompletedProcess(argv, 0, stdout='main\n', stderr='')
|
|
|
|
monkeypatch.setattr(subprocess, 'run', run)
|
|
which = release.shutil.which
|
|
monkeypatch.setattr(release.shutil, 'which', lambda name: 'gh' if name == 'gh' else which(name))
|
|
monkeypatch.setattr(release, 'generate_changelog', lambda *a, **kw: 'fixture notes')
|
|
monkeypatch.delenv('GITHUB_OUTPUT', raising=False)
|
|
return git, remote, calls
|
|
|
|
|
|
@pytest.mark.parametrize('previous,stable_date,admitted', [
|
|
('v1.2.4-canary.20260818103045', '2026-08-01T00:00:00Z', False),
|
|
('v1.2.3-canary.20260818103045', '2026-08-01T00:00:00Z', True),
|
|
(None, '2026-06-01T00:00:00Z', False),
|
|
(None, '2026-08-01T00:00:00Z', True),
|
|
])
|
|
def test_canary_cut_binds_pushed_head_before_draft_and_dispatch(canary_repo, monkeypatch, previous, stable_date, admitted):
|
|
git, remote, calls = canary_repo
|
|
monkeypatch.setenv('GIT_COMMITTER_DATE', stable_date)
|
|
git('commit', '--allow-empty', '-qm', 'stable')
|
|
git('tag', 'v1.2.3')
|
|
if previous:
|
|
git('tag', previous)
|
|
git('commit', '--allow-empty', '-qm', 'feat: next')
|
|
tag = 'v1.2.4-canary.20260818103000'
|
|
args = SimpleNamespace(date='20260818103000', publish=True, no_changelog=True, remote='origin')
|
|
if stable_date.startswith('2026-06'):
|
|
with pytest.raises(SystemExit) as error:
|
|
release.cmd_canary(args)
|
|
assert error.value.code == 1
|
|
else:
|
|
release.cmd_canary(args)
|
|
if not admitted:
|
|
assert calls == []
|
|
assert tag not in git('tag', '--list').splitlines()
|
|
return
|
|
create = next(c for c in calls if c[1:3] == ['release', 'create'])
|
|
assert create[3] == tag and {'--draft', '--prerelease', '--verify-tag'} <= set(create)
|
|
dispatch = next(c for c in calls if c[1:3] == ['workflow', 'run'])
|
|
assert dispatch == ['gh', 'workflow', 'run', 'desktop-bundled-release.yml', '--ref', 'main',
|
|
'-f', f'tag={tag}', '-f', 'upload_release=true', '--repo', 'fixture/release']
|
|
assert git('--git-dir', str(remote), 'rev-parse', tag + '^{commit}') == git('rev-parse', 'HEAD')
|
|
calls.clear()
|
|
release.cmd_canary(args) # existing tag
|
|
args.date = '20260818103100'
|
|
release.cmd_canary(args) # unchanged HEAD
|
|
assert calls == []
|
|
|
|
|
|
def test_tag_shapes_and_real_prune_cutoff(canary_repo, monkeypatch, capsys):
|
|
git, _, calls = canary_repo
|
|
assert release.canary_tag_for_date('0.27.4', '20260818103000') == 'v0.27.5-canary.20260818103000'
|
|
assert release.canary_tag_for_date('v1.4.0', '20261231235959') == 'v1.4.1-canary.20261231235959'
|
|
tags = ['v0.27.1-canary.20260801', 'v0.27.1-canary.20260801235959',
|
|
'v0.27.1-canary.20260804', 'v0.27.1-canary.20260804235959',
|
|
'v0.27.1-canary.invalid', 'v0.27.1', 'v2026.7.20']
|
|
for tag in tags:
|
|
git('tag', tag)
|
|
class Clock(datetime):
|
|
@classmethod
|
|
def now(cls, tz=None):
|
|
return cls(2026, 8, 18, tzinfo=timezone.utc)
|
|
monkeypatch.setattr(release, 'datetime', Clock)
|
|
release.prune_old_canaries(SimpleNamespace(remote='origin', publish=False))
|
|
assert {line.removeprefix('Would delete ') for line in capsys.readouterr().out.splitlines()
|
|
if line.startswith('Would delete ')} == set(tags[:2])
|
|
assert calls == [] and set(git('tag', '--list').splitlines()) == set(tags)
|
|
|
|
|
|
def test_stable_dispatch_and_failure(monkeypatch, tmp_path):
|
|
calls = []
|
|
monkeypatch.setattr(release, 'REPO_ROOT', tmp_path)
|
|
monkeypatch.setattr(release.shutil, 'which', lambda _: 'gh')
|
|
monkeypatch.setattr(release.subprocess, 'run', lambda cmd, **kw: (
|
|
calls.append(cmd) or subprocess.CompletedProcess(cmd, 0, '', '')))
|
|
assert release.dispatch_desktop_build('v1.2.3', 'owner/repo')
|
|
assert calls == [['gh', 'workflow', 'run', 'stable-release.yml', '--ref', 'v1.2.3',
|
|
'-f', 'tag=v1.2.3', '--repo', 'owner/repo']]
|
|
with pytest.raises(ValueError):
|
|
release.dispatch_desktop_build('v1.2.3/other', 'owner/repo')
|
|
monkeypatch.setattr(release.subprocess, 'run', lambda cmd, **kw: subprocess.CompletedProcess(cmd, 1, '', 'refused'))
|
|
assert release.dispatch_desktop_build('v1.2.3', 'owner/repo') is False
|
|
monkeypatch.setattr(release.shutil, 'which', lambda _: None)
|
|
assert release.dispatch_desktop_build('v1.2.4-canary.20260818103000', 'owner/repo') is False
|