Commit 68e0419d authored by stip@chromium.org's avatar stip@chromium.org

Rework bot and test parsing to allow receipt of (bot, set(test)) specifications.

This is needed for a further CL to unify TS and CQ using PRESUBMIT.py.

BUG=278554

Review URL: https://codereview.chromium.org/54373011

git-svn-id: svn://svn.chromium.org/chrome/trunk/tools/depot_tools@232813 0039d316-1c4b-4281-b951-d872f2087c98
parent 22879a78
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
"""Enables directory-specific presubmit checks to run at upload and/or commit. """Enables directory-specific presubmit checks to run at upload and/or commit.
""" """
__version__ = '1.6.2' __version__ = '1.7.0'
# TODO(joi) Add caching where appropriate/needed. The API is designed to allow # TODO(joi) Add caching where appropriate/needed. The API is designed to allow
# caching (between all different invocations of presubmit scripts for a given # caching (between all different invocations of presubmit scripts for a given
...@@ -1031,14 +1031,22 @@ class GetTrySlavesExecuter(object): ...@@ -1031,14 +1031,22 @@ class GetTrySlavesExecuter(object):
'Presubmit functions must return a list, got a %s instead: %s' % 'Presubmit functions must return a list, got a %s instead: %s' %
(type(result), str(result))) (type(result), str(result)))
for item in result: for item in result:
if not isinstance(item, basestring): if isinstance(item, basestring):
raise PresubmitFailure('All try slaves names must be strings.') # Old-style ['bot'] format.
if item != item.strip(): botname = item
elif isinstance(item, tuple):
# New-style [('bot', set(['tests']))] format.
botname = item[0]
else:
raise PresubmitFailure('PRESUBMIT.py returned invalid tryslave/test'
' format.')
if botname != botname.strip():
raise PresubmitFailure( raise PresubmitFailure(
'Try slave names cannot start/end with whitespace') 'Try slave names cannot start/end with whitespace')
if ',' in item: if ',' in botname:
raise PresubmitFailure( raise PresubmitFailure(
'Do not use \',\' separated builder or test names: %s' % item) 'Do not use \',\' separated builder or test names: %s' % botname)
else: else:
result = [] result = []
return result return result
...@@ -1084,7 +1092,18 @@ def DoGetTrySlaves(change, ...@@ -1084,7 +1092,18 @@ def DoGetTrySlaves(change,
results += executer.ExecPresubmitScript( results += executer.ExecPresubmitScript(
presubmit_script, filename, project, change) presubmit_script, filename, project, change)
slaves = list(set(results)) if all(isinstance(i, tuple) for i in results):
# New-style [('bot', set(['tests']))] format.
slave_dict = {}
for result in results:
slave_dict.setdefault(result[0], set()).update(result[1])
slaves = list(slave_dict.iteritems())
elif all(isinstance(i, basestring) for i in results):
# Old-style ['bot'] format.
slaves = list(set(results))
else:
raise ValueError('PRESUBMIT.py returned invalid trybot specification!')
if slaves and verbose: if slaves and verbose:
output_stream.write(', '.join(slaves)) output_stream.write(', '.join(slaves))
output_stream.write('\n') output_stream.write('\n')
......
...@@ -51,8 +51,8 @@ class TryChangeUnittest(TryChangeTestsBase): ...@@ -51,8 +51,8 @@ class TryChangeUnittest(TryChangeTestsBase):
'PrintSuccess', 'PrintSuccess',
'RunCommand', 'RunGit', 'SCM', 'SVN', 'TryChange', 'USAGE', 'breakpad', 'RunCommand', 'RunGit', 'SCM', 'SVN', 'TryChange', 'USAGE', 'breakpad',
'datetime', 'errno', 'fix_encoding', 'gcl', 'gclient_utils', 'gen_parser', 'datetime', 'errno', 'fix_encoding', 'gcl', 'gclient_utils', 'gen_parser',
'getpass', 'json', 'logging', 'optparse', 'os', 'posixpath', 're', 'scm', 'getpass', 'itertools', 'json', 'logging', 'optparse', 'os', 'posixpath',
'shutil', 'subprocess2', 'sys', 'tempfile', 'urllib'] 're', 'scm', 'shutil', 'subprocess2', 'sys', 'tempfile', 'urllib']
# If this test fails, you should add the relevant test. # If this test fails, you should add the relevant test.
self.compareMembers(trychange, members) self.compareMembers(trychange, members)
...@@ -70,7 +70,10 @@ class TryChangeSimpleTest(unittest.TestCase): ...@@ -70,7 +70,10 @@ class TryChangeSimpleTest(unittest.TestCase):
options, args = trychange.gen_parser(None).parse_args(cmd) options, args = trychange.gen_parser(None).parse_args(cmd)
self.assertEquals([], args) self.assertEquals([], args)
# pylint: disable=W0212 # pylint: disable=W0212
values = trychange._ParseSendChangeOptions(options) bot_spec = trychange._ParseBotList(options.bot, options.testfilter)
if options.testfilter:
bot_spec = trychange._ApplyTestFilter(options.testfilter, bot_spec)
values = trychange._ParseSendChangeOptions(bot_spec, options)
self.assertEquals( self.assertEquals(
[ [
('user', 'joe'), ('user', 'joe'),
...@@ -90,7 +93,7 @@ class TryChangeSimpleTest(unittest.TestCase): ...@@ -90,7 +93,7 @@ class TryChangeSimpleTest(unittest.TestCase):
self.assertEquals([], args) self.assertEquals([], args)
try: try:
# pylint: disable=W0212 # pylint: disable=W0212
trychange._ParseSendChangeOptions(options) trychange._ParseBotList(options.bot, options.testfilter)
self.fail() self.fail()
except ValueError: except ValueError:
pass pass
......
...@@ -11,6 +11,7 @@ to the server by HTTP. ...@@ -11,6 +11,7 @@ to the server by HTTP.
import datetime import datetime
import errno import errno
import getpass import getpass
import itertools
import json import json
import logging import logging
import optparse import optparse
...@@ -314,7 +315,88 @@ class GIT(SCM): ...@@ -314,7 +315,88 @@ class GIT(SCM):
branch=self.diff_against) branch=self.diff_against)
def _ParseSendChangeOptions(options): def _ParseBotList(botlist, testfilter):
"""Parses bot configurations from a list of strings."""
bots = []
if testfilter:
for bot in itertools.chain.from_iterable(botspec.split(',')
for botspec in botlist):
tests = set()
if ':' in bot:
if bot.endswith(':compile'):
tests |= set(['compile'])
else:
raise ValueError(
'Can\'t use both --testfilter and --bot builder:test formats '
'at the same time')
bots.append((bot, tests))
else:
for botspec in botlist:
botname = botspec.split(':')[0]
tests = set()
if ':' in botspec:
tests |= set(filter(None, botspec.split(':')[1].split(',')))
bots.append((botname, tests))
return bots
def _ApplyTestFilter(testfilter, bot_spec):
"""Applies testfilter from CLI.
Specifying a testfilter strips off any builder-specified tests (except for
compile).
"""
if testfilter:
return [(botname, set(testfilter) | (tests & set(['compile'])))
for botname, tests in bot_spec]
else:
return bot_spec
def _GenTSBotSpec(checkouts, change, changed_files, options):
bot_spec = []
# Get try slaves from PRESUBMIT.py files if not specified.
# Even if the diff comes from options.url, use the local checkout for bot
# selection.
try:
import presubmit_support
root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
if not change:
if not changed_files:
changed_files = checkouts[0].file_tuples
change = presubmit_support.Change(options.name,
'',
checkouts[0].checkout_root,
changed_files,
options.issue,
options.patchset,
options.email)
trybots = presubmit_support.DoGetTrySlaves(
change,
checkouts[0].GetFileNames(),
checkouts[0].checkout_root,
root_presubmit,
options.project,
options.verbose,
sys.stdout)
if trybots:
if isinstance(trybots[0], basestring):
# PRESUBMIT.py sent us an old-style string list of bots.
# _ParseBotList's testfilter is set to None otherwise it will complain.
bot_spec = _ApplyTestFilter(options.testfilter,
_ParseBotList(trybots, None))
else:
# PRESUBMIT.py sent us a new-style (bot, set()) specification.
bot_spec = _ApplyTestFilter(options.testfilter, trybots)
except ImportError:
pass
return bot_spec
def _ParseSendChangeOptions(bot_spec, options):
"""Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" """Parse common options passed to _SendChangeHTTP and _SendChangeSVN."""
values = [ values = [
('user', options.user), ('user', options.user),
...@@ -339,23 +421,13 @@ def _ParseSendChangeOptions(options): ...@@ -339,23 +421,13 @@ def _ParseSendChangeOptions(options):
if options.project: if options.project:
values.append(('project', options.project)) values.append(('project', options.project))
filters = ','.join(options.testfilter) for bot, tests in bot_spec:
if filters: values.append(('bot', ('%s:%s' % (bot, ','.join(tests)))))
for botlist in options.bot:
for bot in botlist.split(','):
if ':' in bot:
raise ValueError(
'Can\'t use both --testfilter and --bot builder:test formats '
'at the same time')
else:
values.append(('bot', '%s:%s' % (bot, filters)))
else:
for bot in options.bot:
values.append(('bot', bot))
return values return values
def _SendChangeHTTP(options): def _SendChangeHTTP(bot_spec, options):
"""Send a change to the try server using the HTTP protocol.""" """Send a change to the try server using the HTTP protocol."""
if not options.host: if not options.host:
raise NoTryServerAccess('Please use the --host option to specify the try ' raise NoTryServerAccess('Please use the --host option to specify the try '
...@@ -364,7 +436,7 @@ def _SendChangeHTTP(options): ...@@ -364,7 +436,7 @@ def _SendChangeHTTP(options):
raise NoTryServerAccess('Please use the --port option to specify the try ' raise NoTryServerAccess('Please use the --port option to specify the try '
'server port to connect to.') 'server port to connect to.')
values = _ParseSendChangeOptions(options) values = _ParseSendChangeOptions(bot_spec, options)
values.append(('patch', options.diff)) values.append(('patch', options.diff))
url = 'http://%s:%s/send_try_patch' % (options.host, options.port) url = 'http://%s:%s/send_try_patch' % (options.host, options.port)
...@@ -389,7 +461,7 @@ def _SendChangeHTTP(options): ...@@ -389,7 +461,7 @@ def _SendChangeHTTP(options):
logging.info('Done') logging.info('Done')
except IOError, e: except IOError, e:
logging.info(str(e)) logging.info(str(e))
if options.bot and len(e.args) > 2 and e.args[2] == 'got a bad status line': if bot_spec and len(e.args) > 2 and e.args[2] == 'got a bad status line':
raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url) raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url)
else: else:
raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url,
...@@ -403,14 +475,14 @@ def _SendChangeHTTP(options): ...@@ -403,14 +475,14 @@ def _SendChangeHTTP(options):
raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response))
def _SendChangeSVN(options): def _SendChangeSVN(bot_spec, options):
"""Send a change to the try server by committing a diff file on a subversion """Send a change to the try server by committing a diff file on a subversion
server.""" server."""
if not options.svn_repo: if not options.svn_repo:
raise NoTryServerAccess('Please use the --svn_repo option to specify the' raise NoTryServerAccess('Please use the --svn_repo option to specify the'
' try server svn repository to connect to.') ' try server svn repository to connect to.')
values = _ParseSendChangeOptions(options) values = _ParseSendChangeOptions(bot_spec, options)
description = ''.join("%s=%s\n" % (k, v) for k, v in values) description = ''.join("%s=%s\n" % (k, v) for k, v in values)
logging.info('Sending by SVN') logging.info('Sending by SVN')
logging.info(description) logging.info(description)
...@@ -460,11 +532,12 @@ def _SendChangeSVN(options): ...@@ -460,11 +532,12 @@ def _SendChangeSVN(options):
shutil.rmtree(temp_dir, True) shutil.rmtree(temp_dir, True)
def PrintSuccess(options): def PrintSuccess(bot_spec, options):
if not options.dry_run: if not options.dry_run:
text = 'Patch \'%s\' sent to try server' % options.name text = 'Patch \'%s\' sent to try server' % options.name
if options.bot: if bot_spec:
text += ': %s' % ', '.join(options.bot) text += ': %s' % ', '.join(
'%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec)
print(text) print(text)
...@@ -811,75 +884,47 @@ def TryChange(argv, ...@@ -811,75 +884,47 @@ def TryChange(argv,
'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.')
print('Results will be emailed to: ' + options.email) print('Results will be emailed to: ' + options.email)
if not options.bot: if options.bot:
# Get try slaves from PRESUBMIT.py files if not specified. bot_spec = _ApplyTestFilter(
# Even if the diff comes from options.url, use the local checkout for bot options.testfilter, _ParseBotList(options.bot, options.testfilter))
# selection. else:
try: bot_spec = _GenTSBotSpec(checkouts, change, changed_files, options)
import presubmit_support
root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
if not change:
if not changed_files:
changed_files = checkouts[0].file_tuples
change = presubmit_support.Change(options.name,
'',
checkouts[0].checkout_root,
changed_files,
options.issue,
options.patchset,
options.email)
options.bot = presubmit_support.DoGetTrySlaves(
change,
checkouts[0].GetFileNames(),
checkouts[0].checkout_root,
root_presubmit,
options.project,
options.verbose,
sys.stdout)
except ImportError:
pass
if options.testfilter:
bots = set()
for bot in options.bot:
assert ',' not in bot
if bot.endswith(':compile'):
# Skip over compile-only builders for now.
continue
bots.add(bot.split(':', 1)[0])
options.bot = list(bots)
# If no bot is specified, either the default pool will be selected or the if options.testfilter:
# try server will refuse the job. Either case we don't need to interfere. bot_spec = _ApplyTestFilter(options.testfilter, bot_spec)
if any('triggered' in b.split(':', 1)[0] for b in options.bot): if any('triggered' in b[0] for b in bot_spec):
print >> sys.stderr, ( print >> sys.stderr, (
'ERROR You are trying to send a job to a triggered bot. This type of' 'ERROR You are trying to send a job to a triggered bot. This type of'
' bot requires an\ninitial job from a parent (usually a builder). ' ' bot requires an\ninitial job from a parent (usually a builder). '
'Instead send your job to the parent.\nBot list: %s' % options.bot) 'Instead send your job to the parent.\nBot list: %s' % bot_spec)
return 1 return 1
if options.print_bots: if options.print_bots:
print 'Bots which would be used:' print 'Bots which would be used:'
for bot in options.bot: for bot in bot_spec:
print ' %s' % bot if bot[1]:
print ' %s:%s' % (bot[0], ','.join(bot[1]))
else:
print ' %s' % (bot[0])
return 0 return 0
# Send the patch. # Send the patch.
if options.send_patch: if options.send_patch:
# If forced. # If forced.
options.send_patch(options) options.send_patch(bot_spec, options)
PrintSuccess(options) PrintSuccess(bot_spec, options)
return 0 return 0
try: try:
if can_http: if can_http:
_SendChangeHTTP(options) _SendChangeHTTP(bot_spec, options)
PrintSuccess(options) PrintSuccess(bot_spec, options)
return 0 return 0
except NoTryServerAccess: except NoTryServerAccess:
if not can_svn: if not can_svn:
raise raise
_SendChangeSVN(options) _SendChangeSVN(bot_spec, options)
PrintSuccess(options) PrintSuccess(bot_spec, options)
return 0 return 0
except (InvalidScript, NoTryServerAccess), e: except (InvalidScript, NoTryServerAccess), e:
if swallow_exception: if swallow_exception:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment