Skip to content

Commit aa25c33

Browse files
committed
ci: Switch to strongly typed directives
Replace the current system with something that is more structured and will also catch unknown directives.
1 parent 16d9435 commit aa25c33

File tree

1 file changed

+54
-25
lines changed

1 file changed

+54
-25
lines changed

ci/ci-util.py

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import json
99
import os
10+
import pprint
1011
import re
1112
import subprocess as sp
1213
import sys
@@ -50,15 +51,6 @@
5051
DEFAULT_BRANCH = "master"
5152
WORKFLOW_NAME = "CI" # Workflow that generates the benchmark artifacts
5253
ARTIFACT_PREFIX = "baseline-icount*"
53-
# Place this in a PR body to skip regression checks (must be at the start of a line).
54-
REGRESSION_DIRECTIVE = "ci: allow-regressions"
55-
# Place this in a PR body to skip extensive tests
56-
SKIP_EXTENSIVE_DIRECTIVE = "ci: skip-extensive"
57-
# Place this in a PR body to allow running a large number of extensive tests. If not
58-
# set, this script will error out if a threshold is exceeded in order to avoid
59-
# accidentally spending huge amounts of CI time.
60-
ALLOW_MANY_EXTENSIVE_DIRECTIVE = "ci: allow-many-extensive"
61-
MANY_EXTENSIVE_THRESHOLD = 20
6254

6355
# Don't run exhaustive tests if these files change, even if they contaiin a function
6456
# definition.
@@ -80,6 +72,48 @@ def eprint(*args, **kwargs):
8072
print(*args, file=sys.stderr, **kwargs)
8173

8274

75+
@dataclass(init=False)
76+
class PrCfg:
77+
"""Directives that we allow in the commit body to control test behavior.
78+
79+
These are of the form `ci: foo`, at the start of a line.
80+
"""
81+
82+
# Skip regression checks (must be at the start of a line).
83+
allow_regressions: bool = False
84+
# Don't run extensive tests
85+
skip_extensive: bool = False
86+
87+
# Allow running a large number of extensive tests. If not set, this script
88+
# will error out if a threshold is exceeded in order to avoid accidentally
89+
# spending huge amounts of CI time.
90+
allow_many_extensive: bool = False
91+
92+
# Max number of extensive tests to run by default
93+
MANY_EXTENSIVE_THRESHOLD: int = 20
94+
95+
# String values of directive names
96+
DIR_ALLOW_REGRESSIONS: str = "allow-regressions"
97+
DIR_SKIP_EXTENSIVE: str = "skip-extensive"
98+
DIR_ALLOW_MANY_EXTENSIVE: str = "allow-many-extensive"
99+
100+
def __init__(self, body: str):
101+
directives = re.finditer(r"^\s*ci:\s*(?P<dir_name>\S*)", body, re.MULTILINE)
102+
for dir in directives:
103+
name = dir.group("dir_name")
104+
if name == self.DIR_ALLOW_REGRESSIONS:
105+
self.allow_regressions = True
106+
elif name == self.DIR_SKIP_EXTENSIVE:
107+
self.skip_extensive = True
108+
elif name == self.DIR_ALLOW_MANY_EXTENSIVE:
109+
self.allow_many_extensive = True
110+
else:
111+
eprint(f"Found unexpected directive `{name}`")
112+
exit(1)
113+
114+
pprint.pp(self)
115+
116+
83117
@dataclass
84118
class PrInfo:
85119
"""GitHub response for PR query"""
@@ -88,6 +122,7 @@ class PrInfo:
88122
commits: list[str]
89123
created_at: str
90124
number: int
125+
cfg: PrCfg
91126

92127
@classmethod
93128
def load(cls, pr_number: int | str) -> Self:
@@ -104,13 +139,9 @@ def load(cls, pr_number: int | str) -> Self:
104139
],
105140
text=True,
106141
)
107-
eprint("PR info:", json.dumps(pr_info, indent=4))
108-
return cls(**json.loads(pr_info))
109-
110-
def contains_directive(self, directive: str) -> bool:
111-
"""Return true if the provided directive is on a line in the PR body"""
112-
lines = self.body.splitlines()
113-
return any(line.startswith(directive) for line in lines)
142+
pr_json = json.loads(pr_info)
143+
eprint("PR info:", json.dumps(pr_json, indent=4))
144+
return cls(**json.loads(pr_info), cfg=PrCfg(pr_json["body"]))
114145

115146

116147
class FunctionDef(TypedDict):
@@ -223,10 +254,8 @@ def emit_workflow_output(self):
223254

224255
if pr_number is not None and len(pr_number) > 0:
225256
pr = PrInfo.load(pr_number)
226-
skip_tests = pr.contains_directive(SKIP_EXTENSIVE_DIRECTIVE)
227-
error_on_many_tests = not pr.contains_directive(
228-
ALLOW_MANY_EXTENSIVE_DIRECTIVE
229-
)
257+
skip_tests = pr.cfg.skip_extensive
258+
error_on_many_tests = not pr.cfg.allow_many_extensive
230259

231260
if skip_tests:
232261
eprint("Skipping all extensive tests")
@@ -257,12 +286,12 @@ def emit_workflow_output(self):
257286
eprint(f"may_skip_libm_ci={may_skip}")
258287
eprint(f"total extensive tests: {total_to_test}")
259288

260-
if error_on_many_tests and total_to_test > MANY_EXTENSIVE_THRESHOLD:
289+
if error_on_many_tests and total_to_test > PrCfg.MANY_EXTENSIVE_THRESHOLD:
261290
eprint(
262-
f"More than {MANY_EXTENSIVE_THRESHOLD} tests would be run; add"
263-
f" `{ALLOW_MANY_EXTENSIVE_DIRECTIVE}` to the PR body if this is"
291+
f"More than {PrCfg.MANY_EXTENSIVE_THRESHOLD} tests would be run; add"
292+
f" `{PrCfg.DIR_ALLOW_MANY_EXTENSIVE}` to the PR body if this is"
264293
" intentional. If this is refactoring that happens to touch a lot of"
265-
f" files, `{SKIP_EXTENSIVE_DIRECTIVE}` can be used instead."
294+
f" files, `{PrCfg.DIR_SKIP_EXTENSIVE}` can be used instead."
266295
)
267296
exit(1)
268297

@@ -372,7 +401,7 @@ def handle_bench_regressions(args: list[str]):
372401
exit(1)
373402

374403
pr = PrInfo.load(pr_number)
375-
if pr.contains_directive(REGRESSION_DIRECTIVE):
404+
if pr.cfg.allow_regressions:
376405
eprint("PR allows regressions")
377406
return
378407

0 commit comments

Comments
 (0)