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

Side by Side Diff: scripts/slave/annotated_run.py

Issue 1076643002: Reland of 'Make allow_subannotations more robust' (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Fixed SET_BUILD_PROPERTY parse failure Created 5 years, 8 months 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 | Annotate | Revision Log
« no previous file with comments | « scripts/common/annotator.py ('k') | scripts/slave/recipe_modules/step/api.py » ('j') | 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) 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 """Entry point for fully-annotated builds. 6 """Entry point for fully-annotated builds.
7 7
8 This script is part of the effort to move all builds to annotator-based 8 This script is part of the effort to move all builds to annotator-based
9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory() 9 systems. Any builder configured to use the AnnotatorFactory.BaseFactory()
10 found in scripts/master/factory/annotator_factory.py executes a single 10 found in scripts/master/factory/annotator_factory.py executes a single
(...skipping 187 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 self.abort_reason = None 198 self.abort_reason = None
199 199
200 @property 200 @property
201 def step(self): 201 def step(self):
202 return copy.deepcopy(self._step) 202 return copy.deepcopy(self._step)
203 203
204 @property 204 @property
205 def retcode(self): 205 def retcode(self):
206 return self._retcode 206 return self._retcode
207 207
208 @retcode.setter
209 def retcode(self, val):
210 assert self._retcode is None, 'Can\'t override already-defined retcode'
211 self._retcode = val
212
208 @property 213 @property
209 def presentation(self): 214 def presentation(self):
210 return self._presentation 215 return self._presentation
211 216
212 # TODO(martiniss) update comment 217 # TODO(martiniss) update comment
213 # Result of 'render_step', fed into 'step_callback'. 218 # Result of 'render_step', fed into 'step_callback'.
214 Placeholders = collections.namedtuple( 219 Placeholders = collections.namedtuple(
215 'Placeholders', ['cmd', 'stdout', 'stderr', 'stdin']) 220 'Placeholders', ['cmd', 'stdout', 'stderr', 'stdin'])
216 221
217 222
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after
440 return engine.run(steps, api) 445 return engine.run(steps, api)
441 446
442 447
443 class RecipeEngine(object): 448 class RecipeEngine(object):
444 """Knows how to execute steps emitted by a recipe, holds global state such as 449 """Knows how to execute steps emitted by a recipe, holds global state such as
445 step history and build properties. Each recipe module API has a reference to 450 step history and build properties. Each recipe module API has a reference to
446 this object. 451 this object.
447 452
448 Recipe modules that are aware of the engine: 453 Recipe modules that are aware of the engine:
449 * properties - uses engine.properties. 454 * properties - uses engine.properties.
450 * step_history - uses engine.step_history.
451 * step - uses engine.create_step(...). 455 * step - uses engine.create_step(...).
452 456
453 This class acts mostly as a documentation of expected public engine interface. 457 This class acts mostly as a documentation of expected public engine interface.
454 """ 458 """
455 459
456 @staticmethod 460 @staticmethod
457 def create(stream, properties, test_data): 461 def create(stream, properties, test_data):
458 """Create a new instance of RecipeEngine based on 'engine' property.""" 462 """Create a new instance of RecipeEngine based on 'engine' property."""
459 engine_cls_name = properties.get('engine', 'SequentialRecipeEngine') 463 engine_cls_name = properties.get('engine', 'SequentialRecipeEngine')
460 for cls in RecipeEngine.__subclasses__(): 464 for cls in RecipeEngine.__subclasses__():
(...skipping 27 matching lines...) Expand all
488 Args: 492 Args:
489 step: ConfigGroup object with information about the step, see 493 step: ConfigGroup object with information about the step, see
490 recipe_modules/step/config.py. 494 recipe_modules/step/config.py.
491 495
492 Returns: 496 Returns:
493 Opaque engine specific object that is understood by 'run_steps' method. 497 Opaque engine specific object that is understood by 'run_steps' method.
494 """ 498 """
495 raise NotImplementedError 499 raise NotImplementedError
496 500
497 501
502 def _merge(*dicts):
503 result = {}
504 for d in dicts:
505 result.update(d)
506 return result
507
508
498 class SequentialRecipeEngine(RecipeEngine): 509 class SequentialRecipeEngine(RecipeEngine):
499 """Always runs step sequentially. Currently the engine used by default.""" 510 """Always runs step sequentially. Currently the engine used by default."""
500 def __init__(self, stream, properties, test_data): 511 def __init__(self, stream, properties, test_data):
501 super(SequentialRecipeEngine, self).__init__() 512 super(SequentialRecipeEngine, self).__init__()
502 self._stream = stream 513 self._stream = stream
503 self._properties = properties 514 self._properties = properties
504 self._test_data = test_data 515 self._test_data = test_data
505 self._step_history = collections.OrderedDict() 516 self._step_results = collections.OrderedDict()
517 self._step_disambiguation_index = {}
506 518
507 self._previous_step_annotation = None 519 self._annotation = None
508 self._previous_step_result = None 520 self._step_result = None
509 self._api = None 521 self._api = None
510 522
511 @property 523 @property
512 def properties(self): 524 def properties(self):
513 return self._properties 525 return self._properties
514 526
515 @property 527 @property
516 def previous_step_result(self): 528 def previous_step_result(self):
517 """Allows api.step to get the active result from any context.""" 529 """Allows api.step to get the active result from any context."""
518 return self._previous_step_result 530 return self._step_result
519 531
520 def _emit_results(self): 532 def _emit_results(self):
521 annotation = self._previous_step_annotation 533 annotation = self._annotation
522 step_result = self._previous_step_result 534 step_result = self._step_result
523 535
524 self._previous_step_annotation = None 536 self._annotation = None
525 self._previous_step_result = None 537 self._step_result = None
526 538
527 if not annotation or not step_result: 539 if not annotation or not step_result:
528 return 540 return
529 541
530 step_result.presentation.finalize(annotation) 542 step_result.presentation.finalize(annotation)
531 if self._test_data.enabled: 543 if self._test_data.enabled:
532 val = annotation.stream.getvalue() 544 val = annotation.stream.getvalue()
533 lines = filter(None, val.splitlines()) 545 lines = filter(None, val.splitlines())
534 if lines: 546 if lines:
535 # note that '~' sorts after 'z' so that this will be last on each 547 # note that '~' sorts after 'z' so that this will be last on each
536 # step. also use _step to get access to the mutable step 548 # step. also use _step to get access to the mutable step
537 # dictionary. 549 # dictionary.
538 # pylint: disable=w0212 550 # pylint: disable=w0212
539 step_result._step['~followup_annotations'] = lines 551 step_result._step['~followup_annotations'] = lines
540 annotation.step_ended() 552 annotation.step_ended()
541 553
542 def run_step(self, step): 554 def _disambiguate_name(self, step_name):
543 ok_ret = step.pop('ok_ret') 555 if step_name in self._step_disambiguation_index:
544 infra_step = step.pop('infra_step') 556 self._step_disambiguation_index[step_name] += 1
557 step_name += ' (%s)' % self._step_disambiguation_index[step_name]
558 else:
559 self._step_disambiguation_index[step_name] = 1
560 return step_name
545 561
546 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData) 562 def _disambiguate_step(self, step):
547 step_test = self._test_data.pop_step_test_data(step['name'], 563 """Disambiguates step (destructively) by adding an index afterward. E.g.
548 test_data_fn)
549 placeholders = render_step(step, step_test)
550 564
551 self._step_history[step['name']] = step 565 gclient sync
566 gclient sync (2)
567 ...
568 """
569 step['name'] = self._disambiguate_name(step['name'])
570
571 def _subannotator(self):
572 class Subannotator(object):
573 # We use ann as the self argument because we are closing over the
574 # SequentialRecipeEngine self.
575 # pylint: disable=e0213
576 def BUILD_STEP(ann, name):
577 self._open_step({'name': self._disambiguate_name(name)})
578
579 def STEP_WARNINGS(ann):
580 self._step_result.presentation.status = 'WARNING'
581 def STEP_FAILURE(ann):
582 self._step_result.presentation.status = 'FAILURE'
583 def STEP_EXCEPTION(ann):
584 self._step_result.presentation.status = 'EXCEPTION'
585
586 def STEP_TEXT(ann, msg):
587 self._step_result.presentation.step_text = msg
588
589 def STEP_LINK(ann, link_label, link_url):
590 self._step_result.presentation.links[link_label] = link_url
591
592 def STEP_LOG_LINE(ann, log_label, log_line):
593 self._step_result.presentation.logs[log_label] += log_line
594 def STEP_LOG_END(ann, log_label):
595 # We do step finalization all at once.
596 pass
597
598 def SET_BUILD_PROPERTY(ann, name, value):
599 self._step_result.presentation.properties[name] = json.loads(value)
600
601 def STEP_SUMMARY_TEXT(ann, msg):
602 self._step_result.presentation.step_summary_text = msg
603
604 return Subannotator()
605
606 def _open_step(self, step):
552 self._emit_results() 607 self._emit_results()
608 step_result = StepData(step, None)
609 self._step_results[step['name']] = step_result
610 self._step_result = step_result
611 self._annotation = self._stream.step(step['name'])
612 self._annotation.step_started()
613 if self._test_data.enabled:
614 self._annotation.stream = cStringIO.StringIO()
553 615
554 step_result = None 616 def _step_kernel(self, step, step_test, subannotator=None):
555
556 if not self._test_data.enabled: 617 if not self._test_data.enabled:
557 self._previous_step_annotation, retcode = annotator.run_step( 618 # Warning: run_step can change the current self._annotation and
558 self._stream, **step) 619 # self._step_result if it uses a subannotator.
559 620 retcode = annotator.run_step(
560 step_result = StepData(step, retcode) 621 self._stream,
561 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) 622 step_annotation=self._annotation,
623 subannotator=subannotator,
624 **step)
625 self._step_result.retcode = retcode
626 # TODO(luqui): What is the purpose of this line?
627 self._annotation.annotation_stream.step_cursor(
628 self._step_result.step['name'])
562 else: 629 else:
563 self._previous_step_annotation = annotation = self._stream.step(
564 step['name'])
565 annotation.step_started()
566 try: 630 try:
567 annotation.stream = cStringIO.StringIO() 631 self._step_result.retcode = step_test.retcode
568
569 step_result = StepData(step, step_test.retcode)
570 except OSError: 632 except OSError:
571 exc_type, exc_value, exc_tb = sys.exc_info() 633 exc_type, exc_value, exc_tb = sys.exc_info()
572 trace = traceback.format_exception(exc_type, exc_value, exc_tb) 634 trace = traceback.format_exception(exc_type, exc_value, exc_tb)
573 trace_lines = ''.join(trace).split('\n') 635 trace_lines = ''.join(trace).split('\n')
574 annotation.write_log_lines('exception', filter(None, trace_lines)) 636 self._annotation.write_log_lines(
575 annotation.step_exception() 637 'exception', filter(None, trace_lines))
638 self._annotation.step_exception()
576 639
577 get_placeholder_results(step_result, placeholders) 640 return self._step_result.retcode
578 self._previous_step_result = step_result
579 641
580 if step_result.retcode in ok_ret: 642 def run_step(self, step):
581 step_result.presentation.status = 'SUCCESS' 643 self._disambiguate_step(step)
582 return step_result 644 ok_ret = step.pop('ok_ret')
645 infra_step = step.pop('infra_step')
646 allow_subannotations = step.get('allow_subannotations', False)
647
648 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
649 step_test = self._test_data.pop_step_test_data(step['name'], test_data_fn)
650 placeholders = render_step(step, step_test)
651
652 if allow_subannotations:
653 # TODO(luqui) Make this hierarchical.
654 self._open_step(step)
655 start_annotation = self._annotation
656 retcode = self._step_kernel(step, step_test,
657 subannotator=self._subannotator())
658
659 # Open a closing step for presentation modifications.
660 if self._annotation != start_annotation:
661 self._open_step({ 'name': step['name'] + ' (end)' })
662 self._step_result.retcode = retcode
663 else:
664 self._open_step(step)
665 self._step_kernel(step, step_test)
666
667 get_placeholder_results(self._step_result, placeholders)
668
669 if self._step_result.retcode in ok_ret:
670 self._step_result.presentation.status = 'SUCCESS'
671 return self._step_result
583 else: 672 else:
584 if not infra_step: 673 if not infra_step:
585 state = 'FAILURE' 674 state = 'FAILURE'
586 exc = recipe_api.StepFailure 675 exc = recipe_api.StepFailure
587 else: 676 else:
588 state = 'EXCEPTION' 677 state = 'EXCEPTION'
589 exc = recipe_api.InfraFailure 678 exc = recipe_api.InfraFailure
590 679
591 step_result.presentation.status = state 680 self._step_result.presentation.status = state
592 if step_test.enabled: 681 if step_test.enabled:
593 # To avoid cluttering the expectations, don't emit this in testmode. 682 # To avoid cluttering the expectations, don't emit this in testmode.
594 self._previous_step_annotation.emit( 683 self._annotation.emit(
595 'step returned non-zero exit code: %d' % step_result.retcode) 684 'step returned non-zero exit code: %d' % self._step_result.retcode)
596 685
597 raise exc(step['name'], step_result) 686 raise exc(step['name'], self._step_result)
598 687
599 688
600 def run(self, steps_function, api): 689 def run(self, steps_function, api):
601 self._api = api 690 self._api = api
602 retcode = None 691 retcode = None
603 final_result = None 692 final_result = None
604 693
605 try: 694 try:
606 try: 695 try:
607 retcode = steps_function(api) 696 retcode = steps_function(api)
608 assert retcode is None, ( 697 assert retcode is None, (
609 "Non-None return from GenSteps is not supported yet") 698 'Non-None return from GenSteps is not supported yet')
610 699
611 assert not self._test_data.enabled or not self._test_data.step_data, ( 700 assert not self._test_data.enabled or not self._test_data.step_data, (
612 "Unconsumed test data! %s" % (self._test_data.step_data,)) 701 'Unconsumed test data! %s' % (self._test_data.step_data,))
613 finally: 702 finally:
614 self._emit_results() 703 self._emit_results()
615 except recipe_api.StepFailure as f: 704 except recipe_api.StepFailure as f:
616 retcode = f.retcode or 1 705 retcode = f.retcode or 1
617 final_result = { 706 final_result = {
618 "name": "$final_result", 707 'name': '$final_result',
619 "reason": f.reason, 708 'reason': f.reason,
620 "status_code": retcode 709 'status_code': retcode
621 } 710 }
622 711
623 except Exception as ex: 712 except Exception as ex:
624 unexpected_exception = self._test_data.is_unexpected_exception(ex) 713 unexpected_exception = self._test_data.is_unexpected_exception(ex)
625 714
626 retcode = -1 715 retcode = -1
627 final_result = { 716 final_result = {
628 "name": "$final_result", 717 'name': '$final_result',
629 "reason": "Uncaught Exception: %r" % ex, 718 'reason': 'Uncaught Exception: %r' % ex,
630 "status_code": retcode 719 'status_code': retcode
631 } 720 }
632 721
633 with self._stream.step('Uncaught Exception') as s: 722 with self._stream.step('Uncaught Exception') as s:
634 s.step_exception() 723 s.step_exception()
635 s.write_log_lines('exception', traceback.format_exc().splitlines()) 724 s.write_log_lines('exception', traceback.format_exc().splitlines())
636 725
637 if unexpected_exception: 726 if unexpected_exception:
638 raise 727 raise
639 728
640 if final_result is not None: 729 if final_result is not None:
641 self._step_history[final_result['name']] = final_result 730 self._step_results[final_result['name']] = (
731 StepData(final_result, final_result['status_code']))
642 732
643 return RecipeExecutionResult(retcode, self._step_history) 733 return RecipeExecutionResult(retcode, self._step_results)
644 734
645 def create_step(self, step): # pylint: disable=R0201 735 def create_step(self, step): # pylint: disable=R0201
646 # This version of engine doesn't do anything, just converts step to dict 736 # This version of engine doesn't do anything, just converts step to dict
647 # (that is consumed by annotator engine). 737 # (that is consumed by annotator engine).
648 return step.as_jsonish() 738 return step.as_jsonish()
649 739
650 740
651 class ParallelRecipeEngine(RecipeEngine): 741 class ParallelRecipeEngine(RecipeEngine):
652 """New engine that knows how to run steps in parallel. 742 """New engine that knows how to run steps in parallel.
653 743
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
705 795
706 def shell_main(argv): 796 def shell_main(argv):
707 if update_scripts(): 797 if update_scripts():
708 return subprocess.call([sys.executable] + argv) 798 return subprocess.call([sys.executable] + argv)
709 else: 799 else:
710 return main(argv) 800 return main(argv)
711 801
712 802
713 if __name__ == '__main__': 803 if __name__ == '__main__':
714 sys.exit(shell_main(sys.argv)) 804 sys.exit(shell_main(sys.argv))
OLDNEW
« no previous file with comments | « scripts/common/annotator.py ('k') | scripts/slave/recipe_modules/step/api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698