Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 2 # Copyright (c) 2013 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 """Contains generating and parsing systems of the Chromium Buildbot Annotator. | 6 """Contains generating and parsing systems of the Chromium Buildbot Annotator. |
| 7 | 7 |
| 8 When executed as a script, this reads step name / command pairs from a file and | 8 When executed as a script, this reads step name / command pairs from a file and |
| 9 executes those lines while annotating the output. The input is json: | 9 executes those lines while annotating the output. The input is json: |
| 10 | 10 |
| 11 [{"name": "step_name", "cmd": ["command", "arg1", "arg2"]}, | 11 [{"name": "step_name", "cmd": ["command", "arg1", "arg2"]}, |
| 12 {"name": "step_name2", "cmd": ["command2", "arg1"]}] | 12 {"name": "step_name2", "cmd": ["command2", "arg1"]}] |
| 13 | 13 |
| 14 """ | 14 """ |
| 15 | 15 |
| 16 import itertools | |
| 16 import json | 17 import json |
| 17 import optparse | 18 import optparse |
| 18 import os | 19 import os |
| 19 import re | 20 import re |
| 21 import subprocess | |
| 20 import sys | 22 import sys |
| 23 import threading | |
| 21 import traceback | 24 import traceback |
| 22 | 25 |
| 23 from common import chromium_utils | |
| 24 | |
| 25 | 26 |
| 26 def emit(line, stream, flush_before=None): | 27 def emit(line, stream, flush_before=None): |
| 27 if flush_before: | 28 if flush_before: |
| 28 flush_before.flush() | 29 flush_before.flush() |
| 29 print >> stream, '\n' + line | 30 print >> stream |
| 31 # WinDOS can only handle 64kb of output to the console at a time, per process. | |
| 32 if sys.platform.startswith('win'): | |
| 33 lim = 2**15 | |
| 34 while line: | |
| 35 to_print, line = line[:lim], line[lim:] | |
| 36 stream.write(to_print) | |
| 37 else: | |
| 38 print >> stream, line | |
| 30 stream.flush() | 39 stream.flush() |
| 31 | 40 |
| 32 | 41 |
| 33 class StepCommands(object): | 42 class StepCommands(object): |
| 34 """Class holding step commands. Intended to be subclassed.""" | 43 """Class holding step commands. Intended to be subclassed.""" |
| 35 def __init__(self, stream, flush_before): | 44 def __init__(self, stream, flush_before): |
| 36 self.stream = stream | 45 self.stream = stream |
| 37 self.flush_before = flush_before | 46 self.flush_before = flush_before |
| 38 | 47 |
| 39 def emit(self, line): | 48 def emit(self, line): |
| (...skipping 366 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 406 | 415 |
| 407 # For error reporting. | 416 # For error reporting. |
| 408 step_dict = locals().copy() | 417 step_dict = locals().copy() |
| 409 step_dict.pop('kwargs') | 418 step_dict.pop('kwargs') |
| 410 step_dict.pop('stream') | 419 step_dict.pop('stream') |
| 411 step_dict.update(kwargs) | 420 step_dict.update(kwargs) |
| 412 | 421 |
| 413 for step_name in (seed_steps or []): | 422 for step_name in (seed_steps or []): |
| 414 stream.seed_step(step_name) | 423 stream.seed_step(step_name) |
| 415 | 424 |
| 416 filter_obj = None | |
| 417 if not allow_subannotations: | |
| 418 class AnnotationFilter(chromium_utils.RunCommandFilter): | |
| 419 # Missing __init__ | |
| 420 # Method could be a function (but not really since it's an override) | |
| 421 # pylint: disable=W0232,R0201 | |
| 422 def FilterLine(self, line): | |
| 423 return line.replace('@@@', '###') | |
| 424 filter_obj = AnnotationFilter() | |
| 425 | |
| 426 ret = None | 425 ret = None |
| 427 with stream.step(name) as s: | 426 with stream.step(name) as s: |
| 428 print_step(step_dict) | 427 print_step(step_dict) |
| 429 try: | 428 try: |
| 430 ret = chromium_utils.RunCommand(command=cmd, | 429 proc = subprocess.Popen( |
| 431 cwd=cwd, | 430 cmd, |
| 432 env=_merge_envs(os.environ, env), | 431 env=_merge_envs(os.environ, env), |
| 433 filter_obj=filter_obj, | 432 cwd=cwd, |
| 434 print_cmd=False) | 433 stdout=subprocess.PIPE, |
| 434 stderr=subprocess.PIPE) | |
| 435 | |
| 436 outlock = threading.Lock() | |
| 437 def filter_lines(lock, allow_subannotations, inhandle, outhandle): | |
| 438 while True: | |
| 439 line = inhandle.readline() | |
| 440 if not line: | |
| 441 break | |
| 442 if not allow_subannotations: | |
| 443 if line.startswith('@@@'): | |
| 444 line = '###'+line[3:] | |
|
agable
2013/06/25 15:55:28
This only replaces the leading @@@ with ###, where
iannucci
2013/06/25 21:22:09
I'll do you one better. I changed it to just print
| |
| 445 lock.acquire() | |
| 446 try: | |
| 447 outhandle.write(line) | |
| 448 outhandle.flush() | |
| 449 finally: | |
| 450 lock.release() | |
| 451 | |
| 452 outthread = threading.Thread( | |
| 453 target=filter_lines, | |
| 454 args=(outlock, allow_subannotations, proc.stdout, sys.stdout)) | |
| 455 errthread = threading.Thread( | |
| 456 target=filter_lines, | |
| 457 args=(outlock, allow_subannotations, proc.stderr, sys.stderr)) | |
| 458 outthread.start() | |
| 459 errthread.start() | |
| 460 proc.wait() | |
| 461 outthread.join() | |
| 462 errthread.join() | |
| 463 ret = proc.returncode | |
| 435 except OSError: | 464 except OSError: |
| 436 # File wasn't found, error will be reported to stream when the exception | 465 # File wasn't found, error will be reported to stream when the exception |
| 437 # crosses the context manager. | 466 # crosses the context manager. |
| 438 ret = -1 | 467 ret = -1 |
| 439 raise | 468 raise |
| 440 if ret > 0: | 469 if ret > 0: |
| 441 stream.step_cursor(stream.current_step) | 470 stream.step_cursor(stream.current_step) |
| 442 print 'step returned non-zero exit code: %d' % ret | 471 print 'step returned non-zero exit code: %d' % ret |
| 443 s.step_failure() | 472 s.step_failure() |
| 444 if followup_fn: | 473 if followup_fn: |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 482 usage = '%s <command list file or - for stdin>' % sys.argv[0] | 511 usage = '%s <command list file or - for stdin>' % sys.argv[0] |
| 483 parser = optparse.OptionParser(usage=usage) | 512 parser = optparse.OptionParser(usage=usage) |
| 484 _, args = parser.parse_args() | 513 _, args = parser.parse_args() |
| 485 if not args: | 514 if not args: |
| 486 parser.error('Must specify an input filename.') | 515 parser.error('Must specify an input filename.') |
| 487 if len(args) > 1: | 516 if len(args) > 1: |
| 488 parser.error('Too many arguments specified.') | 517 parser.error('Too many arguments specified.') |
| 489 | 518 |
| 490 steps = [] | 519 steps = [] |
| 491 | 520 |
| 521 def force_list_str(lst): | |
| 522 ret = [] | |
| 523 for v in lst: | |
| 524 if isinstance(v, basestring): | |
| 525 v = str(v) | |
|
agable
2013/06/25 15:55:28
this isn't very unicode() friendly :(
iannucci
2013/06/25 21:22:09
No, but we shouldn't have unicode in our annotator
| |
| 526 elif isinstance(v, list): | |
| 527 v = force_list_str(v) | |
| 528 ret.append(v) | |
| 529 return ret | |
| 530 | |
| 531 def force_dict_strs(obj): | |
| 532 ret = {} | |
| 533 for k, v in obj.iteritems(): | |
| 534 if isinstance(v, basestring): | |
| 535 v = str(v) | |
| 536 elif isinstance(v, list): | |
| 537 v = force_list_str(v) | |
| 538 ret[str(k)] = v | |
|
agable
2013/06/25 15:55:28
elif isinstance(v, dict):?
iannucci
2013/06/25 21:22:09
Oops. Fixed.
| |
| 539 return ret | |
| 540 | |
| 492 if args[0] == '-': | 541 if args[0] == '-': |
| 493 steps.extend(json.load(sys.stdin)) | 542 steps.extend(json.load(sys.stdin, object_hook=force_dict_strs)) |
| 494 else: | 543 else: |
| 495 with open(args[0], 'rb') as f: | 544 with open(args[0], 'rb') as f: |
| 496 steps.extend(json.load(f)) | 545 steps.extend(json.load(f, object_hook=force_dict_strs)) |
| 497 | 546 |
| 498 return 1 if run_steps(steps, False)[0] else 0 | 547 return 1 if run_steps(steps, False)[0] else 0 |
| 499 | 548 |
| 500 | 549 |
| 501 if __name__ == '__main__': | 550 if __name__ == '__main__': |
| 502 sys.exit(main()) | 551 sys.exit(main()) |
| OLD | NEW |