Tiem Song | 2b49aaf | 2022-08-23 15:41:00 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # Future improvements: |
| 4 | # - link bugs and code references |
| 5 | # - handle multiple bug references per line (currently only fetches one) |
| 6 | # - fetch more than the first ~900 entries from Buganizer |
| 7 | # - check if CLI tool is available + authenticated |
| 8 | |
| 9 | import pathlib, re, subprocess |
| 10 | |
| 11 | outfile = "closed_bugs.csv" |
| 12 | |
| 13 | print("Searching code for bug references") |
| 14 | androidx_root = pathlib.Path(__file__).parent.parent.resolve() |
| 15 | grep_cmd = subprocess.run( |
| 16 | ["egrep", |
| 17 | |
| 18 | # -I excludes binary files |
| 19 | # -i case insensitive search |
| 20 | # -n include line numbers |
| 21 | # -r recursive |
| 22 | "-Iinr", |
| 23 | |
| 24 | # regex for buganizer format ("b/123456789") |
| 25 | "b\/[[:digit:]]{8,9}", |
| 26 | |
| 27 | # Files and directories to include and exclude |
| 28 | "--exclude-dir=.idea", |
| 29 | "--include=*.gradle", |
| 30 | "--include=*.java", |
| 31 | "--include=*.kt", |
| 32 | "--include=*.xml", |
| 33 | |
| 34 | # Search all of the AndroidX repo checkout |
| 35 | f"{androidx_root}" |
| 36 | ], |
| 37 | capture_output=True, |
| 38 | text=True |
| 39 | ) |
| 40 | raw_output_lines = grep_cmd.stdout.split("\n") |
| 41 | |
| 42 | print("Cleaning up search results") |
| 43 | bug_dict = {} # mapping of bug id to list of filename + line number |
| 44 | for line in raw_output_lines: |
| 45 | regex_result = re.search('b\/[0-9]{8,9}', line) |
| 46 | if regex_result is not None: |
| 47 | bug_id = regex_result.group(0).removeprefix("b/") |
| 48 | file = line.split(":")[0].removeprefix(str(androidx_root)) |
| 49 | linenum = line.split(":")[1] |
| 50 | |
| 51 | if bug_id in bug_dict: |
| 52 | matching_files = bug_dict[bug_id] |
| 53 | else: |
| 54 | matching_files = set() |
| 55 | matching_files.add(f"{file}:{linenum}") |
| 56 | bug_dict[bug_id] = matching_files |
| 57 | print(f"Found {len(bug_dict)} bugs") |
| 58 | |
| 59 | # Create bug id query string. |
| 60 | # The CLI tool fails if there are too many bugs (>900?); only use the first 900. |
| 61 | bug_ids = list(bug_dict.keys()) |
| 62 | bug_ids.sort() |
| 63 | joined_ids = "|".join(bug_ids[0:899]) |
| 64 | |
| 65 | # Query buganizer to determine which of the given bugs are closed. |
| 66 | # Store the issue, reporter, and assignee of the matching [closed] bugs. |
| 67 | print("Querying Buganizer to find how many of these bugs are resolved") |
| 68 | bugged_cmd = subprocess.run( |
| 69 | ["bugged", "search", f"id:({joined_ids})", "status:closed", "--columns=issue,reporter,assignee"], |
| 70 | capture_output=True, |
| 71 | text=True # capture output as String instead of byte sequence |
| 72 | ) |
| 73 | closed_bug_list = bugged_cmd.stdout.split("\n") |
| 74 | |
| 75 | # Remove header and trailing rows of Buganizer query result |
| 76 | closed_bug_list.pop(0) |
| 77 | closed_bug_list.pop() |
| 78 | print(f"{len(closed_bug_list)} have been resolved") |
| 79 | |
| 80 | # Combine buganizer results with file search results and write to CSV |
| 81 | csv_str = "bug_id,reporter,assignee,files\n" |
| 82 | for line in closed_bug_list: |
| 83 | elements = re.split(" +", line) |
| 84 | bug_id = elements[0] |
| 85 | reporter = elements[1] |
| 86 | assignee = elements[2] |
| 87 | matching_files = bug_dict[bug_id] |
| 88 | line_str = f"b/{bug_id},{reporter},{assignee}," |
| 89 | |
| 90 | # The list of matching file(s) are enclosed in double quotes to preserve \n in the csv |
| 91 | line_str += ("\"" + "\n".join(matching_files) + "\"") |
| 92 | |
| 93 | csv_str += line_str + "\n" |
| 94 | |
| 95 | print(csv_str, file=open(outfile, 'w')) |
| 96 | print(f"Wrote results to {outfile}") |