Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(17)

Side by Side Diff: trychange.py

Issue 54373011: Rework bot and test parsing to allow receipt of (bot, set(test)) specifications. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Final changes. Created 7 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « tests/trychange_unittest.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 """Client-side script to send a try job to the try server. It communicates to 6 """Client-side script to send a try job to the try server. It communicates to
7 the try server by either writting to a svn repository or by directly connecting 7 the try server by either writting to a svn repository or by directly connecting
8 to the server by HTTP. 8 to the server by HTTP.
9 """ 9 """
10 10
11 import datetime 11 import datetime
12 import errno 12 import errno
13 import getpass 13 import getpass
14 import itertools
14 import json 15 import json
15 import logging 16 import logging
16 import optparse 17 import optparse
17 import os 18 import os
18 import posixpath 19 import posixpath
19 import re 20 import re
20 import shutil 21 import shutil
21 import sys 22 import sys
22 import tempfile 23 import tempfile
23 import urllib 24 import urllib
(...skipping 283 matching lines...) Expand 10 before | Expand all | Expand 10 after
307 if RunGit(['diff-index', 'HEAD']): 308 if RunGit(['diff-index', 'HEAD']):
308 print 'Cannot try with a dirty tree. You must commit locally first.' 309 print 'Cannot try with a dirty tree. You must commit locally first.'
309 return None 310 return None
310 return scm.GIT.GenerateDiff( 311 return scm.GIT.GenerateDiff(
311 self.checkout_root, 312 self.checkout_root,
312 files=self.files, 313 files=self.files,
313 full_move=True, 314 full_move=True,
314 branch=self.diff_against) 315 branch=self.diff_against)
315 316
316 317
317 def _ParseSendChangeOptions(options): 318 def _ParseBotList(botlist, testfilter):
319 """Parses bot configurations from a list of strings."""
320 bots = []
321 if testfilter:
322 for bot in itertools.chain.from_iterable(botspec.split(',')
323 for botspec in botlist):
324 tests = set()
325 if ':' in bot:
326 if bot.endswith(':compile'):
327 tests |= set(['compile'])
328 else:
329 raise ValueError(
330 'Can\'t use both --testfilter and --bot builder:test formats '
331 'at the same time')
332
333 bots.append((bot, tests))
334 else:
335 for botspec in botlist:
336 botname = botspec.split(':')[0]
337 tests = set()
338 if ':' in botspec:
339 tests |= set(filter(None, botspec.split(':')[1].split(',')))
340 bots.append((botname, tests))
341 return bots
342
343
344 def _ApplyTestFilter(testfilter, bot_spec):
345 """Applies testfilter from CLI.
346
347 Specifying a testfilter strips off any builder-specified tests (except for
348 compile).
349 """
350 if testfilter:
351 return [(botname, set(testfilter) | (tests & set(['compile'])))
352 for botname, tests in bot_spec]
353 else:
354 return bot_spec
355
356
357 def _GenTSBotSpec(checkouts, change, changed_files, options):
358 bot_spec = []
359 # Get try slaves from PRESUBMIT.py files if not specified.
360 # Even if the diff comes from options.url, use the local checkout for bot
361 # selection.
362 try:
363 import presubmit_support
364 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
365 if not change:
366 if not changed_files:
367 changed_files = checkouts[0].file_tuples
368 change = presubmit_support.Change(options.name,
369 '',
370 checkouts[0].checkout_root,
371 changed_files,
372 options.issue,
373 options.patchset,
374 options.email)
375 trybots = presubmit_support.DoGetTrySlaves(
376 change,
377 checkouts[0].GetFileNames(),
378 checkouts[0].checkout_root,
379 root_presubmit,
380 options.project,
381 options.verbose,
382 sys.stdout)
383 if trybots:
384 if isinstance(trybots[0], basestring):
385 # PRESUBMIT.py sent us an old-style string list of bots.
386 # _ParseBotList's testfilter is set to None otherwise it will complain.
387 bot_spec = _ApplyTestFilter(options.testfilter,
388 _ParseBotList(trybots, None))
389 else:
390 # PRESUBMIT.py sent us a new-style (bot, set()) specification.
391 bot_spec = _ApplyTestFilter(options.testfilter, trybots)
392
393 except ImportError:
394 pass
395
396 return bot_spec
397
398
399 def _ParseSendChangeOptions(bot_spec, options):
318 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" 400 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN."""
319 values = [ 401 values = [
320 ('user', options.user), 402 ('user', options.user),
321 ('name', options.name), 403 ('name', options.name),
322 ] 404 ]
323 if options.email: 405 if options.email:
324 values.append(('email', options.email)) 406 values.append(('email', options.email))
325 if options.revision: 407 if options.revision:
326 values.append(('revision', options.revision)) 408 values.append(('revision', options.revision))
327 if options.clobber: 409 if options.clobber:
328 values.append(('clobber', 'true')) 410 values.append(('clobber', 'true'))
329 if options.root: 411 if options.root:
330 values.append(('root', options.root)) 412 values.append(('root', options.root))
331 if options.patchlevel: 413 if options.patchlevel:
332 values.append(('patchlevel', options.patchlevel)) 414 values.append(('patchlevel', options.patchlevel))
333 if options.issue: 415 if options.issue:
334 values.append(('issue', options.issue)) 416 values.append(('issue', options.issue))
335 if options.patchset: 417 if options.patchset:
336 values.append(('patchset', options.patchset)) 418 values.append(('patchset', options.patchset))
337 if options.target: 419 if options.target:
338 values.append(('target', options.target)) 420 values.append(('target', options.target))
339 if options.project: 421 if options.project:
340 values.append(('project', options.project)) 422 values.append(('project', options.project))
341 423
342 filters = ','.join(options.testfilter) 424 for bot, tests in bot_spec:
343 if filters: 425 values.append(('bot', ('%s:%s' % (bot, ','.join(tests)))))
344 for botlist in options.bot: 426
345 for bot in botlist.split(','):
346 if ':' in bot:
347 raise ValueError(
348 'Can\'t use both --testfilter and --bot builder:test formats '
349 'at the same time')
350 else:
351 values.append(('bot', '%s:%s' % (bot, filters)))
352 else:
353 for bot in options.bot:
354 values.append(('bot', bot))
355 return values 427 return values
356 428
357 429
358 def _SendChangeHTTP(options): 430 def _SendChangeHTTP(bot_spec, options):
359 """Send a change to the try server using the HTTP protocol.""" 431 """Send a change to the try server using the HTTP protocol."""
360 if not options.host: 432 if not options.host:
361 raise NoTryServerAccess('Please use the --host option to specify the try ' 433 raise NoTryServerAccess('Please use the --host option to specify the try '
362 'server host to connect to.') 434 'server host to connect to.')
363 if not options.port: 435 if not options.port:
364 raise NoTryServerAccess('Please use the --port option to specify the try ' 436 raise NoTryServerAccess('Please use the --port option to specify the try '
365 'server port to connect to.') 437 'server port to connect to.')
366 438
367 values = _ParseSendChangeOptions(options) 439 values = _ParseSendChangeOptions(bot_spec, options)
368 values.append(('patch', options.diff)) 440 values.append(('patch', options.diff))
369 441
370 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) 442 url = 'http://%s:%s/send_try_patch' % (options.host, options.port)
371 proxies = None 443 proxies = None
372 if options.proxy: 444 if options.proxy:
373 if options.proxy.lower() == 'none': 445 if options.proxy.lower() == 'none':
374 # Effectively disable HTTP_PROXY or Internet settings proxy setup. 446 # Effectively disable HTTP_PROXY or Internet settings proxy setup.
375 proxies = {} 447 proxies = {}
376 else: 448 else:
377 proxies = {'http': options.proxy, 'https': options.proxy} 449 proxies = {'http': options.proxy, 'https': options.proxy}
378 450
379 logging.info('Sending by HTTP') 451 logging.info('Sending by HTTP')
380 logging.info(''.join("%s=%s\n" % (k, v) for k, v in values)) 452 logging.info(''.join("%s=%s\n" % (k, v) for k, v in values))
381 logging.info(url) 453 logging.info(url)
382 logging.info(options.diff) 454 logging.info(options.diff)
383 if options.dry_run: 455 if options.dry_run:
384 return 456 return
385 457
386 try: 458 try:
387 logging.info('Opening connection...') 459 logging.info('Opening connection...')
388 connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies) 460 connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies)
389 logging.info('Done') 461 logging.info('Done')
390 except IOError, e: 462 except IOError, e:
391 logging.info(str(e)) 463 logging.info(str(e))
392 if options.bot and len(e.args) > 2 and e.args[2] == 'got a bad status line': 464 if bot_spec and len(e.args) > 2 and e.args[2] == 'got a bad status line':
393 raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url) 465 raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url)
394 else: 466 else:
395 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, 467 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url,
396 str(e.args))) 468 str(e.args)))
397 if not connection: 469 if not connection:
398 raise NoTryServerAccess('%s is unaccessible.' % url) 470 raise NoTryServerAccess('%s is unaccessible.' % url)
399 logging.info('Reading response...') 471 logging.info('Reading response...')
400 response = connection.read() 472 response = connection.read()
401 logging.info('Done') 473 logging.info('Done')
402 if response != 'OK': 474 if response != 'OK':
403 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) 475 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response))
404 476
405 477
406 def _SendChangeSVN(options): 478 def _SendChangeSVN(bot_spec, options):
407 """Send a change to the try server by committing a diff file on a subversion 479 """Send a change to the try server by committing a diff file on a subversion
408 server.""" 480 server."""
409 if not options.svn_repo: 481 if not options.svn_repo:
410 raise NoTryServerAccess('Please use the --svn_repo option to specify the' 482 raise NoTryServerAccess('Please use the --svn_repo option to specify the'
411 ' try server svn repository to connect to.') 483 ' try server svn repository to connect to.')
412 484
413 values = _ParseSendChangeOptions(options) 485 values = _ParseSendChangeOptions(bot_spec, options)
414 description = ''.join("%s=%s\n" % (k, v) for k, v in values) 486 description = ''.join("%s=%s\n" % (k, v) for k, v in values)
415 logging.info('Sending by SVN') 487 logging.info('Sending by SVN')
416 logging.info(description) 488 logging.info(description)
417 logging.info(options.svn_repo) 489 logging.info(options.svn_repo)
418 logging.info(options.diff) 490 logging.info(options.diff)
419 if options.dry_run: 491 if options.dry_run:
420 return 492 return
421 493
422 # Create a temporary directory, put a uniquely named file in it with the diff 494 # Create a temporary directory, put a uniquely named file in it with the diff
423 # content and svn import that. 495 # content and svn import that.
(...skipping 29 matching lines...) Expand all
453 command.append('--no-ignore') 525 command.append('--no-ignore')
454 526
455 subprocess2.check_call(command) 527 subprocess2.check_call(command)
456 except subprocess2.CalledProcessError, e: 528 except subprocess2.CalledProcessError, e:
457 raise NoTryServerAccess(str(e)) 529 raise NoTryServerAccess(str(e))
458 finally: 530 finally:
459 temp_file.close() 531 temp_file.close()
460 shutil.rmtree(temp_dir, True) 532 shutil.rmtree(temp_dir, True)
461 533
462 534
463 def PrintSuccess(options): 535 def PrintSuccess(bot_spec, options):
464 if not options.dry_run: 536 if not options.dry_run:
465 text = 'Patch \'%s\' sent to try server' % options.name 537 text = 'Patch \'%s\' sent to try server' % options.name
466 if options.bot: 538 if bot_spec:
467 text += ': %s' % ', '.join(options.bot) 539 text += ': %s' % ', '.join(
540 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec)
468 print(text) 541 print(text)
469 542
470 543
471 def GuessVCS(options, path, file_list): 544 def GuessVCS(options, path, file_list):
472 """Helper to guess the version control system. 545 """Helper to guess the version control system.
473 546
474 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't 547 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't
475 support it yet. 548 support it yet.
476 549
477 This examines the path directory, guesses which SCM we're using, and 550 This examines the path directory, guesses which SCM we're using, and
(...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after
804 options.name = 'Issue %s' % options.issue 877 options.name = 'Issue %s' % options.issue
805 else: 878 else:
806 options.name = 'Unnamed' 879 options.name = 'Unnamed'
807 print('Note: use --name NAME to change the try job name.') 880 print('Note: use --name NAME to change the try job name.')
808 881
809 if not options.email: 882 if not options.email:
810 parser.error('Using an anonymous checkout. Please use --email or set ' 883 parser.error('Using an anonymous checkout. Please use --email or set '
811 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') 884 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.')
812 print('Results will be emailed to: ' + options.email) 885 print('Results will be emailed to: ' + options.email)
813 886
814 if not options.bot: 887 if options.bot:
815 # Get try slaves from PRESUBMIT.py files if not specified. 888 bot_spec = _ApplyTestFilter(
816 # Even if the diff comes from options.url, use the local checkout for bot 889 options.testfilter, _ParseBotList(options.bot, options.testfilter))
817 # selection. 890 else:
818 try: 891 bot_spec = _GenTSBotSpec(checkouts, change, changed_files, options)
819 import presubmit_support
820 root_presubmit = checkouts[0].ReadRootFile('PRESUBMIT.py')
821 if not change:
822 if not changed_files:
823 changed_files = checkouts[0].file_tuples
824 change = presubmit_support.Change(options.name,
825 '',
826 checkouts[0].checkout_root,
827 changed_files,
828 options.issue,
829 options.patchset,
830 options.email)
831 options.bot = presubmit_support.DoGetTrySlaves(
832 change,
833 checkouts[0].GetFileNames(),
834 checkouts[0].checkout_root,
835 root_presubmit,
836 options.project,
837 options.verbose,
838 sys.stdout)
839 except ImportError:
840 pass
841 if options.testfilter:
842 bots = set()
843 for bot in options.bot:
844 assert ',' not in bot
845 if bot.endswith(':compile'):
846 # Skip over compile-only builders for now.
847 continue
848 bots.add(bot.split(':', 1)[0])
849 options.bot = list(bots)
850 892
851 # If no bot is specified, either the default pool will be selected or the 893 if options.testfilter:
852 # try server will refuse the job. Either case we don't need to interfere. 894 bot_spec = _ApplyTestFilter(options.testfilter, bot_spec)
853 895
854 if any('triggered' in b.split(':', 1)[0] for b in options.bot): 896 if any('triggered' in b[0] for b in bot_spec):
855 print >> sys.stderr, ( 897 print >> sys.stderr, (
856 'ERROR You are trying to send a job to a triggered bot. This type of' 898 'ERROR You are trying to send a job to a triggered bot. This type of'
857 ' bot requires an\ninitial job from a parent (usually a builder). ' 899 ' bot requires an\ninitial job from a parent (usually a builder). '
858 'Instead send your job to the parent.\nBot list: %s' % options.bot) 900 'Instead send your job to the parent.\nBot list: %s' % bot_spec)
859 return 1 901 return 1
860 902
861 if options.print_bots: 903 if options.print_bots:
862 print 'Bots which would be used:' 904 print 'Bots which would be used:'
863 for bot in options.bot: 905 for bot in bot_spec:
864 print ' %s' % bot 906 if bot[1]:
907 print ' %s:%s' % (bot[0], ','.join(bot[1]))
908 else:
909 print ' %s' % (bot[0])
865 return 0 910 return 0
866 911
867 # Send the patch. 912 # Send the patch.
868 if options.send_patch: 913 if options.send_patch:
869 # If forced. 914 # If forced.
870 options.send_patch(options) 915 options.send_patch(bot_spec, options)
871 PrintSuccess(options) 916 PrintSuccess(bot_spec, options)
872 return 0 917 return 0
873 try: 918 try:
874 if can_http: 919 if can_http:
875 _SendChangeHTTP(options) 920 _SendChangeHTTP(bot_spec, options)
876 PrintSuccess(options) 921 PrintSuccess(bot_spec, options)
877 return 0 922 return 0
878 except NoTryServerAccess: 923 except NoTryServerAccess:
879 if not can_svn: 924 if not can_svn:
880 raise 925 raise
881 _SendChangeSVN(options) 926 _SendChangeSVN(bot_spec, options)
882 PrintSuccess(options) 927 PrintSuccess(bot_spec, options)
883 return 0 928 return 0
884 except (InvalidScript, NoTryServerAccess), e: 929 except (InvalidScript, NoTryServerAccess), e:
885 if swallow_exception: 930 if swallow_exception:
886 return 1 931 return 1
887 print >> sys.stderr, e 932 print >> sys.stderr, e
888 return 1 933 return 1
889 except (gclient_utils.Error, subprocess2.CalledProcessError), e: 934 except (gclient_utils.Error, subprocess2.CalledProcessError), e:
890 print >> sys.stderr, e 935 print >> sys.stderr, e
891 return 1 936 return 1
892 return 0 937 return 0
893 938
894 939
895 if __name__ == "__main__": 940 if __name__ == "__main__":
896 fix_encoding.fix_encoding() 941 fix_encoding.fix_encoding()
897 sys.exit(TryChange(None, None, False)) 942 sys.exit(TryChange(None, None, False))
OLDNEW
« no previous file with comments | « tests/trychange_unittest.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698