| #!/usr/bin/env python3 | |
| # Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file | |
| # for details. All rights reserved. Use of this source code is governed by a | |
| # BSD-style license that can be found in the LICENSE file. | |
| # | |
| """Webscraper for make_a_fuzz nightly cluster run results. | |
| Given the uri of a make_a_fuzz run, this script will first | |
| extract the links pointing to each of the individual shards | |
| and then parse the output generated by each shard to | |
| find divergences reported by the dartfuzz_test.dart program, | |
| concatenate all output, or summarize all test results. | |
| Example: | |
| collect_data.py --type sum | |
| https://ci.chromium.org/p/dart/builders/ci.sandbox/fuzz-linux/303 | |
| """ | |
| # This script may require a one time install of BeautifulSoup: | |
| # sudo apt-get install python3-bs4 | |
| import argparse | |
| import re | |
| import sys | |
| from bs4 import BeautifulSoup | |
| import requests | |
| # Matches shard raw stdout to extract divergence reports. | |
| P_DIV = re.compile("(Isolate.+? !DIVERGENCE! (\n|.)+?)Isolate ", re.MULTILINE) | |
| # Matches shard raw stdout to extract report summaries. | |
| P_SUM = re.compile( | |
| r"^Tests: (\d+) Success: (\d+) " | |
| r"\(Rerun: (\d+)\) Skipped: (\d+) " | |
| r"Timeout: (\d+) Divergences: (\d+)", re.MULTILINE) | |
| # Matches uri to extract shard number. | |
| P_SHARD = re.compile(r".*make_a_fuzz_shard_(\d+)") | |
| def get_shard_links(uri): | |
| links = [] | |
| resp = requests.get(uri) | |
| soup = BeautifulSoup(resp.text, "html.parser") | |
| for a in soup.findAll("a"): | |
| if "stdout" in a.text: | |
| href = a["href"] | |
| if ("make_a_fuzz_shard" in href and "__trigger__" not in href): | |
| links.append(href + "?format=raw") | |
| return links | |
| def print_reencoded(text): | |
| # Re-encoding avoids breaking some terminals. | |
| print(text.encode("ascii", errors="ignore").decode("unicode-escape")) | |
| def print_output_all(text): | |
| print_reencoded(text) | |
| def print_output_div(shard, text, keywords): | |
| sys.stderr.write("Shard: " + shard + " \r") | |
| m = P_DIV.findall(text) | |
| if m: | |
| for x in m: | |
| keep = True | |
| for word in keywords: | |
| if word in x[0]: | |
| keep = False | |
| break | |
| if keep: | |
| print_reencoded(x[0]) | |
| def get_output_sum(shard, text, should_print, s=[0, 0, 0, 0, 0, 0], divs=[]): | |
| m = P_SUM.findall(text) | |
| if not m: | |
| sys.stderr.write("Failed to parse shard %s stdout for summary" % shard) | |
| return | |
| for test in m: | |
| if int(test[-1]) == 1: | |
| divs.append(shard) | |
| for i in range(len(s)): | |
| s[i] += int(test[i]) | |
| if should_print: | |
| print( | |
| "Tests: %d Success: %d (Rerun: %d) Skipped: %d Timeout: %d " | |
| "Divergences: %d (failing shards: %s) \r" % | |
| tuple(s + [", ".join(divs) if divs else "none"]), | |
| end="") | |
| return s | |
| def get_stats(uri, output_type, keywords, output_csv): | |
| resp = requests.get(uri) | |
| if output_type == "all": | |
| print_output_all(resp.text) | |
| elif output_type == "div": | |
| shard = P_SHARD.findall(uri)[0] | |
| print_output_div(shard, resp.text, keywords) | |
| elif output_type == "sum": | |
| shard = P_SHARD.findall(uri)[0] | |
| should_print = not output_csv | |
| return get_output_sum(shard, resp.text, should_print) | |
| return None | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument( | |
| "--type", | |
| choices=("div", "sum", "all"), | |
| required=True, | |
| help= | |
| "Select output type (div: divergence report, sum: summary, all: complete stdout)" | |
| ) | |
| parser.add_argument( | |
| "--filter", | |
| nargs="+", | |
| default=[], | |
| help="Do not include divergences containing these keywords.") | |
| parser.add_argument( | |
| "--output-csv", | |
| dest="output_csv", | |
| action="store_true", | |
| default=False, | |
| help= | |
| "Print output in CSV format to stdout. Only supported for --type=sum") | |
| parser.add_argument( | |
| "uri", | |
| type=str, | |
| help= | |
| "Uri of one make_a_fuzz run from https://ci.chromium.org/p/dart/builders/ci.sandbox/fuzz-linux." | |
| ) | |
| args = parser.parse_args() | |
| if args.type != 'sum' and args.output_csv: | |
| print('Error: --output-csv can only be provided for --type=sum') | |
| return | |
| shard_links = get_shard_links(args.uri) | |
| if len(shard_links) == 0: | |
| print("Invalid run") | |
| sys.exit(-1) | |
| return | |
| for link in shard_links: | |
| stats = get_stats(link, args.type, args.filter, args.output_csv) | |
| if args.output_csv: | |
| print("%d,%d,%d,%d,%d,%d" % tuple(stats)) | |
| else: | |
| print("") | |
| sys.exit(0) | |
| if __name__ == "__main__": | |
| main() |