v31.1-roots.1 · Commit 8: [PATCH 08/17] chore(roots): integrate v31 identity and maintenance tooling
ci/test/test_workflow_selection.py
Before: /dev/null · After: ci/test/test_workflow_selection.py
File metadata
new file mode 100644 index 0000000000000000000000000000000000000000..5520cc40bde154f28d6621f5682abdcb8f5abafc
Download original patch · Release notes
Diff rows 1–70 of 70. Line numbers refer to the original before/after files.
+ Added− RemovedUnmarked lines are context.
Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.
ci/test/test_workflow_selection.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) 2026-present The Bitcoin Roots developers
|
|
3
|
+
# Distributed under the MIT software license, see the accompanying file COPYING.
|
|
4
|
+
"""Execute the actual nightly matrix selector and PR result gate locally."""
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
import subprocess
|
|
10
|
+
import tempfile
|
|
11
|
+
import unittest
|
|
12
|
+
|
|
13
|
+
ROOT = Path(__file__).resolve().parents[2]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_block(file, name):
|
|
17
|
+
text = file.read_text()
|
|
18
|
+
start = text.index('- name: ' + name)
|
|
19
|
+
rest = text[start:]
|
|
20
|
+
match = re.search(r' run: \|\n((?: [^\n]*\n|\n)+)', rest)
|
|
21
|
+
if not match:
|
|
22
|
+
raise AssertionError(name)
|
|
23
|
+
return '\n'.join(line[10:] if line.startswith(' ') else line for line in match[1].splitlines())
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class WorkflowSelectionTest(unittest.TestCase):
|
|
27
|
+
def nightly(self, suite):
|
|
28
|
+
block = run_block(ROOT / '.github/workflows/nightly.yml', 'Select requested Linux assurance suites')
|
|
29
|
+
with tempfile.TemporaryDirectory() as directory:
|
|
30
|
+
output = Path(directory) / 'output'
|
|
31
|
+
result = subprocess.run(['bash', '-c', block], env=os.environ | {'SUITE': suite, 'GITHUB_OUTPUT': str(output)}, capture_output=True, text=True, timeout=5)
|
|
32
|
+
matrices = dict(line.split('=', 1) for line in output.read_text().splitlines()) if output.exists() else {}
|
|
33
|
+
return result, {key: json.loads(value) for key, value in matrices.items()}
|
|
34
|
+
|
|
35
|
+
def test_all_preserves_extended_platform_and_sanitizer_coverage(self):
|
|
36
|
+
result, matrices = self.nightly('all')
|
|
37
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
38
|
+
self.assertEqual(len(matrices['value']['include']), 4)
|
|
39
|
+
self.assertEqual(len(matrices['sanitizers']['include']), 3)
|
|
40
|
+
for matrix in matrices.values():
|
|
41
|
+
for row in matrix['include']:
|
|
42
|
+
self.assertTrue((ROOT / row['file-env']).is_file())
|
|
43
|
+
self.assertGreater(row['timeout'], 60)
|
|
44
|
+
|
|
45
|
+
def test_specific_suites_do_not_create_unrelated_matrix_jobs(self):
|
|
46
|
+
for suite in ['tsan', 'msan', 'sanitizers', 'tidy', 'i686', 'platforms', 'previous-releases', 'extended-functional', 'fuzz', 'macos-fuzz', 'windows-fuzz', 'arm32', 'kernel', 'macos-smoke', 'windows-smoke']:
|
|
47
|
+
with self.subTest(suite=suite):
|
|
48
|
+
result, matrices = self.nightly(suite)
|
|
49
|
+
self.assertEqual(result.returncode, 0, result.stderr)
|
|
50
|
+
self.assertLessEqual(sum(len(m['include']) for m in matrices.values()), 3)
|
|
51
|
+
result, _ = self.nightly('unrecognized-suite')
|
|
52
|
+
self.assertNotEqual(result.returncode, 0)
|
|
53
|
+
|
|
54
|
+
def test_selected_failures_cannot_pass_the_pr_result_gate(self):
|
|
55
|
+
block = run_block(ROOT / '.github/workflows/ci.yml', 'Enforce selected required results')
|
|
56
|
+
env = {'CLASSIFY': 'success', 'LINT': 'success'}
|
|
57
|
+
for key in ['GUI', 'SANITIZERS', 'FUZZ', 'NIGHTLY_SANITIZERS', 'NIGHTLY_FUZZ', 'NIGHTLY_PLATFORMS', 'NIGHTLY_FULL']:
|
|
58
|
+
env[key] = 'skipped'
|
|
59
|
+
env[key + '_SELECTED'] = 'false'
|
|
60
|
+
base = subprocess.run(['bash', '-c', block], env=os.environ | env, capture_output=True, timeout=5)
|
|
61
|
+
self.assertEqual(base.returncode, 0, base.stderr)
|
|
62
|
+
for name in ['GUI', 'SANITIZERS', 'FUZZ', 'NIGHTLY_FUZZ']:
|
|
63
|
+
for status in ['failure', 'cancelled', 'skipped']:
|
|
64
|
+
with self.subTest(job=name, status=status):
|
|
65
|
+
failed = subprocess.run(['bash', '-c', block], env=os.environ | env | {name + '_SELECTED': 'true', name: status}, capture_output=True, timeout=5)
|
|
66
|
+
self.assertNotEqual(failed.returncode, 0)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
if __name__ == '__main__':
|
|
70
|
+
unittest.main()
|