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 """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 Loading... | |
| 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 the command line.""" | |
| 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 | |
|
M-A Ruel
2013/11/01 13:13:57
lines
ghost stip (do not use)
2013/11/04 22:01:52
Done.
| |
| 399 | |
| 400 def _ParseSendChangeOptions(bot_spec, options): | |
| 318 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" | 401 """Parse common options passed to _SendChangeHTTP and _SendChangeSVN.""" |
| 319 values = [ | 402 values = [ |
| 320 ('user', options.user), | 403 ('user', options.user), |
| 321 ('name', options.name), | 404 ('name', options.name), |
| 322 ] | 405 ] |
| 323 if options.email: | 406 if options.email: |
| 324 values.append(('email', options.email)) | 407 values.append(('email', options.email)) |
| 325 if options.revision: | 408 if options.revision: |
| 326 values.append(('revision', options.revision)) | 409 values.append(('revision', options.revision)) |
| 327 if options.clobber: | 410 if options.clobber: |
| 328 values.append(('clobber', 'true')) | 411 values.append(('clobber', 'true')) |
| 329 if options.root: | 412 if options.root: |
| 330 values.append(('root', options.root)) | 413 values.append(('root', options.root)) |
| 331 if options.patchlevel: | 414 if options.patchlevel: |
| 332 values.append(('patchlevel', options.patchlevel)) | 415 values.append(('patchlevel', options.patchlevel)) |
| 333 if options.issue: | 416 if options.issue: |
| 334 values.append(('issue', options.issue)) | 417 values.append(('issue', options.issue)) |
| 335 if options.patchset: | 418 if options.patchset: |
| 336 values.append(('patchset', options.patchset)) | 419 values.append(('patchset', options.patchset)) |
| 337 if options.target: | 420 if options.target: |
| 338 values.append(('target', options.target)) | 421 values.append(('target', options.target)) |
| 339 if options.project: | 422 if options.project: |
| 340 values.append(('project', options.project)) | 423 values.append(('project', options.project)) |
| 341 | 424 |
| 342 filters = ','.join(options.testfilter) | 425 for bot, tests in bot_spec: |
| 343 if filters: | 426 values.append(('bot', ('%s:%s' % (bot, ','.join(tests))))) |
| 344 for botlist in options.bot: | 427 |
| 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 | 428 return values |
| 356 | 429 |
| 357 | 430 |
| 358 def _SendChangeHTTP(options): | 431 def _SendChangeHTTP(bot_spec, options): |
| 359 """Send a change to the try server using the HTTP protocol.""" | 432 """Send a change to the try server using the HTTP protocol.""" |
| 360 if not options.host: | 433 if not options.host: |
| 361 raise NoTryServerAccess('Please use the --host option to specify the try ' | 434 raise NoTryServerAccess('Please use the --host option to specify the try ' |
| 362 'server host to connect to.') | 435 'server host to connect to.') |
| 363 if not options.port: | 436 if not options.port: |
| 364 raise NoTryServerAccess('Please use the --port option to specify the try ' | 437 raise NoTryServerAccess('Please use the --port option to specify the try ' |
| 365 'server port to connect to.') | 438 'server port to connect to.') |
| 366 | 439 |
| 367 values = _ParseSendChangeOptions(options) | 440 values = _ParseSendChangeOptions(bot_spec, options) |
| 368 values.append(('patch', options.diff)) | 441 values.append(('patch', options.diff)) |
| 369 | 442 |
| 370 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) | 443 url = 'http://%s:%s/send_try_patch' % (options.host, options.port) |
| 371 proxies = None | 444 proxies = None |
| 372 if options.proxy: | 445 if options.proxy: |
| 373 if options.proxy.lower() == 'none': | 446 if options.proxy.lower() == 'none': |
| 374 # Effectively disable HTTP_PROXY or Internet settings proxy setup. | 447 # Effectively disable HTTP_PROXY or Internet settings proxy setup. |
| 375 proxies = {} | 448 proxies = {} |
| 376 else: | 449 else: |
| 377 proxies = {'http': options.proxy, 'https': options.proxy} | 450 proxies = {'http': options.proxy, 'https': options.proxy} |
| 378 | 451 |
| 379 logging.info('Sending by HTTP') | 452 logging.info('Sending by HTTP') |
| 380 logging.info(''.join("%s=%s\n" % (k, v) for k, v in values)) | 453 logging.info(''.join("%s=%s\n" % (k, v) for k, v in values)) |
| 381 logging.info(url) | 454 logging.info(url) |
| 382 logging.info(options.diff) | 455 logging.info(options.diff) |
| 383 if options.dry_run: | 456 if options.dry_run: |
| 384 return | 457 return |
| 385 | 458 |
| 386 try: | 459 try: |
| 387 logging.info('Opening connection...') | 460 logging.info('Opening connection...') |
| 388 connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies) | 461 connection = urllib.urlopen(url, urllib.urlencode(values), proxies=proxies) |
| 389 logging.info('Done') | 462 logging.info('Done') |
| 390 except IOError, e: | 463 except IOError, e: |
| 391 logging.info(str(e)) | 464 logging.info(str(e)) |
| 392 if options.bot and len(e.args) > 2 and e.args[2] == 'got a bad status line': | 465 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) | 466 raise NoTryServerAccess('%s is unaccessible. Bad --bot argument?' % url) |
| 394 else: | 467 else: |
| 395 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, | 468 raise NoTryServerAccess('%s is unaccessible. Reason: %s' % (url, |
| 396 str(e.args))) | 469 str(e.args))) |
| 397 if not connection: | 470 if not connection: |
| 398 raise NoTryServerAccess('%s is unaccessible.' % url) | 471 raise NoTryServerAccess('%s is unaccessible.' % url) |
| 399 logging.info('Reading response...') | 472 logging.info('Reading response...') |
| 400 response = connection.read() | 473 response = connection.read() |
| 401 logging.info('Done') | 474 logging.info('Done') |
| 402 if response != 'OK': | 475 if response != 'OK': |
| 403 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) | 476 raise NoTryServerAccess('%s is unaccessible. Got:\n%s' % (url, response)) |
| 404 | 477 |
| 405 | 478 |
| 406 def _SendChangeSVN(options): | 479 def _SendChangeSVN(bot_spec, options): |
| 407 """Send a change to the try server by committing a diff file on a subversion | 480 """Send a change to the try server by committing a diff file on a subversion |
| 408 server.""" | 481 server.""" |
| 409 if not options.svn_repo: | 482 if not options.svn_repo: |
| 410 raise NoTryServerAccess('Please use the --svn_repo option to specify the' | 483 raise NoTryServerAccess('Please use the --svn_repo option to specify the' |
| 411 ' try server svn repository to connect to.') | 484 ' try server svn repository to connect to.') |
| 412 | 485 |
| 413 values = _ParseSendChangeOptions(options) | 486 values = _ParseSendChangeOptions(bot_spec, options) |
| 414 description = ''.join("%s=%s\n" % (k, v) for k, v in values) | 487 description = ''.join("%s=%s\n" % (k, v) for k, v in values) |
| 415 logging.info('Sending by SVN') | 488 logging.info('Sending by SVN') |
| 416 logging.info(description) | 489 logging.info(description) |
| 417 logging.info(options.svn_repo) | 490 logging.info(options.svn_repo) |
| 418 logging.info(options.diff) | 491 logging.info(options.diff) |
| 419 if options.dry_run: | 492 if options.dry_run: |
| 420 return | 493 return |
| 421 | 494 |
| 422 # Create a temporary directory, put a uniquely named file in it with the diff | 495 # Create a temporary directory, put a uniquely named file in it with the diff |
| 423 # content and svn import that. | 496 # content and svn import that. |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 453 command.append('--no-ignore') | 526 command.append('--no-ignore') |
| 454 | 527 |
| 455 subprocess2.check_call(command) | 528 subprocess2.check_call(command) |
| 456 except subprocess2.CalledProcessError, e: | 529 except subprocess2.CalledProcessError, e: |
| 457 raise NoTryServerAccess(str(e)) | 530 raise NoTryServerAccess(str(e)) |
| 458 finally: | 531 finally: |
| 459 temp_file.close() | 532 temp_file.close() |
| 460 shutil.rmtree(temp_dir, True) | 533 shutil.rmtree(temp_dir, True) |
| 461 | 534 |
| 462 | 535 |
| 463 def PrintSuccess(options): | 536 def PrintSuccess(bot_spec, options): |
| 464 if not options.dry_run: | 537 if not options.dry_run: |
| 465 text = 'Patch \'%s\' sent to try server' % options.name | 538 text = 'Patch \'%s\' sent to try server' % options.name |
| 466 if options.bot: | 539 if bot_spec: |
| 467 text += ': %s' % ', '.join(options.bot) | 540 text += ': %s' % ', '.join( |
| 541 '%s:%s' % (b[0], ','.join(b[1])) for b in bot_spec) | |
| 468 print(text) | 542 print(text) |
| 469 | 543 |
| 470 | 544 |
| 471 def GuessVCS(options, path, file_list): | 545 def GuessVCS(options, path, file_list): |
| 472 """Helper to guess the version control system. | 546 """Helper to guess the version control system. |
| 473 | 547 |
| 474 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't | 548 NOTE: Very similar to upload.GuessVCS. Doesn't look for hg since we don't |
| 475 support it yet. | 549 support it yet. |
| 476 | 550 |
| 477 This examines the path directory, guesses which SCM we're using, and | 551 This examines the path directory, guesses which SCM we're using, and |
| (...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 804 options.name = 'Issue %s' % options.issue | 878 options.name = 'Issue %s' % options.issue |
| 805 else: | 879 else: |
| 806 options.name = 'Unnamed' | 880 options.name = 'Unnamed' |
| 807 print('Note: use --name NAME to change the try job name.') | 881 print('Note: use --name NAME to change the try job name.') |
| 808 | 882 |
| 809 if not options.email: | 883 if not options.email: |
| 810 parser.error('Using an anonymous checkout. Please use --email or set ' | 884 parser.error('Using an anonymous checkout. Please use --email or set ' |
| 811 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') | 885 'the TRYBOT_RESULTS_EMAIL_ADDRESS environment variable.') |
| 812 print('Results will be emailed to: ' + options.email) | 886 print('Results will be emailed to: ' + options.email) |
| 813 | 887 |
| 814 if not options.bot: | 888 if options.bot: |
| 815 # Get try slaves from PRESUBMIT.py files if not specified. | 889 bot_spec = _ApplyTestFilter( |
| 816 # Even if the diff comes from options.url, use the local checkout for bot | 890 options.testfilter, _ParseBotList(options.bot, options.testfilter)) |
| 817 # selection. | 891 else: |
| 818 try: | 892 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 | 893 |
| 851 # If no bot is specified, either the default pool will be selected or the | 894 if options.testfilter: |
| 852 # try server will refuse the job. Either case we don't need to interfere. | 895 bot_spec = _ApplyTestFilter(options.testfilter, bot_spec) |
| 853 | 896 |
| 854 if any('triggered' in b.split(':', 1)[0] for b in options.bot): | 897 if any('triggered' in b[0] for b in bot_spec): |
| 855 print >> sys.stderr, ( | 898 print >> sys.stderr, ( |
| 856 'ERROR You are trying to send a job to a triggered bot. This type of' | 899 '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). ' | 900 ' bot requires an\ninitial job from a parent (usually a builder). ' |
| 858 'Instead send your job to the parent.\nBot list: %s' % options.bot) | 901 'Instead send your job to the parent.\nBot list: %s' % bot_spec) |
| 859 return 1 | 902 return 1 |
| 860 | 903 |
| 861 if options.print_bots: | 904 if options.print_bots: |
| 862 print 'Bots which would be used:' | 905 print 'Bots which would be used:' |
| 863 for bot in options.bot: | 906 for bot in bot_spec: |
| 864 print ' %s' % bot | 907 if bot[1]: |
| 908 print ' %s:%s' % (bot[0], ','.join(bot[1])) | |
| 909 else: | |
| 910 print ' %s' % (bot[0]) | |
| 865 return 0 | 911 return 0 |
| 866 | 912 |
| 867 # Send the patch. | 913 # Send the patch. |
| 868 if options.send_patch: | 914 if options.send_patch: |
| 869 # If forced. | 915 # If forced. |
| 870 options.send_patch(options) | 916 options.send_patch(bot_spec, options) |
| 871 PrintSuccess(options) | 917 PrintSuccess(bot_spec, options) |
| 872 return 0 | 918 return 0 |
| 873 try: | 919 try: |
| 874 if can_http: | 920 if can_http: |
| 875 _SendChangeHTTP(options) | 921 _SendChangeHTTP(bot_spec, options) |
| 876 PrintSuccess(options) | 922 PrintSuccess(bot_spec, options) |
| 877 return 0 | 923 return 0 |
| 878 except NoTryServerAccess: | 924 except NoTryServerAccess: |
| 879 if not can_svn: | 925 if not can_svn: |
| 880 raise | 926 raise |
| 881 _SendChangeSVN(options) | 927 _SendChangeSVN(bot_spec, options) |
| 882 PrintSuccess(options) | 928 PrintSuccess(bot_spec, options) |
| 883 return 0 | 929 return 0 |
| 884 except (InvalidScript, NoTryServerAccess), e: | 930 except (InvalidScript, NoTryServerAccess), e: |
| 885 if swallow_exception: | 931 if swallow_exception: |
| 886 return 1 | 932 return 1 |
| 887 print >> sys.stderr, e | 933 print >> sys.stderr, e |
| 888 return 1 | 934 return 1 |
| 889 except (gclient_utils.Error, subprocess2.CalledProcessError), e: | 935 except (gclient_utils.Error, subprocess2.CalledProcessError), e: |
| 890 print >> sys.stderr, e | 936 print >> sys.stderr, e |
| 891 return 1 | 937 return 1 |
| 892 return 0 | 938 return 0 |
| 893 | 939 |
| 894 | 940 |
| 895 if __name__ == "__main__": | 941 if __name__ == "__main__": |
| 896 fix_encoding.fix_encoding() | 942 fix_encoding.fix_encoding() |
| 897 sys.exit(TryChange(None, None, False)) | 943 sys.exit(TryChange(None, None, False)) |
| OLD | NEW |