-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathupdate.py
134 lines (120 loc) · 3.77 KB
/
update.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#!/usr/bin/env python3
from __future__ import annotations
from enum import Enum
import json
from pathlib import Path
import re
from typing import Dict
import click
from pydantic import BaseModel, TypeAdapter
import requests
BADGE_DIR = Path(__file__).with_name("badges")
STATUS_FILE = Path(__file__).with_name("status.json")
class Status(Enum):
PASSING = "passing"
FAILING = "failing"
UNKNOWN = "unknown"
@classmethod
def from_rc(cls, rc: int) -> "Status":
return cls.PASSING if rc == 0 else cls.FAILING
@property
def color(self) -> str:
if self is Status.PASSING:
return "success"
elif self is Status.FAILING:
return "critical"
elif self is Status.UNKNOWN:
return "inactive"
else:
raise AssertionError(f"Unhandled Status member: {self!r}")
class ClientStatus(BaseModel):
highest_build: int
tests: Dict[str, Status]
def get_status(self) -> Status:
status = Status.UNKNOWN
for st in self.tests.values():
if st is Status.PASSING:
status = st
elif st is Status.FAILING:
status = st
break
return status
@click.command()
@click.argument("result_branch")
@click.argument(
"rcfiles", nargs=-1, type=click.Path(exists=True, dir_okay=False, path_type=Path)
)
def main(result_branch: str, rcfiles: tuple[Path, ...]) -> None:
m = re.fullmatch(r"result-(.+)-(\d+)", result_branch)
if not m:
raise ValueError(f"Invalid result branch name: {result_branch!r}")
clientid = m[1]
buildno = int(m[2])
rcs = {f.stem: int(f.read_text()) for f in rcfiles}
with STATUS_FILE.open() as fp:
data = json.load(fp)
adapter = TypeAdapter(Dict[str, ClientStatus])
status = adapter.validate_python(data)
try:
client = status[clientid]
except KeyError:
client = status[clientid] = ClientStatus(
highest_build=buildno,
tests={k: Status.from_rc(v) for k, v in rcs.items()},
)
else:
if buildno <= client.highest_build:
# Print a message?
return
client.highest_build = buildno
unupdated = set(client.tests.keys())
for test, rc in rcs.items():
client.tests[test] = Status.from_rc(rc)
unupdated.discard(test)
for test in unupdated:
client.tests[test] = Status.UNKNOWN
STATUS_FILE.write_bytes(adapter.dump_json(status, indent=4) + b"\n")
client_status = client.get_status()
global_status = Status.UNKNOWN
for cl in status.values():
st = cl.get_status()
if st is Status.PASSING:
global_status = st
elif st is Status.FAILING:
global_status = st
break
with requests.Session() as s:
download_badge(
s,
BADGE_DIR / ".all-clients.svg",
"Tests on Clients",
global_status.value,
global_status.color,
)
download_badge(
s,
BADGE_DIR / f"{clientid}.svg",
"Tests",
client_status.value,
client_status.color,
)
for test, st in client.tests.items():
download_badge(
s,
BADGE_DIR / clientid / f"{test}.svg",
test,
st.value,
st.color,
)
def download_badge(
s: requests.Session, path: Path, label: str, message: str, color: str
) -> None:
r = s.get(
"https://img.shields.io/static/v1",
params={"label": label, "message": message, "color": color},
)
r.raise_for_status()
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(r.content)
if __name__ == "__main__":
main()