Release-Tags nach Paketbuild direkt veröffentlichen
This commit is contained in:
1 parent
5a1a284505
commit
ccc372778c
19 files changed
+278
-387
No files matched your search
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Vierzielpakete bauen, entpackt prüfen und vollständige Freigaben validieren."""
|
||||
"""Vierzielpakete bauen und optional auf dem Zielsystem prüfen."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
@@ -186,45 +186,6 @@ def target_check(archive, target, output, windows_console_test=None):
|
||||
(output / 'report.json').write_text(json.dumps(report, indent=2) + '\n')
|
||||
|
||||
|
||||
def gate(directory, evidence, revision, version):
|
||||
"""Fail closed: vier exakte Pakete und unabhängige, passende Zielnachweise."""
|
||||
require(re.fullmatch('[0-9a-f]{40}', revision), 'Ungültige Revision')
|
||||
require(digest(ROOT / 'tests/libraries/portable.tbl') == TBL_SHA, 'TBL-Probe verändert')
|
||||
assets = []
|
||||
for target in TARGETS:
|
||||
archive = directory / name(version, target)
|
||||
require(archive.is_file(), f'Pflichtpaket fehlt: {archive.name}')
|
||||
sha = digest(archive)
|
||||
checksum = archive.with_name(archive.name + '.sha256')
|
||||
require(checksum.read_text() == f'{sha} {archive.name}\n', 'Paketprüfsumme falsch')
|
||||
manifest = verify(archive, target, ROOT / 'target/release/tb-template')
|
||||
require(manifest['revision'] == revision and not manifest['source_dirty'], 'Paket aus falschem/uncommittetem Stand')
|
||||
proof = json.loads((evidence / target / 'report.json').read_text())
|
||||
require(proof.get('target') == target and proof.get('revision') == revision and proof.get('package') == archive.name and
|
||||
proof.get('package_sha256') == sha and proof.get('tbl_sha256') == TBL_SHA, 'Zielnachweis passt nicht zum Paket')
|
||||
required = {'native', 'library', 'default-template', 'windows-console' if target == list(TARGETS)[0] else 'ide-pty'}
|
||||
require(proof.get('automated_passed') is True and all(proof.get('checks', {}).get(k) == 'passed' for k in required), 'Automatisierte Zielprüfung fehlt')
|
||||
manual = json.loads((evidence / target / 'manual.json').read_text())
|
||||
require(manual.get('revision') == revision and manual.get('package_sha256') == sha and
|
||||
manual.get('target') == target and manual.get('passed') is True and
|
||||
manual.get('tester') and manual.get('terminal_versions') and manual.get('evidence'), 'Manueller Terminalnachweis fehlt')
|
||||
terminals = {'windows-terminal'} if target == list(TARGETS)[0] else {'terminal.app'} if target == list(TARGETS)[1] else {'xterm', 'vte'}
|
||||
require(isinstance(manual['terminal_versions'], dict) and
|
||||
all(manual['terminal_versions'].get(t) for t in terminals), 'Pflichtemulator/Version fehlt')
|
||||
require(all(manual.get('cases', {}).get(k) is True for k in
|
||||
('f1_f12', 'modifiers', 'mouse', 'unicode_colors', 'resize', 'input_break', 'shell')), 'Manuelle Pflichtfolge unvollständig')
|
||||
for reference in manual['evidence']:
|
||||
relative = Path(reference)
|
||||
require(not relative.is_absolute() and '..' not in relative.parts, 'Ungültiger manueller Nachweispfad')
|
||||
path = evidence / target / relative
|
||||
require(path.is_file() and path.stat().st_size > 0, 'Manueller Beleg fehlt')
|
||||
assets.extend([archive, checksum])
|
||||
acceptance = json.loads((evidence / 'acceptance.json').read_text())
|
||||
require(acceptance.get('revision') == revision and acceptance.get('passed') is True and
|
||||
all(acceptance.get(k) for k in ('foreign', 'inventory', 'workspace', 'benchmarks', 'native_ide', 'run_url', 'buildhost')), 'Pflichtabnahme/Benchmarks fehlen')
|
||||
return assets
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
sub = parser.add_subparsers(dest='command', required=True)
|
||||
@@ -236,13 +197,10 @@ def main():
|
||||
else:
|
||||
p.add_argument('--output', type=Path, required=True)
|
||||
p.add_argument('--windows-console-test', type=Path)
|
||||
p = sub.add_parser('gate'); p.add_argument('--packages', type=Path, required=True); p.add_argument('--evidence', type=Path, required=True)
|
||||
p.add_argument('--revision', required=True); p.add_argument('--version', required=True)
|
||||
args = parser.parse_args()
|
||||
if args.command == 'pack': pack(args.bin_dir.resolve(), args.target, args.output.resolve(), args.inspector.resolve())
|
||||
elif args.command == 'verify': verify(args.archive.resolve(), args.target, args.inspector.resolve() if args.inspector else None)
|
||||
elif args.command == 'target-check': target_check(args.archive.resolve(), args.target, args.output.resolve(), args.windows_console_test.resolve() if args.windows_console_test else None)
|
||||
else: gate(args.packages, args.evidence, args.revision, args.version)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Nur einen bestehenden Tag und Nachweise aus einer Origin-Revision verwenden."""
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import subprocess
|
||||
import tempfile
|
||||
import distribution as dist
|
||||
import release
|
||||
|
||||
tag, evidence_revision = os.environ['TB_TAG'], os.environ['TB_EVIDENCE_REVISION']
|
||||
release.release_version(tag)
|
||||
dist.require(re.fullmatch('[0-9a-f]{40}', evidence_revision), 'Ungültige Nachweisrevision')
|
||||
revision = subprocess.check_output(['git', 'rev-parse', '--verify', tag + '^{commit}'], text=True).strip()
|
||||
api = release.Gitea()
|
||||
info = api.request('GET', '/git/commits/' + evidence_revision)
|
||||
dist.require(info['sha'] == evidence_revision, 'Nachweise nicht am Origin')
|
||||
# Checkout enthält alle Origin-Branches. Ein nicht vorhandener Commit scheitert hier.
|
||||
base = f'release-evidence/{tag}'
|
||||
names = subprocess.check_output(['git', 'ls-tree', '-r', '--name-only', evidence_revision, base], text=True).splitlines()
|
||||
with tempfile.TemporaryDirectory(prefix='tb-release-') as tmp:
|
||||
evidence = Path(tmp) / 'evidence'; evidence.mkdir()
|
||||
for name in names:
|
||||
relative = Path(name).relative_to(base)
|
||||
dist.require('..' not in relative.parts and not relative.is_absolute(), 'Ungültiger Nachweispfad')
|
||||
path = evidence / relative; path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(subprocess.check_output(['git', 'show', evidence_revision + ':' + name]))
|
||||
packages = Path(tmp) / 'packages'; packages.mkdir()
|
||||
draft = api.request('GET', '/releases/tags/' + tag)
|
||||
wanted = release.expected(tag[1:])
|
||||
dist.require(draft['draft'] and len(draft.get('assets', [])) == len(wanted) and
|
||||
{a['name'] for a in draft['assets']} == set(wanted), 'Unvollständiger Entwurf')
|
||||
for asset in draft['assets']:
|
||||
(packages / asset['name']).write_bytes(api.download(asset))
|
||||
# Grundabnahme kommt aus dem ursprünglichen Taglauf, nicht aus manuell ersetzten Angaben.
|
||||
(evidence / 'acceptance.json').write_bytes((packages / 'acceptance.json').read_bytes())
|
||||
release.publish(api, packages, evidence, tag, revision)
|
||||
+12
-43
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Gitea-Entwurf befüllen; erst nach exakten Paket-/Zielnachweisen freigeben."""
|
||||
"""Taggebundene Vierzielpakete hochladen und direkt auf Gitea veröffentlichen."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -37,12 +36,6 @@ class Gitea:
|
||||
with self.open(Request(self.api + path, data=payload, headers=headers, method=method), timeout=120) as response:
|
||||
return json.load(response)
|
||||
|
||||
def download(self, asset):
|
||||
url = asset['browser_download_url']
|
||||
dist.require(urlparse(url).netloc == urlparse(self.base).netloc, 'Fremder Assetserver')
|
||||
with self.open(Request(url, headers={'Authorization': 'token ' + self.token}), timeout=120) as response:
|
||||
return response.read()
|
||||
|
||||
|
||||
def release_version(tag):
|
||||
dist.require(re.fullmatch(r'v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)', tag),
|
||||
@@ -51,10 +44,10 @@ def release_version(tag):
|
||||
|
||||
|
||||
def expected(version):
|
||||
return [n for t in dist.TARGETS for n in (dist.name(version, t), dist.name(version, t) + '.sha256')] + ['acceptance.json']
|
||||
return [n for t in dist.TARGETS for n in (dist.name(version, t), dist.name(version, t) + '.sha256')]
|
||||
|
||||
|
||||
def stage(api, directory, tag, revision):
|
||||
def publish(api, directory, tag, revision):
|
||||
version = release_version(tag)
|
||||
dist.require(re.fullmatch('[0-9a-f]{40}', revision), 'Tag/Revision ungültig')
|
||||
dist.require(api.request('GET', '/tags/' + quote(tag, safe=''))['commit']['sha'] == revision, 'Tag zeigt auf andere Revision')
|
||||
@@ -64,27 +57,28 @@ def stage(api, directory, tag, revision):
|
||||
manifest = dist.verify(path, target, dist.ROOT / 'target/release/tb-template')
|
||||
dist.require(manifest['revision'] == revision and not manifest['source_dirty'], 'Paketrevision falsch/uncommitted')
|
||||
dist.require(path.with_name(path.name + '.sha256').read_text() == f'{dist.digest(path)} {path.name}\n', 'Paketprüfsumme falsch')
|
||||
acceptance = json.loads((directory / 'acceptance.json').read_text())
|
||||
dist.require(acceptance.get('revision') == revision and acceptance.get('passed') is True, 'Grundabnahme fehlt')
|
||||
try:
|
||||
release = api.request('GET', '/releases/tags/' + quote(tag, safe=''))
|
||||
except HTTPError as error:
|
||||
if error.code != 404:
|
||||
raise
|
||||
error.close()
|
||||
release = api.request('POST', '/releases', dict(tag_name=tag, target_commitish=revision,
|
||||
name='Terminal Basic ' + version, draft=True,
|
||||
body='Vierzielpakete im Entwurf. Zielnachweise fehlen noch; keine Freigabe.'))
|
||||
body=f'Vierzielpakete für Revision {revision}. Upload läuft.'))
|
||||
dist.require(release['draft'], 'Bereits veröffentlichten Release nicht verändern')
|
||||
# Wiederholungen dürfen identische Uploads ergänzen, keine vorhandenen Assets ersetzen.
|
||||
# Wiederholungen ergänzen fehlende Uploads; vorhandene Assets werden nicht ersetzt.
|
||||
existing = {a['name']: a for a in release.get('assets', [])}
|
||||
dist.require(set(existing) <= set(names), 'Unerwartete Assets im Entwurf')
|
||||
for name in names:
|
||||
data = (directory / name).read_bytes()
|
||||
if name in existing:
|
||||
dist.require(api.download(existing[name]) == data, f'Abweichendes vorhandenes Asset: {name}')
|
||||
dist.require(existing[name]['size'] == len(data), f'Abweichende Größe des vorhandenen Assets: {name}')
|
||||
else:
|
||||
api.request('POST', f'/releases/{release["id"]}/assets?name=' + quote(name), data)
|
||||
verify_uploads(api, release['id'], directory, names)
|
||||
api.request('PATCH', f'/releases/{release["id"]}', dict(draft=False,
|
||||
body=f'Terminal Basic {version}, Revision {revision}. Vier Zielpakete mit SHA-256-Prüfsummen.'))
|
||||
return release['id']
|
||||
|
||||
|
||||
@@ -93,42 +87,17 @@ def verify_uploads(api, release_id, directory, names):
|
||||
assets = release.get('assets', [])
|
||||
dist.require(len(assets) == len(names) and {a['name'] for a in assets} == set(names), 'Unvollständiger Asset-Upload')
|
||||
for asset in assets:
|
||||
dist.require(hashlib.sha256(api.download(asset)).hexdigest() == dist.digest(directory / asset['name']), 'Uploadprüfsumme falsch')
|
||||
dist.require(asset['size'] == (directory / asset['name']).stat().st_size,
|
||||
f'Uploadgröße falsch: {asset["name"]}')
|
||||
return release
|
||||
|
||||
|
||||
def publish(api, directory, evidence, tag, revision):
|
||||
version = release_version(tag)
|
||||
dist.require(api.request('GET', '/tags/' + quote(tag, safe=''))['commit']['sha'] == revision, 'Tag zeigt auf andere Revision')
|
||||
dist.gate(directory, evidence, revision, version)
|
||||
release = api.request('GET', '/releases/tags/' + quote(tag, safe=''))
|
||||
verify_uploads(api, release['id'], directory, expected(version))
|
||||
dist.require(release['draft'], 'Bereits freigegebenen Release nicht verändern')
|
||||
api.request('PATCH', f'/releases/{release["id"]}', dict(draft=False,
|
||||
body=f'Geprüfte Vierzielpakete für Revision {revision}. Zugehörige Zielnachweise: {os.environ.get("TB_EVIDENCE_REVISION", "lokaler Nachweissatz")}.'))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('command', choices=['stage', 'download', 'publish'])
|
||||
parser.add_argument('--packages', type=Path, required=True)
|
||||
parser.add_argument('--tag', required=True); parser.add_argument('--revision', required=True)
|
||||
parser.add_argument('--evidence', type=Path)
|
||||
args = parser.parse_args()
|
||||
api = Gitea()
|
||||
if args.command == 'stage': stage(api, args.packages, args.tag, args.revision)
|
||||
elif args.command == 'publish':
|
||||
dist.require(args.evidence, '--evidence erforderlich')
|
||||
publish(api, args.packages, args.evidence, args.tag, args.revision)
|
||||
else:
|
||||
args.packages.mkdir(parents=True) # Nie lokale Kandidaten überschreiben.
|
||||
release = api.request('GET', '/releases/tags/' + quote(args.tag, safe=''))
|
||||
names = expected(release_version(args.tag))
|
||||
dist.require(release['draft'], 'Nur bestehende Entwürfe freigeben')
|
||||
assets = release.get('assets', [])
|
||||
dist.require(len(assets) == len(names) and {a['name'] for a in assets} == set(names), 'Pflichtassets fehlen')
|
||||
for asset in assets:
|
||||
(args.packages / asset['name']).write_bytes(api.download(asset))
|
||||
publish(Gitea(), args.packages, args.tag, args.revision)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1,22 +1,49 @@
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import tarfile
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from urllib.error import HTTPError
|
||||
import distribution as d
|
||||
import release as r
|
||||
|
||||
|
||||
class API:
|
||||
"""Release-API ohne Downloadpfad; protokolliert jede Mutation."""
|
||||
def __init__(self):
|
||||
self.release = None
|
||||
self.fail_at = None
|
||||
self.writes = []
|
||||
self.omit_upload = False
|
||||
self.wrong_size = False
|
||||
|
||||
def request(self, method, path, data=None):
|
||||
if path.startswith('/tags/'):
|
||||
return {'commit': {'sha': 'a' * 40}}
|
||||
if method == 'GET':
|
||||
if self.release is None:
|
||||
raise HTTPError(path, 404, 'Not Found', {}, None)
|
||||
return copy.deepcopy(self.release)
|
||||
self.writes.append((method, path))
|
||||
if method == 'POST' and path == '/releases':
|
||||
self.release = dict(id=1, assets=[], **data)
|
||||
elif method == 'POST':
|
||||
name = path.split('name=')[1]
|
||||
if name == self.fail_at:
|
||||
raise OSError('simulierter Uploadfehler')
|
||||
if not self.omit_upload:
|
||||
self.release['assets'].append(dict(name=name, size=len(data) + self.wrong_size))
|
||||
elif method == 'PATCH':
|
||||
self.release.update(data)
|
||||
return copy.deepcopy(self.release)
|
||||
|
||||
|
||||
class DistributionTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(self.temp.cleanup)
|
||||
self.root = Path(self.temp.name)
|
||||
self.packages = self.root / 'packages'; self.packages.mkdir()
|
||||
self.evidence = self.root / 'evidence'; self.evidence.mkdir()
|
||||
self.packages = Path(self.temp.name)
|
||||
self.revision = 'a' * 40
|
||||
self.version = '1.0.0'
|
||||
self.manifests = {}
|
||||
@@ -25,22 +52,11 @@ class DistributionTests(unittest.TestCase):
|
||||
path.write_bytes(target.encode())
|
||||
path.with_name(path.name + '.sha256').write_text(f'{d.digest(path)} {path.name}\n')
|
||||
self.manifests[target] = dict(revision=self.revision, version=self.version, source_dirty=False)
|
||||
proof = self.evidence / target; proof.mkdir()
|
||||
report = dict(target=target, revision=self.revision, package=path.name,
|
||||
package_sha256=d.digest(path), tbl_sha256=d.TBL_SHA, automated_passed=True,
|
||||
checks={k:'passed' for k in ('native','library','ide-pty','windows-console','default-template')})
|
||||
(proof / 'report.json').write_text(json.dumps(report))
|
||||
(proof / 'manual.json').write_text(json.dumps(dict(target=target, revision=self.revision,
|
||||
package_sha256=d.digest(path), passed=True, tester='Prüfer',
|
||||
terminal_versions={k:'Version 1' for k in ('windows-terminal','terminal.app','xterm','vte')},
|
||||
cases={k:True for k in ('f1_f12','modifiers','mouse','unicode_colors','resize','input_break','shell')}, evidence=['terminal.txt'])))
|
||||
(proof / 'terminal.txt').write_text('Bedienprotokoll')
|
||||
acceptance = dict(revision=self.revision, passed=True, foreign=True, inventory=True,
|
||||
workspace=True, benchmarks=True, native_ide=True, run_url='https://git.rfc1437.de/run/1', buildhost='Linux arm64')
|
||||
for directory in (self.packages, self.evidence):
|
||||
(directory / 'acceptance.json').write_text(json.dumps(acceptance))
|
||||
self.mock_verify = patch.object(d, 'verify', side_effect=lambda archive, target, *args: self.manifests[target])
|
||||
self.mock_verify.start(); self.addCleanup(self.mock_verify.stop)
|
||||
mock_verify = patch.object(d, 'verify', side_effect=lambda archive, target, *args: self.manifests[target])
|
||||
mock_verify.start(); self.addCleanup(mock_verify.stop)
|
||||
|
||||
def publish(self, api):
|
||||
return r.publish(api, self.packages, 'v' + self.version, self.revision)
|
||||
|
||||
def test_release_tags_are_exact_numeric_versions(self):
|
||||
for tag in ('v1.0.0', 'v0.0.0', 'v12.34.56'):
|
||||
@@ -49,80 +65,56 @@ class DistributionTests(unittest.TestCase):
|
||||
'v1.0.0/extra', 'v01.0.0', 'v1x.0.0', 'v1.0.0\n'):
|
||||
with self.assertRaises(ValueError, msg=tag): r.release_version(tag)
|
||||
|
||||
def gate(self):
|
||||
return d.gate(self.packages, self.evidence, self.revision, self.version)
|
||||
|
||||
def test_complete_gate_and_missing_or_wrong_evidence(self):
|
||||
self.assertEqual(len(self.gate()), 8)
|
||||
target = list(d.TARGETS)[0]
|
||||
path = self.evidence / target / 'report.json'
|
||||
valid = json.loads(path.read_text())
|
||||
for key, value in [('revision','b'*40), ('target',list(d.TARGETS)[1]), ('package_sha256','0'*64),
|
||||
('tbl_sha256','0'*64), ('automated_passed',False), ('checks',{'native':'passed'})]:
|
||||
broken = dict(valid, **{key:value}); path.write_text(json.dumps(broken))
|
||||
with self.assertRaises(ValueError, msg=key): self.gate()
|
||||
path.write_text(json.dumps(valid))
|
||||
(self.evidence / target / 'manual.json').unlink()
|
||||
with self.assertRaises(FileNotFoundError): self.gate()
|
||||
|
||||
def test_missing_corrupt_dirty_and_mismatched_packages(self):
|
||||
def test_bad_packages_prevent_any_release_mutation(self):
|
||||
api = API()
|
||||
first = next(iter(d.TARGETS))
|
||||
self.manifests[first]['source_dirty'] = True
|
||||
with self.assertRaises(ValueError): self.gate()
|
||||
self.manifests[first]['source_dirty'] = False
|
||||
for key, value in [('source_dirty', True), ('revision', 'b' * 40)]:
|
||||
original = self.manifests[first][key]
|
||||
self.manifests[first][key] = value
|
||||
with self.assertRaises(ValueError): self.publish(api)
|
||||
self.manifests[first][key] = original
|
||||
path = self.packages / d.name(self.version, first)
|
||||
path.write_bytes(b'broken')
|
||||
with self.assertRaises(ValueError): self.gate()
|
||||
with self.assertRaises(ValueError): self.publish(api)
|
||||
path.unlink()
|
||||
with self.assertRaises(ValueError): self.gate()
|
||||
with self.assertRaises(FileNotFoundError): self.publish(api)
|
||||
self.assertEqual(api.writes, [])
|
||||
|
||||
def test_archive_traversal_rejected_before_extract(self):
|
||||
path = self.root / 'evil.tar.gz'
|
||||
path = self.packages / 'evil.tar.gz'
|
||||
with tarfile.open(path, 'w:gz') as tar:
|
||||
entry = tarfile.TarInfo('../escape'); tar.addfile(entry)
|
||||
dest = self.root / 'unpack'; dest.mkdir()
|
||||
tar.addfile(tarfile.TarInfo('../escape'))
|
||||
dest = self.packages / 'unpack'; dest.mkdir()
|
||||
with self.assertRaises(ValueError): d.unpack(path, dest, list(d.TARGETS)[1])
|
||||
self.assertFalse((self.root / 'escape').exists())
|
||||
self.assertFalse((self.packages / 'escape').exists())
|
||||
|
||||
def test_upload_failure_never_publishes_or_overwrites(self):
|
||||
class API:
|
||||
def __init__(self):
|
||||
self.release = dict(id=1, draft=True, assets=[])
|
||||
self.data = {}; self.fail_at = None; self.writes = []
|
||||
def request(self, method, path, data=None):
|
||||
if path.startswith('/tags/'): return {'commit': {'sha': 'a'*40}}
|
||||
if method == 'GET': return copy.deepcopy(self.release)
|
||||
self.writes.append((method,path))
|
||||
if method == 'POST':
|
||||
name = path.split('name=')[1]
|
||||
if name == self.fail_at: raise OSError('simulierter Uploadfehler')
|
||||
self.data[name] = data
|
||||
self.release['assets'].append(dict(name=name, browser_download_url=name))
|
||||
return {}
|
||||
if method == 'PATCH': self.release.update(data)
|
||||
def download(self, asset): return self.data[asset['name']]
|
||||
def test_upload_failure_then_retry_publishes_directly_without_evidence_or_download(self):
|
||||
api = API(); api.fail_at = r.expected(self.version)[2]
|
||||
with self.assertRaises(OSError): r.stage(api, self.packages, 'v'+self.version, self.revision)
|
||||
with self.assertRaises(OSError): self.publish(api)
|
||||
self.assertTrue(api.release['draft'])
|
||||
self.assertFalse(any(method=='PATCH' for method,_ in api.writes))
|
||||
self.assertFalse(any(method == 'PATCH' for method, _ in api.writes))
|
||||
self.assertEqual(len(api.release['assets']), 2)
|
||||
before_retry = len(api.writes)
|
||||
api.fail_at = None
|
||||
r.stage(api, self.packages, 'v'+self.version, self.revision)
|
||||
r.publish(api, self.packages, self.evidence, 'v'+self.version, self.revision)
|
||||
self.publish(api)
|
||||
self.assertFalse(api.release['draft'])
|
||||
self.assertEqual(len(api.release['assets']), 8)
|
||||
self.assertEqual(len(api.writes[before_retry:]), 7) # sechs fehlende Assets + Freigabe
|
||||
self.assertEqual(api.writes[-1], ('PATCH', '/releases/1'))
|
||||
before = list(api.writes)
|
||||
with self.assertRaises(ValueError): r.stage(api, self.packages, 'v'+self.version, self.revision)
|
||||
with self.assertRaises(ValueError): self.publish(api)
|
||||
self.assertEqual(before, api.writes)
|
||||
|
||||
def test_incomplete_remote_upload_blocks_publish(self):
|
||||
class API:
|
||||
def request(self, method, path, data=None):
|
||||
self.assert_get(method)
|
||||
if path.startswith('/tags/'): return {'commit': {'sha': 'a'*40}}
|
||||
return dict(id=1, draft=True, assets=[])
|
||||
@staticmethod
|
||||
def assert_get(method):
|
||||
if method != 'GET': raise AssertionError('Unerwartete Mutation')
|
||||
with self.assertRaises(ValueError): r.publish(API(), self.packages, self.evidence, 'v'+self.version, self.revision)
|
||||
def test_incomplete_or_wrong_sized_upload_never_publishes(self):
|
||||
for fault in ('omit_upload', 'wrong_size'):
|
||||
api = API(); setattr(api, fault, True)
|
||||
with self.assertRaises(ValueError): self.publish(api)
|
||||
self.assertTrue(api.release['draft'])
|
||||
self.assertFalse(any(method == 'PATCH' for method, _ in api.writes))
|
||||
api.wrong_size = False
|
||||
before = list(api.writes)
|
||||
with self.assertRaises(ValueError): self.publish(api)
|
||||
self.assertEqual(before, api.writes) # vorhandene abweichende Assets bleiben erhalten
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in new issue
Block a user