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

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

Issue 1001183002: Make allow_subannotations more robust. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Implementation of subannotations (with engine refactor) Created 5 years, 9 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
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 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
432 return engine.run(steps, api) 437 return engine.run(steps, api)
433 438
434 439
435 class RecipeEngine(object): 440 class RecipeEngine(object):
436 """Knows how to execute steps emitted by a recipe, holds global state such as 441 """Knows how to execute steps emitted by a recipe, holds global state such as
437 step history and build properties. Each recipe module API has a reference to 442 step history and build properties. Each recipe module API has a reference to
438 this object. 443 this object.
439 444
440 Recipe modules that are aware of the engine: 445 Recipe modules that are aware of the engine:
441 * properties - uses engine.properties. 446 * properties - uses engine.properties.
442 * step_history - uses engine.step_history.
443 * step - uses engine.create_step(...). 447 * step - uses engine.create_step(...).
444 448
445 This class acts mostly as a documentation of expected public engine interface. 449 This class acts mostly as a documentation of expected public engine interface.
446 """ 450 """
447 451
448 @staticmethod 452 @staticmethod
449 def create(stream, properties, test_data): 453 def create(stream, properties, test_data):
450 """Create a new instance of RecipeEngine based on 'engine' property.""" 454 """Create a new instance of RecipeEngine based on 'engine' property."""
451 engine_cls_name = properties.get('engine', 'SequentialRecipeEngine') 455 engine_cls_name = properties.get('engine', 'SequentialRecipeEngine')
452 for cls in RecipeEngine.__subclasses__(): 456 for cls in RecipeEngine.__subclasses__():
(...skipping 27 matching lines...) Expand all
480 Args: 484 Args:
481 step: ConfigGroup object with information about the step, see 485 step: ConfigGroup object with information about the step, see
482 recipe_modules/step/config.py. 486 recipe_modules/step/config.py.
483 487
484 Returns: 488 Returns:
485 Opaque engine specific object that is understood by 'run_steps' method. 489 Opaque engine specific object that is understood by 'run_steps' method.
486 """ 490 """
487 raise NotImplementedError 491 raise NotImplementedError
488 492
489 493
494 def _merge(*dicts):
495 result = {}
496 for d in dicts:
497 result.update(d)
498 return result
499
500
490 class SequentialRecipeEngine(RecipeEngine): 501 class SequentialRecipeEngine(RecipeEngine):
491 """Always runs step sequentially. Currently the engine used by default.""" 502 """Always runs step sequentially. Currently the engine used by default."""
492 def __init__(self, stream, properties, test_data): 503 def __init__(self, stream, properties, test_data):
493 super(SequentialRecipeEngine, self).__init__() 504 super(SequentialRecipeEngine, self).__init__()
494 self._stream = stream 505 self._stream = stream
495 self._properties = properties 506 self._properties = properties
496 self._test_data = test_data 507 self._test_data = test_data
497 self._step_history = collections.OrderedDict() 508 self._step_results = collections.OrderedDict()
509 self._step_disambiguation_index = {}
498 510
499 self._previous_step_annotation = None 511 self._annotation = None
500 self._previous_step_result = None 512 self._step_result = None
501 self._api = None 513 self._api = None
502 514
503 @property 515 @property
504 def properties(self): 516 def properties(self):
505 return self._properties 517 return self._properties
506 518
507 @property 519 @property
508 def previous_step_result(self): 520 def previous_step_result(self):
509 """Allows api.step to get the active result from any context.""" 521 """Allows api.step to get the active result from any context."""
510 return self._previous_step_result 522 return self._step_result
511 523
512 def _emit_results(self): 524 def _emit_results(self):
513 annotation = self._previous_step_annotation 525 annotation = self._annotation
514 step_result = self._previous_step_result 526 step_result = self._step_result
515 527
516 self._previous_step_annotation = None 528 self._annotation = None
517 self._previous_step_result = None 529 self._step_result = None
518 530
519 if not annotation or not step_result: 531 if not annotation or not step_result:
520 return 532 return
521 533
522 step_result.presentation.finalize(annotation) 534 step_result.presentation.finalize(annotation)
523 if self._test_data.enabled: 535 if self._test_data.enabled:
524 val = annotation.stream.getvalue() 536 val = annotation.stream.getvalue()
525 lines = filter(None, val.splitlines()) 537 lines = filter(None, val.splitlines())
526 if lines: 538 if lines:
527 # note that '~' sorts after 'z' so that this will be last on each 539 # note that '~' sorts after 'z' so that this will be last on each
528 # step. also use _step to get access to the mutable step 540 # step. also use _step to get access to the mutable step
529 # dictionary. 541 # dictionary.
530 # pylint: disable=w0212 542 # pylint: disable=w0212
531 step_result._step['~followup_annotations'] = lines 543 step_result._step['~followup_annotations'] = lines
532 annotation.step_ended() 544 annotation.step_ended()
533 545
534 def run_step(self, step): 546 def _disambiguate_name(self, step_name):
535 ok_ret = step.pop('ok_ret') 547 if step_name in self._step_disambiguation_index:
536 infra_step = step.pop('infra_step') 548 self._step_disambiguation_index[step_name] += 1
549 step_name += ' (%s)' % self._step_disambiguation_index[step_name]
550 else:
551 self._step_disambiguation_index[step_name] = 1
552 return step_name
537 553
538 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData) 554 def _disambiguate_step(self, step):
539 step_test = self._test_data.pop_step_test_data(step['name'], 555 """Disambiguates step (destructively) by adding an index afterward. E.g.
540 test_data_fn)
541 placeholders = render_step(step, step_test)
542 556
543 self._step_history[step['name']] = step 557 gclient sync
558 gclient sync (2)
559 ...
560 """
561 step['name'] = self._disambiguate_name(step['name'])
562
563 def _subannotator(self):
564 class Subannotator(object):
565 def BUILD_STEP(ann, name):
566 # TODO(luqui): disambiguate
567 self._open_step({'name': self._disambiguate_name(name)})
568
569 def STEP_WARNINGS(ann):
570 self._step_result.presentation.status = 'WARNING'
571 def STEP_FAILURE(ann):
572 self._step_result.presentation.status = 'FAILURE'
573 def STEP_EXCEPTION(ann):
574 self._step_result.presentation.status = 'EXCEPTION'
575
576 def STEP_TEXT(ann, msg):
577 self._step_result.presentation.step_text = msg
578
579 def STEP_LINK(ann, link_label, link_url):
580 self._step_result.presentation.links[link_label] = link_url
581
582 def STEP_LOG_LINE(ann, log_label, log_line):
583 self._step_result.presentation.logs[log_label] += log_line
584 def STEP_LOG_END(ann, log_label):
585 # We do step finalization all at once.
586 pass
587
588 def SET_BUILD_PROPERTY(ann, name, value):
589 self._step_result.presentation.properties[name] = value
590
591 def STEP_SUMMARY_TEXT(ann, msg):
592 self._step_result.presentation.step_summary_text = msg
593
594 return Subannotator()
595
596 def _open_step(self, step):
544 self._emit_results() 597 self._emit_results()
598 step_result = StepData(step, None)
599 self._step_results[step['name']] = step_result
600 self._step_result = step_result
601 self._annotation = self._stream.step(step['name'])
602 self._annotation.step_started()
603 if self._test_data.enabled:
604 self._annotation.stream = cStringIO.StringIO()
545 605
546 step_result = None 606 def _step_kernel(self, step, step_test, subannotator=None):
547
548 if not self._test_data.enabled: 607 if not self._test_data.enabled:
549 self._previous_step_annotation, retcode = annotator.run_step( 608 # Warning: run_step cah change the current self._annotation and
550 self._stream, **step) 609 # self._step_result if it uses a subannotator.
551 610 retcode = annotator.run_step(
552 step_result = StepData(step, retcode) 611 self._stream,
553 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) 612 step_annotation=self._annotation,
613 subannotator=subannotator,
614 **step)
615 self._step_result.retcode = retcode
616 # TODO(luqui): what is the purpose of this line?
617 self._annotation.annotation_stream.step_cursor(
618 self._step_result.step['name'])
554 else: 619 else:
555 self._previous_step_annotation = annotation = self._stream.step(
556 step['name'])
557 annotation.step_started()
558 try: 620 try:
559 annotation.stream = cStringIO.StringIO() 621 self._step_result.retcode = step_test.retcode
560
561 step_result = StepData(step, step_test.retcode)
562 except OSError: 622 except OSError:
563 exc_type, exc_value, exc_tb = sys.exc_info() 623 exc_type, exc_value, exc_tb = sys.exc_info()
564 trace = traceback.format_exception(exc_type, exc_value, exc_tb) 624 trace = traceback.format_exception(exc_type, exc_value, exc_tb)
565 trace_lines = ''.join(trace).split('\n') 625 trace_lines = ''.join(trace).split('\n')
566 annotation.write_log_lines('exception', filter(None, trace_lines)) 626 self._annotation.write_log_lines(
567 annotation.step_exception() 627 'exception', filter(None, trace_lines))
628 self._annotation.step_exception()
568 629
569 get_placeholder_results(step_result, placeholders) 630 return self._step_result.retcode
570 self._previous_step_result = step_result
571 631
572 if step_result.retcode in ok_ret: 632 def run_step(self, step):
573 step_result.presentation.status = 'SUCCESS' 633 self._disambiguate_step(step)
574 return step_result 634 ok_ret = step.pop('ok_ret')
635 infra_step = step.pop('infra_step')
636 allow_subannotations = step.get('allow_subannotations', False)
637
638 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
639 step_test = self._test_data.pop_step_test_data(step['name'], test_data_fn)
640 placeholders = render_step(step, step_test)
641
642 if allow_subannotations:
643 # TODO(luqui) make this hierarchical
644 start_step = _merge(step, { 'name': step['name'] + ' (start)' })
luqui 2015/03/23 23:26:32 Just emit name. Only emit (end) step if any suban
645 self._open_step(start_step)
646 retcode = self._step_kernel(start_step, step_test,
647 subannotator=self._subannotator())
648
649 # Open a closing step for presentation modifications.
650 self._open_step({ 'name': step['name'] + ' (end)' })
651 self._step_result.retcode = retcode
652 else:
653 self._open_step(step)
654 self._step_kernel(step, step_test)
655
656 get_placeholder_results(self._step_result, placeholders)
657
658 if self._step_result.retcode in ok_ret:
659 self._step_result.presentation.status = 'SUCCESS'
660 return self._step_result
575 else: 661 else:
576 if not infra_step: 662 if not infra_step:
577 state = 'FAILURE' 663 state = 'FAILURE'
578 exc = recipe_api.StepFailure 664 exc = recipe_api.StepFailure
579 else: 665 else:
580 state = 'EXCEPTION' 666 state = 'EXCEPTION'
581 exc = recipe_api.InfraFailure 667 exc = recipe_api.InfraFailure
582 668
583 step_result.presentation.status = state 669 self._step_result.presentation.status = state
584 if step_test.enabled: 670 if step_test.enabled:
585 # To avoid cluttering the expectations, don't emit this in testmode. 671 # To avoid cluttering the expectations, don't emit this in testmode.
586 self._previous_step_annotation.emit( 672 self._annotation.emit(
587 'step returned non-zero exit code: %d' % step_result.retcode) 673 'step returned non-zero exit code: %d' % self._step_result.retcode)
588 674
589 raise exc(step['name'], step_result) 675 raise exc(step['name'], self._step_result)
590 676
591 677
592 def run(self, steps_function, api): 678 def run(self, steps_function, api):
593 self._api = api 679 self._api = api
594 retcode = None 680 retcode = None
595 final_result = None 681 final_result = None
596 682
597 try: 683 try:
598 try: 684 try:
599 retcode = steps_function(api) 685 retcode = steps_function(api)
600 assert retcode is None, ( 686 assert retcode is None, (
601 "Non-None return from GenSteps is not supported yet") 687 'Non-None return from GenSteps is not supported yet')
602 688
603 assert not self._test_data.enabled or not self._test_data.step_data, ( 689 assert not self._test_data.enabled or not self._test_data.step_data, (
604 "Unconsumed test data! %s" % (self._test_data.step_data,)) 690 'Unconsumed test data! %s' % (self._test_data.step_data,))
605 finally: 691 finally:
606 self._emit_results() 692 self._emit_results()
607 except recipe_api.StepFailure as f: 693 except recipe_api.StepFailure as f:
608 retcode = f.retcode or 1 694 retcode = f.retcode or 1
609 final_result = { 695 final_result = {
610 "name": "$final_result", 696 'name': '$final_result',
611 "reason": f.reason, 697 'reason': f.reason,
612 "status_code": retcode 698 'status_code': retcode
613 } 699 }
614 700
615 except Exception as ex: 701 except Exception as ex:
616 unexpected_exception = self._test_data.is_unexpected_exception(ex) 702 unexpected_exception = self._test_data.is_unexpected_exception(ex)
617 703
618 retcode = -1 704 retcode = -1
619 final_result = { 705 final_result = {
620 "name": "$final_result", 706 'name': '$final_result',
621 "reason": "Uncaught Exception: %r" % ex, 707 'reason': 'Uncaught Exception: %r' % ex,
622 "status_code": retcode 708 'status_code': retcode
623 } 709 }
624 710
625 with self._stream.step('Uncaught Exception') as s: 711 with self._stream.step('Uncaught Exception') as s:
626 s.step_exception() 712 s.step_exception()
627 s.write_log_lines('exception', traceback.format_exc().splitlines()) 713 s.write_log_lines('exception', traceback.format_exc().splitlines())
628 714
629 if unexpected_exception: 715 if unexpected_exception:
630 raise 716 raise
631 717
632 if final_result is not None: 718 if final_result is not None:
633 self._step_history[final_result['name']] = final_result 719 self._step_results[final_result['name']] = (
720 StepData(final_result, final_result['status_code']))
634 721
635 return RecipeExecutionResult(retcode, self._step_history) 722 return RecipeExecutionResult(retcode, self._step_results)
636 723
637 def create_step(self, step): # pylint: disable=R0201 724 def create_step(self, step): # pylint: disable=R0201
638 # This version of engine doesn't do anything, just converts step to dict 725 # This version of engine doesn't do anything, just converts step to dict
639 # (that is consumed by annotator engine). 726 # (that is consumed by annotator engine).
640 return step.as_jsonish() 727 return step.as_jsonish()
641 728
642 729
643 class ParallelRecipeEngine(RecipeEngine): 730 class ParallelRecipeEngine(RecipeEngine):
644 """New engine that knows how to run steps in parallel. 731 """New engine that knows how to run steps in parallel.
645 732
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
697 784
698 def shell_main(argv): 785 def shell_main(argv):
699 if update_scripts(): 786 if update_scripts():
700 return subprocess.call([sys.executable] + argv) 787 return subprocess.call([sys.executable] + argv)
701 else: 788 else:
702 return main(argv) 789 return main(argv)
703 790
704 791
705 if __name__ == '__main__': 792 if __name__ == '__main__':
706 sys.exit(shell_main(sys.argv)) 793 sys.exit(shell_main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698