1 # This Source Code Form is subject to the terms of the Mozilla Public
2 # License, v. 2.0. If a copy of the MPL was not distributed with this
3 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
13 from typing
import Callable
, Iterable
, List
, Mapping
, Optional
, Set
, Tuple
15 repos
= ["autoland", "mozilla-central", "try", "mozilla-central", "mozilla-beta", "wpt"]
17 default_fetch_task_filters
= ["-web-platform-tests-|-spidermonkey-"]
18 default_interop_task_filters
= {
24 "!-nofis|-headless|-asan|-tsan|-ccov",
29 def get_parser_fetch_logs() -> argparse
.Namespace
:
30 parser
= argparse
.ArgumentParser()
32 "--log-dir", action
="store", help="Directory into which to download logs"
38 help="Regex filter applied to task names. Filters starting ! must not match. Filters starting ^ (after any !) match the entire task name, otherwise any substring can match. Multiple filters must all match",
43 help="Only download logs if the task is complete",
48 help="repo:commit e.g. mozilla-central:fae24810aef1 for the runs to include",
53 def get_parser_interop_score() -> argparse
.Namespace
:
54 parser
= get_parser_fetch_logs()
60 help="Interop year to score against",
65 dest
="category_filters",
66 help="Regex filter applied to category names. Filters starting ! must not match. Filters starting ^ (after any !) match the entire task name, otherwise any substring can match. Multiple filters must all match",
69 "--expected-failures",
70 help="Path to a file containing a list of tests which are not expected to pass",
76 runs
: Iterable
[Tuple
[str, str]],
77 results_by_category
: Mapping
[str, List
[int]],
78 expected_failures_by_category
: Optional
[Mapping
[str, List
[Tuple
[int, int]]]],
81 include_expected_failures
= expected_failures_by_category
is not None
83 writer
= csv
.writer(sys
.stdout
, delimiter
="\t")
85 headers
= ["Category"]
86 for repo
, commit
in runs
:
87 prefix
= f
"{repo}:{commit}"
88 headers
.append(f
"{prefix}-score")
89 if include_expected_failures
:
90 headers
.append(f
"{prefix}-expected-failures")
91 headers
.append(f
"{prefix}-adjusted-score")
93 writer
.writerow(headers
)
95 totals
= {"score": [0.0] * len(runs
)}
96 if include_expected_failures
:
97 totals
["expected_failures"] = [0.0] * len(runs
)
98 totals
["adjusted_score"] = [0.0] * len(runs
)
100 for category
, category_results
in results_by_category
.items():
102 category_row
.append(category
)
103 for category_index
, result
in enumerate(category_results
):
104 for run_index
, run_score
in enumerate(category_results
):
105 category_row
.append(f
"{run_score / 10:.1f}")
106 totals
["score"][run_index
] += run_score
107 if include_expected_failures
:
108 expected_failures
, adjusted_score
= expected_failures_by_category
[
111 category_row
.append(f
"{expected_failures / 10:.1f}")
112 category_row
.append(f
"{adjusted_score / 10:.1f}")
113 totals
["expected_failures"][run_index
] += expected_failures
114 totals
["adjusted_score"][run_index
] += adjusted_score
115 writer
.writerow(category_row
)
119 def get_total(score
, floor
=True):
120 total
= float(score
) / (len(results_by_category
))
122 total
= math
.floor(total
)
126 totals_row
= ["Total"]
127 for i
in range(len(runs
)):
128 totals_row
.append(f
"{get_total(totals['score'][i]):.1f}")
129 if include_expected_failures
:
131 f
"{get_total(totals['expected_failures'][i], floor=False):.1f}"
133 totals_row
.append(f
"{get_total(totals['adjusted_score'][i]):.1f}")
134 writer
.writerow(totals_row
)
138 repo
: str, commit
: str, task_filters
: List
[str], log_dir
: str, check_complete
: bool
142 return tcfetch
.download_artifacts(
145 task_filters
=task_filters
,
146 check_complete
=check_complete
,
151 def get_runs(commits
: List
[str]) -> List
[Tuple
[str, str]]:
155 raise ValueError(f
"Expected commits of the form repo:commit, got {item}")
156 repo
, commit
= item
.split(":", 1)
157 if repo
not in repos
:
158 raise ValueError(f
"Unsupported repo {repo}")
159 runs
.append((repo
, commit
))
163 def get_category_filter(
164 category_filters
: Optional
[List
[str]],
165 ) -> Optional
[Callable
[[str], bool]]:
166 if category_filters
is None:
170 for item
in category_filters
:
173 invert
= item
[0] == "!"
177 regex
= re
.compile(item
)
179 regex
= re
.compile(f
"^(.*)(?:{item})")
180 filters
.append((regex
, invert
))
182 def match_filters(category
):
183 for regex
, invert
in filters
:
184 matches
= regex
.match(category
) is not None
186 matches
= not matches
196 task_filters
: List
[str],
197 log_dir
: Optional
[str],
198 check_complete
: bool,
201 runs
= get_runs(commits
)
204 task_filters
= default_fetch_task_filters
207 log_dir
= os
.path
.abspath(os
.curdir
)
209 for repo
, commit
in runs
:
210 get_wptreports(repo
, commit
, task_filters
, log_dir
, check_complete
)
213 def get_expected_failures(path
: str) -> Mapping
[str, Set
[Optional
[str]]]:
214 expected_failures
= {}
215 with
open(path
) as f
:
216 for i
, entry
in enumerate(csv
.reader(f
)):
217 entry
= [item
.strip() for item
in entry
]
218 if not any(item
for item
in entry
) or entry
[0][0] == "#":
222 f
"{path}:{i+1} expected at most two columns, got {len(entry)}"
224 if entry
[0][0] != "/":
226 f
'{path}:{i+1} "{entry[0]}" is not a valid test id (must start with "/")'
229 if test_id
not in expected_failures
:
230 expected_failures
[test_id
] = set()
232 subtest_id
= entry
[1]
235 f
"Warning: {path}:{i+1} got empty string subtest id, remove the trailing comma to make this apply to the full test"
237 expected_failures
[test_id
].add(subtest_id
)
239 expected_failures
[test_id
].add(None)
240 return expected_failures
245 task_filters
: List
[str],
246 log_dir
: Optional
[str],
248 check_complete
: bool,
249 category_filters
: Optional
[List
[str]],
250 expected_failures
: Optional
[str],
253 from wpt_interop
import score
255 runs
= get_runs(commits
)
259 temp_dir
= tempfile
.mkdtemp()
263 if expected_failures
is not None:
264 expected_failures_data
= get_expected_failures(expected_failures
)
266 expected_failures_data
= None
269 for repo
, commit
in runs
:
271 if repo
in default_interop_task_filters
:
272 filters
= default_interop_task_filters
[repo
]
274 filters
= default_interop_task_filters
[None]
276 filters
= task_filters
278 log_paths
= get_wptreports(repo
, commit
, filters
, log_dir
, check_complete
)
280 print(f
"Failed to get any logs for {repo}:{commit}", file=sys
.stderr
)
282 run_logs
.append(log_paths
)
285 print("No logs to process", file=sys
.stderr
)
287 include_total
= category_filters
is None
290 get_category_filter(category_filters
) if category_filters
else None
293 scores
, expected_failure_scores
= score
.score_wptreports(
296 category_filter
=category_filter
,
297 expected_failures
=expected_failures_data
,
299 print_scores(runs
, scores
, expected_failure_scores
, include_total
)
301 if temp_dir
is not None:
302 shutil
.rmtree(temp_dir
, True)