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 |