Skip to content

Commit f794107

Browse files
committed
1 parent 05bd281 commit f794107

File tree

3 files changed

+89
-61
lines changed

3 files changed

+89
-61
lines changed

doc/developer/ci-regexp.md

+9
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,12 @@ ci-regexp: failed to listen on address.*Address already in use (os error 98)
1010
ci-regexp: unexpected peek multiplicity
1111
ci-regexp: Expected identifier, found operator
1212
```
13+
14+
Additionally you can use the `ci-apply-to` to limit the `ci-regexp` to a specific buildkite test case:
15+
16+
Examples:
17+
18+
```
19+
ci-apply-to: SQLsmith
20+
ci-apply-to: sqlsmith explain
21+
```

misc/python/materialize/cli/ci_logged_errors_detect.py

+79-61
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@
1414
import os
1515
import re
1616
import sys
17-
from typing import Any, Dict, List
17+
from typing import Any, List, Optional, Set
1818

19-
import junit_xml
2019
import requests
2120

22-
from materialize import ROOT, ci_util
21+
from materialize import ci_util, spawn
2322

2423
CI_RE = re.compile("ci-regexp: (.*)")
24+
CI_APPLY_TO = re.compile("ci-apply-to: (.*)")
2525
ERROR_RE = re.compile(
2626
r"""
2727
( panicked\ at
@@ -55,8 +55,9 @@
5555

5656

5757
class KnownIssue:
58-
def __init__(self, regex: str, info: Any):
58+
def __init__(self, regex: str, apply_to: Optional[str], info: Any):
5959
self.regex = re.compile(regex)
60+
self.apply_to = apply_to
6061
self.info = info
6162

6263

@@ -83,98 +84,108 @@ def main() -> int:
8384
return 0
8485

8586

87+
def annotate_errors(errors: List[str], title: str, style: str) -> None:
88+
if not errors:
89+
return
90+
91+
suite_name = os.getenv("BUILDKITE_LABEL") or "Logged Errors"
92+
93+
error_str = "\n".join(f"* {error}" for error in errors)
94+
95+
if style == "info":
96+
markdown = f"""<details><summary>{suite_name}: {title}</summary>
97+
98+
{error_str}
99+
</details>"""
100+
else:
101+
markdown = f"""{suite_name}: {title}
102+
103+
{error_str}"""
104+
105+
spawn.runv(
106+
[
107+
"buildkite-agent",
108+
"annotate",
109+
f"--style={style}",
110+
f"--context={os.environ['BUILDKITE_JOB_ID']}-{style}",
111+
],
112+
stdin=markdown.encode(),
113+
)
114+
115+
86116
def annotate_logged_errors(log_files: List[str]) -> None:
87117
error_logs = get_error_logs(log_files)
88118

89119
if not error_logs:
90120
return
91121

92-
known_issues = get_known_issues_from_github()
122+
step_key: str = os.getenv("BUILDKITE_STEP_KEY", "")
123+
buildkite_label: str = os.getenv("BUILDKITE_LABEL", "")
93124

94-
step_key = os.getenv("BUILDKITE_STEP_KEY")
95-
suite_name = step_key or "Logged Errors"
96-
junit_suite = junit_xml.TestSuite(suite_name)
125+
known_issues = get_known_issues_from_github()
97126

98127
artifacts = ci_util.get_artifacts()
99128
job = os.getenv("BUILDKITE_JOB_ID")
100129

101-
# Keep track of known errors so we log each only once, and attach the
102-
# additional occurences to the same junit-xml test case.
103-
dict_issue_number_to_test_case_index: Dict[int, int] = {}
130+
unknown_errors: List[str] = []
131+
known_errors: List[str] = []
132+
133+
# Keep track of known errors so we log each only once
134+
already_reported_issue_numbers: Set[int] = set()
104135

105136
for error in error_logs:
106137
for artifact in artifacts:
107138
if artifact["job_id"] == job and artifact["path"] == error.file:
108139
org = os.environ["BUILDKITE_ORGANIZATION_SLUG"]
109140
pipeline = os.environ["BUILDKITE_PIPELINE_SLUG"]
110141
build = os.environ["BUILDKITE_BUILD_NUMBER"]
111-
linked_file = f'<a href="https://buildkite.com/organizations/{org}/pipelines/{pipeline}/builds/{build}/jobs/{artifact["job_id"]}/artifacts/{artifact["id"]}">{error.file}</a>'
142+
linked_file = f'[{error.file}](https://buildkite.com/organizations/{org}/pipelines/{pipeline}/builds/{build}/jobs/{artifact["job_id"]}/artifacts/{artifact["id"]})'
112143
break
113144
else:
114145
linked_file = error.file
115146

116147
for issue in known_issues:
117148
match = issue.regex.search(error.line)
118149
if match and issue.info["state"] == "open":
119-
message = f"Known error in logs: <a href=\"{issue.info['html_url']}\">{issue.info['title']} (#{issue.info['number']})</a><br/>In {linked_file}:{error.line_nr}:"
120-
if issue.info["number"] in dict_issue_number_to_test_case_index:
121-
junit_suite.test_cases[
122-
dict_issue_number_to_test_case_index[issue.info["number"]]
123-
].add_failure_info(message=message, output=error.line)
124-
else:
125-
test_case = junit_xml.TestCase(
126-
f"log error {len(junit_suite.test_cases) + 1} (known)",
127-
suite_name,
128-
allow_multiple_subelements=True,
150+
if issue.apply_to and issue.apply_to not in (
151+
step_key.lower(),
152+
buildkite_label.lower(),
153+
):
154+
continue
155+
156+
if issue.info["number"] not in already_reported_issue_numbers:
157+
known_errors.append(
158+
f"[{issue.info['title']} (#{issue.info['number']})]({issue.info['html_url']}) in {linked_file}:{error.line_nr}: \n``{error.line}``"
129159
)
130-
test_case.add_failure_info(message=message, output=error.line)
131-
dict_issue_number_to_test_case_index[issue.info["number"]] = len(
132-
junit_suite.test_cases
133-
)
134-
junit_suite.test_cases.append(test_case)
160+
already_reported_issue_numbers.add(issue.info["number"])
135161
break
136162
else:
137163
for issue in known_issues:
138164
match = issue.regex.search(error.line)
139165
if match and issue.info["state"] == "closed":
140-
message = f"Potential regression in logs: <a href=\"{issue.info['html_url']}\">{issue.info['title']} (#{issue.info['number']}, closed)</a><br/>In {linked_file}:{error.line_nr}:"
141-
if issue.info["number"] in dict_issue_number_to_test_case_index:
142-
junit_suite.test_cases[
143-
dict_issue_number_to_test_case_index[issue.info["number"]]
144-
].add_failure_info(message=message, output=error.line)
145-
else:
146-
test_case = junit_xml.TestCase(
147-
f"log error {len(junit_suite.test_cases) + 1} (regression)",
148-
suite_name,
149-
allow_multiple_subelements=True,
150-
)
151-
test_case.add_failure_info(
152-
message=message,
153-
output=error.line,
166+
if issue.apply_to and issue.apply_to not in (
167+
step_key.lower(),
168+
buildkite_label.lower(),
169+
):
170+
continue
171+
172+
if issue.info["number"] not in already_reported_issue_numbers:
173+
unknown_errors.append(
174+
f"Potential regression [{issue.info['title']} (#{issue.info['number']}, closed)]({issue.info['html_url']}) in {linked_file}:{error.line_nr}: \n``{error.line}``"
154175
)
155-
dict_issue_number_to_test_case_index[
156-
issue.info["number"]
157-
] = len(junit_suite.test_cases)
158-
junit_suite.test_cases.append(test_case)
176+
already_reported_issue_numbers.add(issue.info["number"])
159177
break
160178
else:
161-
message = f'Unknown error in logs (<a href="https://github.com/MaterializeInc/materialize/blob/main/doc/developer/ci-regexp.md">ci-regexp guide</a>)<br/>In {linked_file}:{error.line_nr}:'
162-
test_case = junit_xml.TestCase(
163-
f"log error {len(junit_suite.test_cases) + 1} (new)",
164-
suite_name,
165-
allow_multiple_subelements=True,
179+
unknown_errors.append(
180+
f"Unknown error in {linked_file}:{error.line_nr}: \n``{error.line}``"
166181
)
167-
test_case.add_failure_info(message=message, output=error.line)
168-
junit_suite.test_cases.append(test_case)
169182

170-
junit_name = f"{step_key}_logged_errors" if step_key else "logged_errors"
171-
172-
junit_report = ci_util.junit_report_filename(junit_name)
173-
with junit_report.open("w") as f:
174-
junit_xml.to_xml_report_file(f, [junit_suite])
175-
176-
if "BUILDKITE_ANALYTICS_TOKEN_LOGGED_ERRORS" in os.environ:
177-
ci_util.upload_junit_report("logged_errors", ROOT / junit_report)
183+
annotate_errors(
184+
unknown_errors,
185+
"Unknown errors and regressions in logs (see [ci-regexp](https://github.com/MaterializeInc/materialize/blob/main/doc/developer/ci-regexp.md))",
186+
"error",
187+
)
188+
annotate_errors(known_errors, "Known errors in logs, ignoring", "info")
178189

179190

180191
def get_error_logs(log_files: List[str]) -> List[ErrorLog]:
@@ -212,8 +223,15 @@ def get_known_issues_from_github() -> list[KnownIssue]:
212223
known_issues = []
213224
for issue in issues_json["items"]:
214225
matches = CI_RE.findall(issue["body"])
226+
matches_apply_to = CI_APPLY_TO.findall(issue["body"])
215227
for match in matches:
216-
known_issues.append(KnownIssue(match.strip(), issue))
228+
if matches_apply_to:
229+
for match_apply_to in matches_apply_to:
230+
known_issues.append(
231+
KnownIssue(match.strip(), match_apply_to.strip().lower(), issue)
232+
)
233+
else:
234+
known_issues.append(KnownIssue(match.strip(), None, issue))
217235
return known_issues
218236

219237

misc/python/stubs/junit_xml.pyi

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class TestCase:
1717
elapsed_sec: Optional[float] = ...,
1818
allow_multiple_subelements: bool = ...,
1919
): ...
20+
def add_skipped_info(self, message: str, output: Optional[str] = None) -> None: ...
2021
def add_error_info(self, message: str, output: Optional[str] = None) -> None: ...
2122
def add_failure_info(self, message: str, output: Optional[str] = None) -> None: ...
2223

0 commit comments

Comments
 (0)