Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 # | 2 # |
| 3 # Copyright (C) 2013 Google Inc. All rights reserved. | 3 # Copyright (C) 2013 Google Inc. All rights reserved. |
| 4 # | 4 # |
| 5 # Redistribution and use in source and binary forms, with or without | 5 # Redistribution and use in source and binary forms, with or without |
| 6 # modification, are permitted provided that the following conditions are | 6 # modification, are permitted provided that the following conditions are |
| 7 # met: | 7 # met: |
| 8 # | 8 # |
| 9 # * Redistributions of source code must retain the above copyright | 9 # * Redistributions of source code must retain the above copyright |
| 10 # notice, this list of conditions and the following disclaimer. | 10 # notice, this list of conditions and the following disclaimer. |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 30 | 30 |
| 31 """Prints a list of test expectations for tests whose bugs haven't been modified recently.""" | 31 """Prints a list of test expectations for tests whose bugs haven't been modified recently.""" |
| 32 | 32 |
| 33 import csv | |
| 33 import datetime | 34 import datetime |
| 34 import json | 35 import json |
| 35 import optparse | 36 import optparse |
| 37 import StringIO | |
| 36 import sys | 38 import sys |
| 37 import urllib2 | 39 import urllib2 |
| 38 | 40 |
| 39 from webkitpy.common.host import Host | 41 from webkitpy.common.host import Host |
| 40 from webkitpy.layout_tests.models.test_expectations import TestExpectationParser | 42 from webkitpy.layout_tests.models.test_expectations import TestExpectationParser |
| 41 | 43 |
| 42 # FIXME: Make this a direct request to Monorail. | 44 # FIXME: Make this a direct request to Monorail. |
| 43 GOOGLE_CODE_URL = 'https://www.googleapis.com/projecthosting/v2/projects/chromiu m/issues/%s?key=AIzaSyDgCqT1Dt5AZWLHo4QJjyMHaCjhnFacGF0' | 45 GOOGLE_CODE_URL = 'https://www.googleapis.com/projecthosting/v2/projects/chromiu m/issues/%s?key=AIzaSyDgCqT1Dt5AZWLHo4QJjyMHaCjhnFacGF0' |
| 44 CRBUG_PREFIX = 'crbug.com/' | 46 CRBUG_PREFIX = 'crbug.com/' |
| 47 CSV_ROW_HEADERS = ['crbug link', 'test file', 'days since last update', 'owner', 'status'] | |
| 45 | 48 |
| 49 class Bug_Info(): | |
|
qyearsley
2016/08/22 23:21:00
For consistency, this should be `class BugInfo(obj
nainar
2016/08/23 00:21:24
Done.
| |
| 50 def __init__(self, bug_link, filename, days_since_last_update, owner, status ): | |
| 51 self.bug_link = bug_link | |
| 52 self.filename = filename | |
| 53 self.filename = filename | |
|
qyearsley
2016/08/22 23:21:00
Duplicate line
nainar
2016/08/23 00:21:24
Done.
| |
| 54 self.days_since_last_update = days_since_last_update | |
| 55 self.owner = owner | |
| 56 self.status = status | |
| 46 | 57 |
| 47 class StaleTestPrinter(object): | 58 class StaleTestPrinter(object): |
| 48 | 59 |
| 49 def __init__(self, options): | 60 def __init__(self, options): |
| 50 self.days = options.days | 61 self.days = options.days |
| 51 self.csv_filename = options.create_csv | 62 self.csv_filename = options.create_csv |
| 52 self.host = Host() | 63 self.host = Host() |
| 53 self.is_stale_results = {} | 64 self.bug_info = {} |
| 54 | 65 |
| 55 def print_stale_tests(self): | 66 def print_stale_tests(self): |
| 56 port = self.host.port_factory.get() | 67 port = self.host.port_factory.get() |
| 57 expectations = port.expectations_dict() | 68 expectations = port.expectations_dict() |
| 58 parser = TestExpectationParser(port, all_tests=(), is_lint_mode=False) | 69 parser = TestExpectationParser(port, all_tests=(), is_lint_mode=False) |
| 59 expectations_file, expectations_contents = expectations.items()[0] | 70 expectations_file, expectations_contents = expectations.items()[0] |
| 60 expectation_lines = parser.parse(expectations_file, expectations_content s) | 71 expectation_lines = parser.parse(expectations_file, expectations_content s) |
| 61 csv_rows = [] | 72 csv_rows = [] |
| 62 for line in expectation_lines: | 73 for line in expectation_lines: |
| 63 row = self.check_expectations_line(line) | 74 row = self.check_expectations_line(line) |
| 64 if row: | 75 if row: |
| 65 csv_rows.append(row) | 76 csv_rows.append(row) |
| 66 if self.csv_filename: | 77 if self.csv_filename: |
| 67 self.write_csv(csv_rows) | 78 self.write_csv(csv_rows) |
| 68 | 79 |
| 69 def write_csv(self, rows): | 80 def write_csv(self, rows): |
| 70 row_strings = [', '.join(r) for r in rows] | 81 out = StringIO.StringIO() |
| 71 contents = '\n'.join(row_strings) + '\n' | 82 writer = csv.writer(out) |
| 72 self.host.filesystem.write_text_file(self.csv_filename, contents) | 83 writer.writerow(CSV_ROW_HEADERS) |
| 84 for row in rows: | |
| 85 writer.writerow(row) | |
| 86 self.host.filesystem.write_text_file(self.csv_filename, out.getvalue()) | |
| 73 | 87 |
| 74 def check_expectations_line(self, line): | 88 def check_expectations_line(self, line): |
| 75 """Checks the bugs in one test expectations line to see if they're stale . | 89 """Checks the bugs in one test expectations line to see if they're stale . |
| 76 | 90 |
| 77 Args: | 91 Args: |
| 78 line: A TestExpectationsLine instance. | 92 line: A TestExpectationsLine instance. |
| 79 | 93 |
| 80 Returns: | 94 Returns: |
| 81 A CSV row (a list of strings), or None if there are no stale bugs. | 95 A CSV row (a list of strings), or None if there are no stale bugs. |
| 82 """ | 96 """ |
| 83 bug_links, test_name = line.bugs, line.name | 97 bug_links, test_name = line.bugs, line.name |
| 84 try: | 98 try: |
| 85 if bug_links and all(self.is_stale(bug_link) for bug_link in bug_lin ks): | 99 if bug_links: |
| 86 print line.original_string.strip() | 100 # prepopulate bug info |
| 87 return [bug_links[0], test_name] | 101 for bug_link in bug_links: |
| 102 self.populate_bug_info(bug_link, test_name); | |
| 103 # return the stale bug's information | |
|
qyearsley
2016/08/22 23:21:00
Comments should generally start with a capital let
nainar
2016/08/23 00:21:24
Done.
| |
| 104 if all(self.is_stale(bug_link) for bug_link in bug_links): | |
| 105 print line.original_string.strip() | |
| 106 return [bug_links[0], self.bug_info[bug_links[0]].filename, self.bug_info[bug_links[0]].days_since_last_update, self.bug_info[bug_links[0]]. owner, self.bug_info[bug_links[0]].status] | |
|
qyearsley
2016/08/22 23:21:00
This line could be broken up to improve readabilit
nainar
2016/08/23 00:21:24
Done.
| |
| 88 except urllib2.HTTPError as error: | 107 except urllib2.HTTPError as error: |
| 89 if error.code == 404: | 108 if error.code == 404: |
| 90 message = 'got 404, bug does not exist.' | 109 message = 'got 404, bug does not exist.' |
| 91 elif error.code == 403: | 110 elif error.code == 403: |
| 92 message = 'got 403, not accessible. Not able to tell if it\'s st ale.' | 111 message = 'got 403, not accessible. Not able to tell if it\'s st ale.' |
| 93 else: | 112 else: |
| 94 message = str(error) | 113 message = str(error) |
| 95 print >> sys.stderr, 'Error when checking %s: %s' % (','.join(bug_li nks), message) | 114 print >> sys.stderr, 'Error when checking %s: %s' % (','.join(bug_li nks), message) |
| 96 return None | 115 return None |
| 97 | 116 |
| 98 def is_stale(self, bug_link): | 117 def populate_bug_info(self, bug_link, test_name): |
| 99 if bug_link in self.is_stale_results: | 118 if bug_link in self.bug_info: |
| 100 return self.is_stale_results[bug_link] | 119 return; |
|
qyearsley
2016/08/22 23:21:00
Unnecessary semicolon
nainar
2016/08/23 00:21:24
Done.
| |
| 101 # In case there's an error in the request, don't make the same request a gain. | 120 # In case there's an error in the request, don't make the same request a gain. |
| 102 self.is_stale_results[bug_link] = False | |
| 103 bug_number = bug_link.strip(CRBUG_PREFIX) | 121 bug_number = bug_link.strip(CRBUG_PREFIX) |
| 104 url = GOOGLE_CODE_URL % bug_number | 122 url = GOOGLE_CODE_URL % bug_number |
| 105 response = urllib2.urlopen(url) | 123 response = urllib2.urlopen(url) |
| 106 parsed = json.loads(response.read()) | 124 parsed = json.loads(response.read()) |
| 107 last_updated = parsed['updated'] | 125 parsed_time = datetime.datetime.strptime(parsed['updated'].split(".")[0] + "UTC", "%Y-%m-%dT%H:%M:%S%Z") |
| 108 parsed_time = datetime.datetime.strptime(last_updated.split(".")[0] + "U TC", "%Y-%m-%dT%H:%M:%S%Z") | |
| 109 time_delta = datetime.datetime.now() - parsed_time | 126 time_delta = datetime.datetime.now() - parsed_time |
| 110 self.is_stale_results[bug_link] = time_delta.days > self.days | 127 owner = 'none' |
| 111 return self.is_stale_results[bug_link] | 128 if 'owner' in parsed.keys(): |
| 129 owner = parsed['owner']['name'] | |
| 130 self.bug_info[bug_link] = Bug_Info(bug_link, test_name, time_delta.days, owner, parsed['state']) | |
| 131 | |
| 132 def is_stale(self, bug_link): | |
| 133 return self.bug_info[bug_link].days_since_last_update > self.days | |
| 112 | 134 |
| 113 | 135 |
| 114 def main(argv): | 136 def main(argv): |
| 115 option_parser = optparse.OptionParser() | 137 option_parser = optparse.OptionParser() |
| 116 option_parser.add_option( | 138 option_parser.add_option( |
| 117 '--days', type='int', default=90, | 139 '--days', type='int', default=90, |
| 118 help='Number of days to consider a bug stale.') | 140 help='Number of days to consider a bug stale.') |
| 119 option_parser.add_option( | 141 option_parser.add_option( |
| 120 '--create-csv', type='string', default='', | 142 '--create-csv', type='string', default='', |
| 121 help='Filename of CSV file to write stale entries to. No file will be wr itten if no name specified.') | 143 help='Filename of CSV file to write stale entries to. No file will be wr itten if no name specified.') |
| 122 options, _ = option_parser.parse_args(argv) | 144 options, _ = option_parser.parse_args(argv) |
| 123 printer = StaleTestPrinter(options) | 145 printer = StaleTestPrinter(options) |
| 124 printer.print_stale_tests() | 146 printer.print_stale_tests() |
| 125 return 0 | 147 return 0 |
| 126 | 148 |
| 127 | 149 |
| 128 if __name__ == '__main__': | 150 if __name__ == '__main__': |
| 129 sys.exit(main(sys.argv[1:])) | 151 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |