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

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: Only emit (end) when subannotations were actually emitted 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 # We use ann as the self argument because we are closing over the
566 # SequentialRecipeEngine self.
iannucci 2015/04/03 20:46:00 is that a good idea? Can we just hold a presentati
luqui 2015/04/07 23:26:54 Yeah I noticed that. The script I'm trying support
567 # pylint: disable=e0213
568 def BUILD_STEP(ann, name):
569 # TODO(luqui): disambiguate
iannucci 2015/04/03 20:46:00 still a todo?
luqui 2015/04/07 23:26:54 Done.
570 self._open_step({'name': self._disambiguate_name(name)})
571
572 def STEP_WARNINGS(ann):
573 self._step_result.presentation.status = 'WARNING'
574 def STEP_FAILURE(ann):
575 self._step_result.presentation.status = 'FAILURE'
576 def STEP_EXCEPTION(ann):
577 self._step_result.presentation.status = 'EXCEPTION'
578
579 def STEP_TEXT(ann, msg):
580 self._step_result.presentation.step_text = msg
581
582 def STEP_LINK(ann, link_label, link_url):
583 self._step_result.presentation.links[link_label] = link_url
584
585 def STEP_LOG_LINE(ann, log_label, log_line):
586 self._step_result.presentation.logs[log_label] += log_line
587 def STEP_LOG_END(ann, log_label):
588 # We do step finalization all at once.
589 pass
590
591 def SET_BUILD_PROPERTY(ann, name, value):
592 self._step_result.presentation.properties[name] = value
593
594 def STEP_SUMMARY_TEXT(ann, msg):
595 self._step_result.presentation.step_summary_text = msg
596
597 return Subannotator()
598
599 def _open_step(self, step):
544 self._emit_results() 600 self._emit_results()
601 step_result = StepData(step, None)
602 self._step_results[step['name']] = step_result
603 self._step_result = step_result
604 self._annotation = self._stream.step(step['name'])
605 self._annotation.step_started()
606 if self._test_data.enabled:
607 self._annotation.stream = cStringIO.StringIO()
545 608
546 step_result = None 609 def _step_kernel(self, step, step_test, subannotator=None):
547
548 if not self._test_data.enabled: 610 if not self._test_data.enabled:
549 self._previous_step_annotation, retcode = annotator.run_step( 611 # Warning: run_step cah change the current self._annotation and
iannucci 2015/04/03 20:46:00 typo cah?
luqui 2015/04/07 23:26:54 Done.
550 self._stream, **step) 612 # self._step_result if it uses a subannotator.
551 613 retcode = annotator.run_step(
552 step_result = StepData(step, retcode) 614 self._stream,
553 self._previous_step_annotation.annotation_stream.step_cursor(step['name']) 615 step_annotation=self._annotation,
616 subannotator=subannotator,
617 **step)
618 self._step_result.retcode = retcode
619 # TODO(luqui): What is the purpose of this line?
620 self._annotation.annotation_stream.step_cursor(
621 self._step_result.step['name'])
iannucci 2015/04/03 20:46:00 it was a bandaid to try to fix subannoations from
554 else: 622 else:
555 self._previous_step_annotation = annotation = self._stream.step(
556 step['name'])
557 annotation.step_started()
558 try: 623 try:
559 annotation.stream = cStringIO.StringIO() 624 self._step_result.retcode = step_test.retcode
560
561 step_result = StepData(step, step_test.retcode)
562 except OSError: 625 except OSError:
563 exc_type, exc_value, exc_tb = sys.exc_info() 626 exc_type, exc_value, exc_tb = sys.exc_info()
564 trace = traceback.format_exception(exc_type, exc_value, exc_tb) 627 trace = traceback.format_exception(exc_type, exc_value, exc_tb)
565 trace_lines = ''.join(trace).split('\n') 628 trace_lines = ''.join(trace).split('\n')
566 annotation.write_log_lines('exception', filter(None, trace_lines)) 629 self._annotation.write_log_lines(
567 annotation.step_exception() 630 'exception', filter(None, trace_lines))
631 self._annotation.step_exception()
568 632
569 get_placeholder_results(step_result, placeholders) 633 return self._step_result.retcode
570 self._previous_step_result = step_result
571 634
572 if step_result.retcode in ok_ret: 635 def run_step(self, step):
573 step_result.presentation.status = 'SUCCESS' 636 self._disambiguate_step(step)
574 return step_result 637 ok_ret = step.pop('ok_ret')
638 infra_step = step.pop('infra_step')
639 allow_subannotations = step.get('allow_subannotations', False)
640
641 test_data_fn = step.pop('step_test_data', recipe_test_api.StepTestData)
642 step_test = self._test_data.pop_step_test_data(step['name'], test_data_fn)
643 placeholders = render_step(step, step_test)
644
645 if allow_subannotations:
646 # TODO(luqui) Make this hierarchical.
647 self._open_step(step)
648 start_annotation = self._annotation
649 retcode = self._step_kernel(step, step_test,
650 subannotator=self._subannotator())
651
652 # Open a closing step for presentation modifications.
653 if self._annotation != start_annotation:
654 self._open_step({ 'name': step['name'] + ' (end)' })
655 self._step_result.retcode = retcode
656 else:
657 self._open_step(step)
658 self._step_kernel(step, step_test)
659
660 get_placeholder_results(self._step_result, placeholders)
661
662 if self._step_result.retcode in ok_ret:
663 self._step_result.presentation.status = 'SUCCESS'
664 return self._step_result
575 else: 665 else:
576 if not infra_step: 666 if not infra_step:
577 state = 'FAILURE' 667 state = 'FAILURE'
578 exc = recipe_api.StepFailure 668 exc = recipe_api.StepFailure
579 else: 669 else:
580 state = 'EXCEPTION' 670 state = 'EXCEPTION'
581 exc = recipe_api.InfraFailure 671 exc = recipe_api.InfraFailure
582 672
583 step_result.presentation.status = state 673 self._step_result.presentation.status = state
584 if step_test.enabled: 674 if step_test.enabled:
585 # To avoid cluttering the expectations, don't emit this in testmode. 675 # To avoid cluttering the expectations, don't emit this in testmode.
586 self._previous_step_annotation.emit( 676 self._annotation.emit(
587 'step returned non-zero exit code: %d' % step_result.retcode) 677 'step returned non-zero exit code: %d' % self._step_result.retcode)
588 678
589 raise exc(step['name'], step_result) 679 raise exc(step['name'], self._step_result)
590 680
591 681
592 def run(self, steps_function, api): 682 def run(self, steps_function, api):
593 self._api = api 683 self._api = api
594 retcode = None 684 retcode = None
595 final_result = None 685 final_result = None
596 686
597 try: 687 try:
598 try: 688 try:
599 retcode = steps_function(api) 689 retcode = steps_function(api)
600 assert retcode is None, ( 690 assert retcode is None, (
601 "Non-None return from GenSteps is not supported yet") 691 'Non-None return from GenSteps is not supported yet')
602 692
603 assert not self._test_data.enabled or not self._test_data.step_data, ( 693 assert not self._test_data.enabled or not self._test_data.step_data, (
604 "Unconsumed test data! %s" % (self._test_data.step_data,)) 694 'Unconsumed test data! %s' % (self._test_data.step_data,))
605 finally: 695 finally:
606 self._emit_results() 696 self._emit_results()
607 except recipe_api.StepFailure as f: 697 except recipe_api.StepFailure as f:
608 retcode = f.retcode or 1 698 retcode = f.retcode or 1
609 final_result = { 699 final_result = {
610 "name": "$final_result", 700 'name': '$final_result',
611 "reason": f.reason, 701 'reason': f.reason,
612 "status_code": retcode 702 'status_code': retcode
613 } 703 }
614 704
615 except Exception as ex: 705 except Exception as ex:
616 unexpected_exception = self._test_data.is_unexpected_exception(ex) 706 unexpected_exception = self._test_data.is_unexpected_exception(ex)
617 707
618 retcode = -1 708 retcode = -1
619 final_result = { 709 final_result = {
620 "name": "$final_result", 710 'name': '$final_result',
621 "reason": "Uncaught Exception: %r" % ex, 711 'reason': 'Uncaught Exception: %r' % ex,
622 "status_code": retcode 712 'status_code': retcode
623 } 713 }
624 714
625 with self._stream.step('Uncaught Exception') as s: 715 with self._stream.step('Uncaught Exception') as s:
626 s.step_exception() 716 s.step_exception()
627 s.write_log_lines('exception', traceback.format_exc().splitlines()) 717 s.write_log_lines('exception', traceback.format_exc().splitlines())
628 718
629 if unexpected_exception: 719 if unexpected_exception:
630 raise 720 raise
631 721
632 if final_result is not None: 722 if final_result is not None:
633 self._step_history[final_result['name']] = final_result 723 self._step_results[final_result['name']] = (
724 StepData(final_result, final_result['status_code']))
634 725
635 return RecipeExecutionResult(retcode, self._step_history) 726 return RecipeExecutionResult(retcode, self._step_results)
636 727
637 def create_step(self, step): # pylint: disable=R0201 728 def create_step(self, step): # pylint: disable=R0201
638 # This version of engine doesn't do anything, just converts step to dict 729 # This version of engine doesn't do anything, just converts step to dict
639 # (that is consumed by annotator engine). 730 # (that is consumed by annotator engine).
640 return step.as_jsonish() 731 return step.as_jsonish()
641 732
642 733
643 class ParallelRecipeEngine(RecipeEngine): 734 class ParallelRecipeEngine(RecipeEngine):
644 """New engine that knows how to run steps in parallel. 735 """New engine that knows how to run steps in parallel.
645 736
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
697 788
698 def shell_main(argv): 789 def shell_main(argv):
699 if update_scripts(): 790 if update_scripts():
700 return subprocess.call([sys.executable] + argv) 791 return subprocess.call([sys.executable] + argv)
701 else: 792 else:
702 return main(argv) 793 return main(argv)
703 794
704 795
705 if __name__ == '__main__': 796 if __name__ == '__main__':
706 sys.exit(shell_main(sys.argv)) 797 sys.exit(shell_main(sys.argv))
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698