OLD | NEW |
1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import os | 5 import os |
6 import StringIO | 6 import StringIO |
7 import sys | 7 import sys |
8 import unittest | 8 import unittest |
9 | 9 |
10 from catapult_base import cloud_storage # pylint: disable=import-error | 10 from catapult_base import cloud_storage # pylint: disable=import-error |
11 | 11 |
12 from telemetry import benchmark | 12 from telemetry import benchmark |
13 from telemetry.core import exceptions | 13 from telemetry.core import exceptions |
14 from telemetry.core import util | 14 from telemetry.core import util |
15 from telemetry import decorators | 15 from telemetry import decorators |
| 16 from telemetry.internal.actions import page_action |
| 17 from telemetry.internal.results import page_test_results |
16 from telemetry.internal.results import results_options | 18 from telemetry.internal.results import results_options |
17 from telemetry.internal import story_runner | 19 from telemetry.internal import story_runner |
18 from telemetry.internal.util import exception_formatter as ex_formatter_module | 20 from telemetry.internal.util import exception_formatter as ex_formatter_module |
19 from telemetry.page import page as page_module | 21 from telemetry.page import page as page_module |
20 from telemetry.page import legacy_page_test | 22 from telemetry.page import legacy_page_test |
21 from telemetry import story as story_module | 23 from telemetry import story as story_module |
22 from telemetry.testing import options_for_unittests | 24 from telemetry.testing import options_for_unittests |
23 from telemetry.testing import system_stub | 25 from telemetry.testing import system_stub |
24 import mock | 26 import mock |
| 27 from telemetry.value import failure |
25 from telemetry.value import improvement_direction | 28 from telemetry.value import improvement_direction |
26 from telemetry.value import list_of_scalar_values | 29 from telemetry.value import list_of_scalar_values |
27 from telemetry.value import scalar | 30 from telemetry.value import scalar |
| 31 from telemetry.value import skip |
28 from telemetry.value import summary as summary_module | 32 from telemetry.value import summary as summary_module |
29 from telemetry.web_perf import story_test | 33 from telemetry.web_perf import story_test |
30 from telemetry.web_perf import timeline_based_measurement | 34 from telemetry.web_perf import timeline_based_measurement |
31 from telemetry.wpr import archive_info | 35 from telemetry.wpr import archive_info |
32 | 36 |
33 # This linter complains if we define classes nested inside functions. | 37 # This linter complains if we define classes nested inside functions. |
34 # pylint: disable=bad-super-call | 38 # pylint: disable=bad-super-call |
35 | 39 |
36 | 40 |
37 class FakePlatform(object): | 41 class FakePlatform(object): |
(...skipping 27 matching lines...) Expand all Loading... |
65 | 69 |
66 def RunStory(self, results): | 70 def RunStory(self, results): |
67 raise NotImplementedError | 71 raise NotImplementedError |
68 | 72 |
69 def DidRunStory(self, results): | 73 def DidRunStory(self, results): |
70 pass | 74 pass |
71 | 75 |
72 def TearDownState(self): | 76 def TearDownState(self): |
73 pass | 77 pass |
74 | 78 |
| 79 def DumpStateUponFailure(self, story, results): |
| 80 pass |
| 81 |
75 | 82 |
76 class TestSharedPageState(TestSharedState): | 83 class TestSharedPageState(TestSharedState): |
77 def RunStory(self, results): | 84 def RunStory(self, results): |
78 self._test.RunPage(self._current_story, None, results) | 85 self._test.RunPage(self._current_story, None, results) |
79 | 86 |
80 | 87 |
81 class FooStoryState(TestSharedPageState): | 88 class FooStoryState(TestSharedPageState): |
82 pass | 89 pass |
83 | 90 |
84 | 91 |
(...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
144 | 151 |
145 | 152 |
146 def GetNumberOfSuccessfulPageRuns(results): | 153 def GetNumberOfSuccessfulPageRuns(results): |
147 return len([run for run in results.all_page_runs if run.ok or run.skipped]) | 154 return len([run for run in results.all_page_runs if run.ok or run.skipped]) |
148 | 155 |
149 | 156 |
150 class TestOnlyException(Exception): | 157 class TestOnlyException(Exception): |
151 pass | 158 pass |
152 | 159 |
153 | 160 |
| 161 class FailureValueMatcher(object): |
| 162 |
| 163 def __init__(self, expected_exception_message): |
| 164 self._expected_exception_message = expected_exception_message |
| 165 |
| 166 def __eq__(self, other): |
| 167 return (isinstance(other, failure.FailureValue) and |
| 168 other.exc_info[1].message == self._expected_exception_message) |
| 169 |
| 170 |
| 171 class SkipValueMatcher(object): |
| 172 |
| 173 def __eq__(self, other): |
| 174 return isinstance(other, skip.SkipValue) |
| 175 |
| 176 |
154 class StoryRunnerTest(unittest.TestCase): | 177 class StoryRunnerTest(unittest.TestCase): |
155 | 178 |
156 def setUp(self): | 179 def setUp(self): |
157 self.fake_stdout = StringIO.StringIO() | 180 self.fake_stdout = StringIO.StringIO() |
158 self.actual_stdout = sys.stdout | 181 self.actual_stdout = sys.stdout |
159 sys.stdout = self.fake_stdout | 182 sys.stdout = self.fake_stdout |
160 self.options = _GetOptionForUnittest() | 183 self.options = _GetOptionForUnittest() |
161 self.results = results_options.CreateResults( | 184 self.results = results_options.CreateResults( |
162 EmptyMetadataForTest(), self.options) | 185 EmptyMetadataForTest(), self.options) |
163 self._story_runner_logging_stub = None | 186 self._story_runner_logging_stub = None |
(...skipping 336 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
500 | 523 |
501 unit_test_events = [] # track what was called when | 524 unit_test_events = [] # track what was called when |
502 class DidRunTestError(Exception): | 525 class DidRunTestError(Exception): |
503 pass | 526 pass |
504 | 527 |
505 class TestTearDownSharedState(TestSharedPageState): | 528 class TestTearDownSharedState(TestSharedPageState): |
506 def TearDownState(self): | 529 def TearDownState(self): |
507 unit_test_events.append('tear-down-state') | 530 unit_test_events.append('tear-down-state') |
508 raise DidRunTestError | 531 raise DidRunTestError |
509 | 532 |
| 533 def DumpStateUponFailure(self, story, results): |
| 534 unit_test_events.append('dump-state') |
| 535 |
510 | 536 |
511 class Test(legacy_page_test.LegacyPageTest): | 537 class Test(legacy_page_test.LegacyPageTest): |
512 def __init__(self, *args): | 538 def __init__(self, *args): |
513 super(Test, self).__init__(*args) | 539 super(Test, self).__init__(*args) |
514 self.run_count = 0 | 540 self.run_count = 0 |
515 | 541 |
516 def RunPage(self, *_): | 542 def RunPage(self, *_): |
517 old_run_count = self.run_count | 543 old_run_count = self.run_count |
518 self.run_count += 1 | 544 self.run_count += 1 |
519 if old_run_count == 0: | 545 if old_run_count == 0: |
520 unit_test_events.append('app-crash') | 546 unit_test_events.append('app-crash') |
521 raise exceptions.AppCrashException | 547 raise exceptions.AppCrashException |
522 | 548 |
523 def ValidateAndMeasurePage(self, page, tab, results): | 549 def ValidateAndMeasurePage(self, page, tab, results): |
524 pass | 550 pass |
525 | 551 |
526 story_set.AddStory(DummyLocalStory(TestTearDownSharedState)) | 552 story_set.AddStory(DummyLocalStory(TestTearDownSharedState)) |
527 story_set.AddStory(DummyLocalStory(TestTearDownSharedState)) | 553 story_set.AddStory(DummyLocalStory(TestTearDownSharedState)) |
528 test = Test() | 554 test = Test() |
529 | 555 |
530 with self.assertRaises(DidRunTestError): | 556 with self.assertRaises(DidRunTestError): |
531 story_runner.Run( | 557 story_runner.Run( |
532 test, story_set, self.options, self.results) | 558 test, story_set, self.options, self.results) |
533 self.assertEqual(['app-crash', 'tear-down-state'], unit_test_events) | 559 self.assertEqual(['app-crash', 'dump-state', 'tear-down-state'], |
| 560 unit_test_events) |
534 # The AppCrashException gets added as a failure. | 561 # The AppCrashException gets added as a failure. |
535 self.assertEquals(1, len(self.results.failures)) | 562 self.assertEquals(1, len(self.results.failures)) |
536 | 563 |
537 def testPagesetRepeat(self): | 564 def testPagesetRepeat(self): |
538 story_set = story_module.StorySet() | 565 story_set = story_module.StorySet() |
539 | 566 |
540 # TODO(eakuefner): Factor this out after flattening page ref in Value | 567 # TODO(eakuefner): Factor this out after flattening page ref in Value |
541 blank_story = DummyLocalStory(TestSharedPageState, name='blank') | 568 blank_story = DummyLocalStory(TestSharedPageState, name='blank') |
542 green_story = DummyLocalStory(TestSharedPageState, name='green') | 569 green_story = DummyLocalStory(TestSharedPageState, name='green') |
543 story_set.AddStory(blank_story) | 570 story_set.AddStory(blank_story) |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
671 | 698 |
672 def DidRunStory(self, results): | 699 def DidRunStory(self, results): |
673 pass | 700 pass |
674 | 701 |
675 def CanRunStory(self, story): | 702 def CanRunStory(self, story): |
676 return True | 703 return True |
677 | 704 |
678 def TearDownState(self): | 705 def TearDownState(self): |
679 pass | 706 pass |
680 | 707 |
| 708 def DumpStateUponFailure(self, story, results): |
| 709 pass |
| 710 |
681 class FailingStory(story_module.Story): | 711 class FailingStory(story_module.Story): |
682 def __init__(self): | 712 def __init__(self): |
683 super(FailingStory, self).__init__( | 713 super(FailingStory, self).__init__( |
684 shared_state_class=SimpleSharedState, | 714 shared_state_class=SimpleSharedState, |
685 is_local=True) | 715 is_local=True) |
686 self.was_run = False | 716 self.was_run = False |
687 | 717 |
688 def Run(self, shared_state): | 718 def Run(self, shared_state): |
689 self.was_run = True | 719 self.was_run = True |
690 raise legacy_page_test.Failure | 720 raise legacy_page_test.Failure |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
723 num_failing_stories=5, runner_max_failures=3, | 753 num_failing_stories=5, runner_max_failures=3, |
724 options_max_failures=None, expected_num_failures=4) | 754 options_max_failures=None, expected_num_failures=4) |
725 | 755 |
726 def testMaxFailuresOption(self): | 756 def testMaxFailuresOption(self): |
727 # Runs up to max_failures+1 failing tests before stopping, since | 757 # Runs up to max_failures+1 failing tests before stopping, since |
728 # every tests after max_failures failures have been encountered | 758 # every tests after max_failures failures have been encountered |
729 # may all be passing. | 759 # may all be passing. |
730 self._testMaxFailuresOptionIsRespectedAndOverridable( | 760 self._testMaxFailuresOptionIsRespectedAndOverridable( |
731 num_failing_stories=5, runner_max_failures=3, | 761 num_failing_stories=5, runner_max_failures=3, |
732 options_max_failures=1, expected_num_failures=2) | 762 options_max_failures=1, expected_num_failures=2) |
| 763 |
| 764 def _CreateErrorProcessingMock(self, method_exceptions=None, |
| 765 legacy_test=False): |
| 766 if legacy_test: |
| 767 test_class = legacy_page_test.LegacyPageTest |
| 768 else: |
| 769 test_class = story_test.StoryTest |
| 770 |
| 771 root_mock = mock.NonCallableMock( |
| 772 story=mock.NonCallableMagicMock(story_module.Story), |
| 773 results=mock.NonCallableMagicMock(page_test_results.PageTestResults), |
| 774 test=mock.NonCallableMagicMock(test_class), |
| 775 state=mock.NonCallableMagicMock( |
| 776 story_module.SharedState, |
| 777 CanRunStory=mock.Mock(return_value=True))) |
| 778 |
| 779 if method_exceptions: |
| 780 root_mock.configure_mock(**{ |
| 781 path + '.side_effect': exception |
| 782 for path, exception in method_exceptions.iteritems()}) |
| 783 |
| 784 return root_mock |
| 785 |
| 786 def testRunStoryAndProcessErrorIfNeeded_success(self): |
| 787 root_mock = self._CreateErrorProcessingMock() |
| 788 |
| 789 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 790 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 791 |
| 792 self.assertEquals(root_mock.method_calls, [ |
| 793 mock.call.test.WillRunStory(root_mock.state.platform), |
| 794 mock.call.state.WillRunStory(root_mock.story), |
| 795 mock.call.state.CanRunStory(root_mock.story), |
| 796 mock.call.state.RunStory(root_mock.results), |
| 797 mock.call.test.Measure(root_mock.state.platform, root_mock.results), |
| 798 mock.call.state.DidRunStory(root_mock.results), |
| 799 mock.call.test.DidRunStory(root_mock.state.platform) |
| 800 ]) |
| 801 |
| 802 def testRunStoryAndProcessErrorIfNeeded_successLegacy(self): |
| 803 root_mock = self._CreateErrorProcessingMock(legacy_test=True) |
| 804 |
| 805 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 806 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 807 |
| 808 self.assertEquals(root_mock.method_calls, [ |
| 809 mock.call.state.WillRunStory(root_mock.story), |
| 810 mock.call.state.CanRunStory(root_mock.story), |
| 811 mock.call.state.RunStory(root_mock.results), |
| 812 mock.call.state.DidRunStory(root_mock.results), |
| 813 mock.call.test.DidRunPage(root_mock.state.platform) |
| 814 ]) |
| 815 |
| 816 def testRunStoryAndProcessErrorIfNeeded_tryTimeout(self): |
| 817 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 818 'state.WillRunStory': exceptions.TimeoutException('foo') |
| 819 }) |
| 820 |
| 821 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 822 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 823 |
| 824 self.assertEquals(root_mock.method_calls, [ |
| 825 mock.call.test.WillRunStory(root_mock.state.platform), |
| 826 mock.call.state.WillRunStory(root_mock.story), |
| 827 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 828 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 829 mock.call.state.DidRunStory(root_mock.results), |
| 830 mock.call.test.DidRunStory(root_mock.state.platform) |
| 831 ]) |
| 832 |
| 833 def testRunStoryAndProcessErrorIfNeeded_tryError(self): |
| 834 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 835 'state.CanRunStory': exceptions.Error('foo') |
| 836 }) |
| 837 |
| 838 with self.assertRaisesRegexp(exceptions.Error, 'foo'): |
| 839 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 840 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 841 |
| 842 self.assertEquals(root_mock.method_calls, [ |
| 843 mock.call.test.WillRunStory(root_mock.state.platform), |
| 844 mock.call.state.WillRunStory(root_mock.story), |
| 845 mock.call.state.CanRunStory(root_mock.story), |
| 846 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 847 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 848 mock.call.state.DidRunStory(root_mock.results), |
| 849 mock.call.test.DidRunStory(root_mock.state.platform) |
| 850 ]) |
| 851 |
| 852 def testRunStoryAndProcessErrorIfNeeded_tryUnsupportedAction(self): |
| 853 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 854 'state.RunStory': page_action.PageActionNotSupported('foo') |
| 855 }) |
| 856 |
| 857 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 858 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 859 |
| 860 self.assertEquals(root_mock.method_calls, [ |
| 861 mock.call.test.WillRunStory(root_mock.state.platform), |
| 862 mock.call.state.WillRunStory(root_mock.story), |
| 863 mock.call.state.CanRunStory(root_mock.story), |
| 864 mock.call.state.RunStory(root_mock.results), |
| 865 mock.call.results.AddValue(SkipValueMatcher()), |
| 866 mock.call.state.DidRunStory(root_mock.results), |
| 867 mock.call.test.DidRunStory(root_mock.state.platform) |
| 868 ]) |
| 869 |
| 870 def testRunStoryAndProcessErrorIfNeeded_tryUnhandlable(self): |
| 871 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 872 'test.WillRunStory': Exception('foo') |
| 873 }) |
| 874 |
| 875 with self.assertRaisesRegexp(Exception, 'foo'): |
| 876 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 877 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 878 |
| 879 self.assertEquals(root_mock.method_calls, [ |
| 880 mock.call.test.WillRunStory(root_mock.state.platform), |
| 881 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 882 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 883 mock.call.state.DidRunStory(root_mock.results), |
| 884 mock.call.test.DidRunStory(root_mock.state.platform) |
| 885 ]) |
| 886 |
| 887 def testRunStoryAndProcessErrorIfNeeded_finallyException(self): |
| 888 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 889 'state.DidRunStory': Exception('bar') |
| 890 }) |
| 891 |
| 892 with self.assertRaisesRegexp(Exception, 'bar'): |
| 893 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 894 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 895 |
| 896 self.assertEquals(root_mock.method_calls, [ |
| 897 mock.call.test.WillRunStory(root_mock.state.platform), |
| 898 mock.call.state.WillRunStory(root_mock.story), |
| 899 mock.call.state.CanRunStory(root_mock.story), |
| 900 mock.call.state.RunStory(root_mock.results), |
| 901 mock.call.test.Measure(root_mock.state.platform, root_mock.results), |
| 902 mock.call.state.DidRunStory(root_mock.results), |
| 903 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results) |
| 904 ]) |
| 905 |
| 906 def testRunStoryAndProcessErrorIfNeeded_tryTimeout_finallyException(self): |
| 907 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 908 'state.RunStory': exceptions.TimeoutException('foo'), |
| 909 'state.DidRunStory': Exception('bar') |
| 910 }) |
| 911 |
| 912 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 913 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 914 |
| 915 self.assertEquals(root_mock.method_calls, [ |
| 916 mock.call.test.WillRunStory(root_mock.state.platform), |
| 917 mock.call.state.WillRunStory(root_mock.story), |
| 918 mock.call.state.CanRunStory(root_mock.story), |
| 919 mock.call.state.RunStory(root_mock.results), |
| 920 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 921 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 922 mock.call.state.DidRunStory(root_mock.results) |
| 923 ]) |
| 924 |
| 925 def testRunStoryAndProcessErrorIfNeeded_tryError_finallyException(self): |
| 926 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 927 'state.WillRunStory': exceptions.Error('foo'), |
| 928 'test.DidRunStory': Exception('bar') |
| 929 }) |
| 930 |
| 931 with self.assertRaisesRegexp(exceptions.Error, 'foo'): |
| 932 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 933 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 934 |
| 935 self.assertEquals(root_mock.method_calls, [ |
| 936 mock.call.test.WillRunStory(root_mock.state.platform), |
| 937 mock.call.state.WillRunStory(root_mock.story), |
| 938 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 939 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 940 mock.call.state.DidRunStory(root_mock.results), |
| 941 mock.call.test.DidRunStory(root_mock.state.platform) |
| 942 ]) |
| 943 |
| 944 def testRunStoryAndProcessErrorIfNeeded_tryUnsupportedAction_finallyException( |
| 945 self): |
| 946 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 947 'test.WillRunStory': page_action.PageActionNotSupported('foo'), |
| 948 'state.DidRunStory': Exception('bar') |
| 949 }) |
| 950 |
| 951 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 952 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 953 |
| 954 self.assertEquals(root_mock.method_calls, [ |
| 955 mock.call.test.WillRunStory(root_mock.state.platform), |
| 956 mock.call.results.AddValue(SkipValueMatcher()), |
| 957 mock.call.state.DidRunStory(root_mock.results) |
| 958 ]) |
| 959 |
| 960 def testRunStoryAndProcessErrorIfNeeded_tryUnhandlable_finallyException(self): |
| 961 root_mock = self._CreateErrorProcessingMock(method_exceptions={ |
| 962 'test.Measure': Exception('foo'), |
| 963 'test.DidRunStory': Exception('bar') |
| 964 }) |
| 965 |
| 966 with self.assertRaisesRegexp(Exception, 'foo'): |
| 967 story_runner._RunStoryAndProcessErrorIfNeeded( |
| 968 root_mock.story, root_mock.results, root_mock.state, root_mock.test) |
| 969 |
| 970 self.assertEquals(root_mock.method_calls, [ |
| 971 mock.call.test.WillRunStory(root_mock.state.platform), |
| 972 mock.call.state.WillRunStory(root_mock.story), |
| 973 mock.call.state.CanRunStory(root_mock.story), |
| 974 mock.call.state.RunStory(root_mock.results), |
| 975 mock.call.test.Measure(root_mock.state.platform, root_mock.results), |
| 976 mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results), |
| 977 mock.call.results.AddValue(FailureValueMatcher('foo')), |
| 978 mock.call.state.DidRunStory(root_mock.results), |
| 979 mock.call.test.DidRunStory(root_mock.state.platform) |
| 980 ]) |
OLD | NEW |