Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Enables directory-specific presubmit checks to run at upload and/or commit. | 6 """Enables directory-specific presubmit checks to run at upload and/or commit. |
| 7 """ | 7 """ |
| 8 | 8 |
| 9 __version__ = '1.8.0' | 9 __version__ = '1.8.0' |
| 10 | 10 |
| 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow | 11 # TODO(joi) Add caching where appropriate/needed. The API is designed to allow |
| 12 # caching (between all different invocations of presubmit scripts for a given | 12 # caching (between all different invocations of presubmit scripts for a given |
| 13 # change). We should add it as our presubmit scripts start feeling slow. | 13 # change). We should add it as our presubmit scripts start feeling slow. |
| 14 | 14 |
| 15 import cpplint | 15 import cpplint |
| 16 import cPickle # Exposed through the API. | 16 import cPickle # Exposed through the API. |
| 17 import cStringIO # Exposed through the API. | 17 import cStringIO # Exposed through the API. |
| 18 import contextlib | 18 import contextlib |
| 19 import fnmatch | 19 import fnmatch |
| 20 import glob | 20 import glob |
| 21 import inspect | 21 import inspect |
| 22 import itertools | |
| 22 import json # Exposed through the API. | 23 import json # Exposed through the API. |
| 23 import logging | 24 import logging |
| 24 import marshal # Exposed through the API. | 25 import marshal # Exposed through the API. |
| 25 import multiprocessing | 26 import multiprocessing |
| 26 import optparse | 27 import optparse |
| 27 import os # Somewhat exposed through the API. | 28 import os # Somewhat exposed through the API. |
| 28 import pickle # Exposed through the API. | 29 import pickle # Exposed through the API. |
| 29 import random | 30 import random |
| 30 import re # Exposed through the API. | 31 import re # Exposed through the API. |
| 31 import sys # Parts exposed through API. | 32 import sys # Parts exposed through API. |
| (...skipping 968 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1000 results.append(p) | 1001 results.append(p) |
| 1001 | 1002 |
| 1002 logging.debug('Presubmit files: %s' % ','.join(results)) | 1003 logging.debug('Presubmit files: %s' % ','.join(results)) |
| 1003 return results | 1004 return results |
| 1004 | 1005 |
| 1005 | 1006 |
| 1006 class GetTrySlavesExecuter(object): | 1007 class GetTrySlavesExecuter(object): |
| 1007 @staticmethod | 1008 @staticmethod |
| 1008 def ExecPresubmitScript(script_text, presubmit_path, project, change): | 1009 def ExecPresubmitScript(script_text, presubmit_path, project, change): |
| 1009 """Executes GetPreferredTrySlaves() from a single presubmit script. | 1010 """Executes GetPreferredTrySlaves() from a single presubmit script. |
| 1011 | |
| 1012 This will soon be deprecated and replaced by GetPreferredTryMasters(). | |
| 1010 | 1013 |
| 1011 Args: | 1014 Args: |
| 1012 script_text: The text of the presubmit script. | 1015 script_text: The text of the presubmit script. |
| 1013 presubmit_path: Project script to run. | 1016 presubmit_path: Project script to run. |
| 1014 project: Project name to pass to presubmit script for bot selection. | 1017 project: Project name to pass to presubmit script for bot selection. |
| 1015 | 1018 |
| 1016 Return: | 1019 Return: |
| 1017 A list of try slaves. | 1020 A list of try slaves. |
| 1018 """ | 1021 """ |
| 1019 context = {} | 1022 context = {} |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1067 ) | 1070 ) |
| 1068 | 1071 |
| 1069 # Ensure it's either all old-style or all new-style. | 1072 # Ensure it's either all old-style or all new-style. |
| 1070 if not valid_oldstyle(result) and not valid_newstyle(result): | 1073 if not valid_oldstyle(result) and not valid_newstyle(result): |
| 1071 raise PresubmitFailure( | 1074 raise PresubmitFailure( |
| 1072 'PRESUBMIT.py returned invalid trybot specification!') | 1075 'PRESUBMIT.py returned invalid trybot specification!') |
| 1073 | 1076 |
| 1074 return result | 1077 return result |
| 1075 | 1078 |
| 1076 | 1079 |
| 1080 class GetTryMastersExecuter(object): | |
| 1081 @staticmethod | |
| 1082 def ExecPresubmitScript(script_text, presubmit_path, project, change): | |
| 1083 """Executes GetPreferredTryMasters() from a single presubmit script. | |
| 1084 | |
| 1085 Args: | |
| 1086 script_text: The text of the presubmit script. | |
| 1087 presubmit_path: Project script to run. | |
| 1088 project: Project name to pass to presubmit script for bot selection. | |
| 1089 | |
| 1090 Return: | |
| 1091 A map of try masters to map of builders to set of tests. | |
| 1092 """ | |
| 1093 context = {} | |
| 1094 try: | |
| 1095 exec script_text in context | |
|
ghost stip (do not use)
2014/03/01 00:56:16
if possible, try to refactor this out to share cod
Michael Achenbach
2014/03/01 01:30:40
Follow up CL.
| |
| 1096 except Exception, e: | |
| 1097 raise PresubmitFailure('"%s" had an exception.\n%s' | |
| 1098 % (presubmit_path, e)) | |
| 1099 | |
| 1100 function_name = 'GetPreferredTryMasters' | |
| 1101 if function_name not in context: | |
| 1102 return {} | |
| 1103 get_preferred_try_masters = context[function_name] | |
| 1104 if not len(inspect.getargspec(get_preferred_try_masters)[0]) == 2: | |
| 1105 raise PresubmitFailure( | |
| 1106 'Expected function "GetPreferredTryMasters" to take two arguments.') | |
| 1107 result = get_preferred_try_masters(project, change) | |
| 1108 | |
| 1109 def CheckType(item, t): | |
|
ghost stip (do not use)
2014/03/01 00:56:16
I'm ok removing lines 1109 to 1141, since any code
Michael Achenbach
2014/03/01 01:30:40
Done.
| |
| 1110 if not isinstance(item, t): | |
| 1111 raise PresubmitFailure( | |
| 1112 'Expected a %s, got a %s instead: %s' % | |
| 1113 (str(t), type(item), str(item))) | |
| 1114 | |
| 1115 def CheckString(item): | |
| 1116 if not item: | |
| 1117 raise PresubmitFailure( | |
| 1118 'PRESUBMIT.py returned empty trymaster/builder string.') | |
| 1119 CheckType(item, basestring) | |
| 1120 if item != item.strip(): | |
| 1121 raise PresubmitFailure( | |
| 1122 'Try master/builder names cannot start/end with whitespace') | |
| 1123 if ',' in item: | |
| 1124 raise PresubmitFailure( | |
| 1125 'Do not use \',\' separated master, builder or test names: %s' | |
| 1126 % item) | |
| 1127 | |
| 1128 CheckType(result, dict) | |
| 1129 for (master, builders) in result.iteritems(): | |
| 1130 CheckString(master) | |
| 1131 CheckType(builders, dict) | |
| 1132 for (builder, tests) in builders.iteritems(): | |
| 1133 CheckString(builder) | |
| 1134 CheckType(tests, set) | |
| 1135 if not tests: | |
| 1136 raise PresubmitFailure( | |
| 1137 'PRESUBMIT.py returned empty tests for builder %s. ' | |
| 1138 'Maybe specify "defaulttests"?' % builder) | |
| 1139 return result | |
| 1140 | |
| 1141 | |
| 1077 def DoGetTrySlaves(change, | 1142 def DoGetTrySlaves(change, |
| 1078 changed_files, | 1143 changed_files, |
| 1079 repository_root, | 1144 repository_root, |
| 1080 default_presubmit, | 1145 default_presubmit, |
| 1081 project, | 1146 project, |
| 1082 verbose, | 1147 verbose, |
| 1083 output_stream): | 1148 output_stream): |
| 1084 """Get the list of try servers from the presubmit scripts. | 1149 """Get the list of try servers from the presubmit scripts (deprecated). |
| 1085 | 1150 |
| 1086 Args: | 1151 Args: |
| 1087 changed_files: List of modified files. | 1152 changed_files: List of modified files. |
| 1088 repository_root: The repository root. | 1153 repository_root: The repository root. |
| 1089 default_presubmit: A default presubmit script to execute in any case. | 1154 default_presubmit: A default presubmit script to execute in any case. |
| 1090 project: Optional name of a project used in selecting trybots. | 1155 project: Optional name of a project used in selecting trybots. |
| 1091 verbose: Prints debug info. | 1156 verbose: Prints debug info. |
| 1092 output_stream: A stream to write debug output to. | 1157 output_stream: A stream to write debug output to. |
| 1093 | 1158 |
| 1094 Return: | 1159 Return: |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 1125 slaves = list(slave_dict.items()) | 1190 slaves = list(slave_dict.items()) |
| 1126 | 1191 |
| 1127 slaves.extend(set(old_style)) | 1192 slaves.extend(set(old_style)) |
| 1128 | 1193 |
| 1129 if slaves and verbose: | 1194 if slaves and verbose: |
| 1130 output_stream.write(', '.join((str(x) for x in slaves))) | 1195 output_stream.write(', '.join((str(x) for x in slaves))) |
| 1131 output_stream.write('\n') | 1196 output_stream.write('\n') |
| 1132 return slaves | 1197 return slaves |
| 1133 | 1198 |
| 1134 | 1199 |
| 1200 def _MergeMasters(masters1, masters2): | |
| 1201 """Merges two master maps. Merges also the tests of each builder. | |
| 1202 """ | |
|
ghost stip (do not use)
2014/03/01 00:56:16
nit: keep """ on same line
| |
| 1203 result = {} | |
| 1204 for (master, builders) in itertools.chain(masters1.iteritems(), | |
| 1205 masters2.iteritems()): | |
| 1206 new_builders = result.setdefault(master, {}) | |
| 1207 for (builder, tests) in builders.iteritems(): | |
| 1208 new_builders.setdefault(builder, set([])).update(tests) | |
| 1209 return result | |
| 1210 | |
| 1211 | |
| 1212 def DoGetTryMasters(change, | |
| 1213 changed_files, | |
| 1214 repository_root, | |
| 1215 default_presubmit, | |
| 1216 project, | |
| 1217 verbose, | |
| 1218 output_stream): | |
| 1219 """Get the list of try masters from the presubmit scripts. | |
| 1220 | |
| 1221 Args: | |
| 1222 changed_files: List of modified files. | |
| 1223 repository_root: The repository root. | |
| 1224 default_presubmit: A default presubmit script to execute in any case. | |
| 1225 project: Optional name of a project used in selecting trybots. | |
| 1226 verbose: Prints debug info. | |
| 1227 output_stream: A stream to write debug output to. | |
| 1228 | |
| 1229 Return: | |
| 1230 Map of try masters to map of builders to set of tests. | |
| 1231 """ | |
| 1232 presubmit_files = ListRelevantPresubmitFiles(changed_files, repository_root) | |
| 1233 if not presubmit_files and verbose: | |
| 1234 output_stream.write("Warning, no presubmit.py found.\n") | |
|
ghost stip (do not use)
2014/03/01 00:56:16
nit: PRESUBMIT.py vs presubmit.py
Michael Achenbach
2014/03/01 01:30:40
Done.
| |
| 1235 results = {} | |
| 1236 executer = GetTryMastersExecuter() | |
| 1237 | |
| 1238 if default_presubmit: | |
| 1239 if verbose: | |
| 1240 output_stream.write("Running default presubmit script.\n") | |
| 1241 fake_path = os.path.join(repository_root, 'PRESUBMIT.py') | |
| 1242 results = _MergeMasters(results, executer.ExecPresubmitScript( | |
| 1243 default_presubmit, fake_path, project, change)) | |
| 1244 for filename in presubmit_files: | |
| 1245 filename = os.path.abspath(filename) | |
| 1246 if verbose: | |
| 1247 output_stream.write("Running %s\n" % filename) | |
| 1248 # Accept CRLF presubmit script. | |
| 1249 presubmit_script = gclient_utils.FileRead(filename, 'rU') | |
| 1250 results = _MergeMasters(results, executer.ExecPresubmitScript( | |
| 1251 presubmit_script, filename, project, change)) | |
| 1252 | |
| 1253 # Make sets to lists again for later JSON serialization. | |
| 1254 for (_, builders) in results.iteritems(): | |
|
ghost stip (do not use)
2014/03/01 00:56:16
for builders in results.itervalues()
Michael Achenbach
2014/03/01 01:30:40
Done.
| |
| 1255 for builder in builders: | |
| 1256 builders[builder] = list(builders[builder]) | |
| 1257 | |
| 1258 if results and verbose: | |
| 1259 output_stream.write('%s\n' % str(results)) | |
| 1260 return results | |
| 1261 | |
| 1262 | |
| 1135 class PresubmitExecuter(object): | 1263 class PresubmitExecuter(object): |
| 1136 def __init__(self, change, committing, rietveld_obj, verbose): | 1264 def __init__(self, change, committing, rietveld_obj, verbose): |
| 1137 """ | 1265 """ |
| 1138 Args: | 1266 Args: |
| 1139 change: The Change object. | 1267 change: The Change object. |
| 1140 committing: True if 'gcl commit' is running, False if 'gcl upload' is. | 1268 committing: True if 'gcl commit' is running, False if 'gcl upload' is. |
| 1141 rietveld_obj: rietveld.Rietveld client object. | 1269 rietveld_obj: rietveld.Rietveld client object. |
| 1142 """ | 1270 """ |
| 1143 self.change = change | 1271 self.change = change |
| 1144 self.committing = committing | 1272 self.committing = committing |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1508 except PresubmitFailure, e: | 1636 except PresubmitFailure, e: |
| 1509 print >> sys.stderr, e | 1637 print >> sys.stderr, e |
| 1510 print >> sys.stderr, 'Maybe your depot_tools is out of date?' | 1638 print >> sys.stderr, 'Maybe your depot_tools is out of date?' |
| 1511 print >> sys.stderr, 'If all fails, contact maruel@' | 1639 print >> sys.stderr, 'If all fails, contact maruel@' |
| 1512 return 2 | 1640 return 2 |
| 1513 | 1641 |
| 1514 | 1642 |
| 1515 if __name__ == '__main__': | 1643 if __name__ == '__main__': |
| 1516 fix_encoding.fix_encoding() | 1644 fix_encoding.fix_encoding() |
| 1517 sys.exit(Main(None)) | 1645 sys.exit(Main(None)) |
| OLD | NEW |