Show failure details in CI and deploy summaries
ci / Formatting (push) Skipped
ci / Python and tests (push) Skipped
ci / Kubernetes (push) Skipped
ci / Compose (push) Skipped
ci / Shell (push) Skipped
ci / YAML (push) Skipped
ci / Dockerfiles (push) Skipped
ci / Workflows (push) Skipped
ci / Compose (pull_request) Successful in 11s
ci / Workflows (pull_request) Failing after 8s
ci / Formatting (pull_request) Canceled after 0s
ci / Python and tests (pull_request) Canceled after 0s
ci / YAML (pull_request) Canceled after 0s
ci / Dockerfiles (pull_request) Canceled after 0s
ci / Kubernetes (pull_request) Canceled after 0s
ci / build (pull_request) Canceled after 0s
ci / Shell (pull_request) Canceled after 7s

This commit is contained in:
forust committed 2026-10-06 23:44:00 +02:00
1 parent d74822cd27
commit 0c76426c17
8 files changed
+432 -45

No files matched your search

+58
View File
@@ -213,6 +213,64 @@ class DurableRunTests(unittest.TestCase):
self.assertEqual(json.loads((directory / 'status.json').read_text())['state'], 'failure')
class FailureSummaryTests(unittest.TestCase):
def test_build_failure_keeps_progress_and_does_not_expose_exception_text(self):
with tempfile.TemporaryDirectory() as scratch:
summary = Path(scratch) / 'summary.md'
def failed_build(_output, report):
report.update(phase='Build or reuse xdfnx-homepage', current='xdfnx-homepage', built=['error-pages'])
report['images']['gcr.forust.xyz/forust/error-pages'] = 'sha256:' + 'b' * 64
raise RuntimeError('private value must not appear in the summary')
with (
patch.dict(os.environ, {'GITHUB_STEP_SUMMARY': str(summary), 'GITHUB_SHA': 'a' * 40}),
patch.object(release_module, 'build_images', side_effect=failed_build),
self.assertRaises(RuntimeError),
):
release_module.build(Path(scratch) / 'release.json')
content = summary.read_text()
self.assertIn('**failure**', content)
self.assertIn('error-pages', content)
self.assertIn('xdfnx-homepage', content)
self.assertNotIn('private value', content)
def test_deploy_failure_reports_completed_apply_and_rollback_result(self):
with tempfile.TemporaryDirectory() as scratch:
state = Path(scratch)
directory = state / 'runs/123-1'
snapshot = directory / 'snapshot/before'
snapshot.mkdir(parents=True)
(directory / 'snapshot/current').write_text(str(snapshot))
(snapshot / 'failed-workloads').write_text('deployment app api\nROLLED_BACK=1\nUNRECOVERED=0\n')
controller.atomic_json(
directory / 'request.json', {'release': release(), 'mode': 'changed', 'refresh_images': False}
)
controller.atomic_json(
directory / 'status.json',
{
'state': 'failure',
'stages': {
'apply-k8s': {'result': 'success', 'exit_code': 0},
'verify-k8s': {'result': 'failure', 'exit_code': 1},
},
},
)
controller.atomic_json(directory / 'plan.json', {'selected': {'k8s': ['app'], 'compose': []}})
(directory / 'apply-events.jsonl').write_text(
json.dumps({'action': 'kubectl', 'target': 'app/k8s/api.yaml', 'result': 'success'}) + '\n'
)
output = io.StringIO()
with patch.object(controller, 'STATE', state), patch('sys.stdout', output):
controller.summary('123-1')
content = output.getvalue()
self.assertIn('verify-k8s | failure | 1', content)
self.assertIn('app/k8s/api.yaml', content)
self.assertIn('Workloads restored: **1**', content)
self.assertIn('manual recovery: **0**', content)
self.assertIn('Compose requires manual recovery', content)
class InstallerTests(unittest.TestCase):
def test_version_comparison_is_exact_without_network_or_host_packages(self):
with tempfile.TemporaryDirectory() as scratch: