144 lines
6.4 KiB
Python
144 lines
6.4 KiB
Python
"""Release retries retain other targets, versions, and updater manifests."""
|
|
import importlib.util
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
import pytest
|
|
|
|
@pytest.fixture
|
|
def module():
|
|
spec = importlib.util.spec_from_file_location(
|
|
"rerun_assets", Path(__file__).parents[1] / "scripts/clear-release-rerun-assets.py"
|
|
)
|
|
runtime = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(runtime)
|
|
return runtime
|
|
|
|
@pytest.mark.parametrize("target,suffix", [
|
|
("x86_64-pc-windows-msvc", "x64_en-US.msi"),
|
|
("aarch64-apple-darwin", "aarch64.dmg"),
|
|
("x86_64-apple-darwin", "x64.dmg"),
|
|
("x86_64-unknown-linux-gnu", "amd64.AppImage"),
|
|
])
|
|
def test_retry_selects_only_its_own_installers(module, target, suffix):
|
|
own = f"VoiceStudio_0.5.2-150_{suffix}"
|
|
others = ["latest.json", "VoiceStudio_universal.app.tar.gz", "VoiceStudio_0.5.2-149_" + suffix,
|
|
"VoiceStudio_Current_User_0.5.2-150_x64_en-US.msi", "Other_0.5.2-150_" + suffix]
|
|
assert module.matching_assets([*others, own, own + ".sig"], "0.5.2-150", target) == [own, own + ".sig"]
|
|
|
|
|
|
def test_windows_retry_does_not_touch_mac_x64_dmg(module):
|
|
assert module.matching_assets(["VoiceStudio_0.5.2-150_x64.dmg", "VoiceStudio_0.5.2.150_x64_en-US.msi"],
|
|
"0.5.2-150", "x86_64-pc-windows-msvc") == ["VoiceStudio_0.5.2.150_x64_en-US.msi"]
|
|
|
|
@pytest.mark.parametrize("error", ["HTTP 403", "HTTP 429", "network failure"])
|
|
def test_failed_inventory_cannot_silently_continue(module, monkeypatch, error):
|
|
monkeypatch.setattr(module.subprocess, "run", lambda *a, **k: SimpleNamespace(returncode=1, stderr=error))
|
|
with pytest.raises(RuntimeError, match=error):
|
|
module.clear_assets("preview", "0.5.2-150", "x86_64-pc-windows-msvc")
|
|
|
|
|
|
def test_retry_step_runs_before_tauri_upload():
|
|
text = (Path(__file__).parents[1] / ".github/workflows/release.yml").read_text()
|
|
start = text.index("- name: Clear this target's installer assets on retry")
|
|
end = text.index("- name: Build + release (Tauri)")
|
|
assert start < end
|
|
assert "if: github.run_attempt > 1" in text[start:end]
|
|
assert "scripts/clear-release-rerun-assets.py" in text[start:end]
|
|
|
|
|
|
@pytest.mark.parametrize("target,arch,sibling", [
|
|
("aarch64-apple-darwin", "aarch64", "x64"),
|
|
("x86_64-apple-darwin", "x64", "aarch64"),
|
|
])
|
|
def test_macos_retry_clears_only_its_versionless_updater(module, target, arch, sibling):
|
|
own = f"VoiceStudio_{arch}.app.tar.gz"
|
|
names = [own, own + ".sig", f"VoiceStudio_{sibling}.app.tar.gz", "latest.json"]
|
|
assert module.matching_assets(names, "0.5.2", target) == [own, own + ".sig"]
|
|
|
|
|
|
def test_deletion_invocation_keeps_nonmatching_assets(module, monkeypatch):
|
|
own = "VoiceStudio_0.5.2-150_x64_en-US.msi"
|
|
calls = []
|
|
def run(args, **kwargs):
|
|
calls.append(args)
|
|
assert kwargs == {"capture_output": True, "text": True}
|
|
if args[2] == "view":
|
|
return SimpleNamespace(returncode=0, stdout=module.json.dumps({"assets": [
|
|
{"name": own}, {"name": own + ".sig"}, {"name": "latest.json"},
|
|
{"name": "VoiceStudio_0.5.2-149_x64_en-US.msi"},
|
|
]}), stderr="")
|
|
return SimpleNamespace(returncode=0, stderr="")
|
|
monkeypatch.setattr(module.subprocess, "run", run)
|
|
module.clear_assets("preview", "0.5.2-150", "x86_64-pc-windows-msvc")
|
|
assert calls == [
|
|
["gh", "release", "view", "preview", "--json", "assets"],
|
|
["gh", "release", "delete-asset", "preview", own, "--yes"],
|
|
["gh", "release", "delete-asset", "preview", own + ".sig", "--yes"],
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("error", ["HTTP 403", "HTTP 429", "network failure"])
|
|
def test_deletion_failure_stops_retry(module, monkeypatch, error):
|
|
calls = []
|
|
def run(args, **kwargs):
|
|
calls.append(args)
|
|
if args[2] == "view":
|
|
return SimpleNamespace(returncode=0, stdout=module.json.dumps({"assets": [
|
|
{"name": "VoiceStudio_0.5.2_x64_en-US.msi"},
|
|
{"name": "VoiceStudio_0.5.2_x64_en-US.msi.sig"},
|
|
]}), stderr="")
|
|
return SimpleNamespace(returncode=1, stderr=error)
|
|
monkeypatch.setattr(module.subprocess, "run", run)
|
|
with pytest.raises(RuntimeError, match=error):
|
|
module.clear_assets("v0.5.2", "0.5.2", "x86_64-pc-windows-msvc")
|
|
assert len(calls) == 2
|
|
|
|
|
|
@pytest.mark.parametrize("error", ["HTTP 404", "release not found"])
|
|
def test_absent_release_needs_no_deletion(module, monkeypatch, error):
|
|
calls = []
|
|
def run(args, **kwargs):
|
|
calls.append(args)
|
|
return SimpleNamespace(returncode=1, stderr=error)
|
|
monkeypatch.setattr(module.subprocess, "run", run)
|
|
module.clear_assets("preview", "0.5.2-150", "x86_64-pc-windows-msvc")
|
|
assert len(calls) == 1
|
|
|
|
|
|
def test_concurrent_missing_asset_is_benign(module, monkeypatch):
|
|
def run(args, **kwargs):
|
|
if args[2] == "view":
|
|
return SimpleNamespace(returncode=0, stdout='{"assets":[{"name":"VoiceStudio_0.5.2_x64_en-US.msi"}]}')
|
|
return SimpleNamespace(returncode=1, stderr="HTTP 404")
|
|
monkeypatch.setattr(module.subprocess, "run", run)
|
|
module.clear_assets("v0.5.2", "0.5.2", "x86_64-pc-windows-msvc")
|
|
|
|
|
|
def test_cleanup_uses_stamped_package_version():
|
|
text = (Path(__file__).parents[1] / ".github/workflows/release.yml").read_text()
|
|
stamp = text.index("- name: Stamp preview version")
|
|
clear = text.index("- name: Clear this target's installer assets on retry")
|
|
build = text.index("- name: Build + release (Tauri)")
|
|
assert stamp < clear < build
|
|
assert 'frontend/package.json' in text[clear:build]
|
|
assert '--version "$VERSION"' in text[clear:build]
|
|
assert "github.run_number" not in text[clear:build]
|
|
|
|
|
|
def test_stable_floor_stamp_selects_new_patch_not_source_package_version(module, tmp_path):
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
|
|
package = tmp_path / "package.json"
|
|
package.write_text(json.dumps({"version": "0.5.2"}))
|
|
script = Path(__file__).parents[1] / "scripts/stamp-preview-version.py"
|
|
subprocess.run([
|
|
sys.executable, str(script), "--package-json", str(package),
|
|
"--stable-tag", "v0.5.2", "--run-number", "150",
|
|
], check=True, capture_output=True, text=True)
|
|
version = json.loads(package.read_text())["version"]
|
|
assert version == "0.5.3-150"
|
|
names = ["VoiceStudio_0.5.2-150_x64_en-US.msi", "VoiceStudio_0.5.3-150_x64_en-US.msi"]
|
|
assert module.matching_assets(names, version, "x86_64-pc-windows-msvc") == [names[1]]
|