Home | History | Annotate | Download | only in internal
      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
      3 # found in the LICENSE file.
      4 
      5 import math
      6 import os
      7 import StringIO
      8 import sys
      9 import unittest
     10 
     11 from py_utils import cloud_storage  # pylint: disable=import-error
     12 
     13 from telemetry import benchmark
     14 from telemetry.core import exceptions
     15 from telemetry.core import util
     16 from telemetry import decorators
     17 from telemetry.internal.actions import page_action
     18 from telemetry.internal.results import page_test_results
     19 from telemetry.internal.results import results_options
     20 from telemetry.internal import story_runner
     21 from telemetry.internal.util import exception_formatter as ex_formatter_module
     22 from telemetry.page import page as page_module
     23 from telemetry.page import legacy_page_test
     24 from telemetry import story as story_module
     25 from telemetry.testing import options_for_unittests
     26 from telemetry.testing import system_stub
     27 import mock
     28 from telemetry.value import failure
     29 from telemetry.value import improvement_direction
     30 from telemetry.value import list_of_scalar_values
     31 from telemetry.value import scalar
     32 from telemetry.value import skip
     33 from telemetry.value import summary as summary_module
     34 from telemetry.web_perf import story_test
     35 from telemetry.web_perf import timeline_based_measurement
     36 from telemetry.wpr import archive_info
     37 
     38 # This linter complains if we define classes nested inside functions.
     39 # pylint: disable=bad-super-call
     40 
     41 
     42 class FakePlatform(object):
     43   def CanMonitorThermalThrottling(self):
     44     return False
     45 
     46 
     47 class TestSharedState(story_module.SharedState):
     48 
     49   _platform = FakePlatform()
     50 
     51   @classmethod
     52   def SetTestPlatform(cls, platform):
     53     cls._platform = platform
     54 
     55   def __init__(self, test, options, story_set):
     56     super(TestSharedState, self).__init__(
     57         test, options, story_set)
     58     self._test = test
     59     self._current_story = None
     60 
     61   @property
     62   def platform(self):
     63     return self._platform
     64 
     65   def WillRunStory(self, story):
     66     self._current_story = story
     67 
     68   def CanRunStory(self, story):
     69     return True
     70 
     71   def RunStory(self, results):
     72     raise NotImplementedError
     73 
     74   def DidRunStory(self, results):
     75     pass
     76 
     77   def TearDownState(self):
     78     pass
     79 
     80   def DumpStateUponFailure(self, story, results):
     81     pass
     82 
     83 
     84 class TestSharedPageState(TestSharedState):
     85   def RunStory(self, results):
     86     self._test.RunPage(self._current_story, None, results)
     87 
     88 
     89 class FooStoryState(TestSharedPageState):
     90   pass
     91 
     92 
     93 class BarStoryState(TestSharedPageState):
     94   pass
     95 
     96 
     97 class DummyTest(legacy_page_test.LegacyPageTest):
     98   def RunPage(self, *_):
     99     pass
    100 
    101   def ValidateAndMeasurePage(self, page, tab, results):
    102     pass
    103 
    104 
    105 class EmptyMetadataForTest(benchmark.BenchmarkMetadata):
    106   def __init__(self):
    107     super(EmptyMetadataForTest, self).__init__('')
    108 
    109 
    110 class DummyLocalStory(story_module.Story):
    111   def __init__(self, shared_state_class, name=''):
    112     super(DummyLocalStory, self).__init__(
    113         shared_state_class, name=name)
    114 
    115   def Run(self, shared_state):
    116     pass
    117 
    118   @property
    119   def is_local(self):
    120     return True
    121 
    122   @property
    123   def url(self):
    124     return 'data:,'
    125 
    126 
    127 class MixedStateStorySet(story_module.StorySet):
    128   @property
    129   def allow_mixed_story_states(self):
    130     return True
    131 
    132 def SetupStorySet(allow_multiple_story_states, story_state_list):
    133   if allow_multiple_story_states:
    134     story_set = MixedStateStorySet()
    135   else:
    136     story_set = story_module.StorySet()
    137   for story_state in story_state_list:
    138     story_set.AddStory(DummyLocalStory(story_state))
    139   return story_set
    140 
    141 def _GetOptionForUnittest():
    142   options = options_for_unittests.GetCopy()
    143   options.output_formats = ['none']
    144   options.suppress_gtest_report = False
    145   parser = options.CreateParser()
    146   story_runner.AddCommandLineArgs(parser)
    147   options.MergeDefaultValues(parser.get_default_values())
    148   story_runner.ProcessCommandLineArgs(parser, options)
    149   return options
    150 
    151 
    152 class FakeExceptionFormatterModule(object):
    153   @staticmethod
    154   def PrintFormattedException(
    155       exception_class=None, exception=None, tb=None, msg=None):
    156     pass
    157 
    158 
    159 def GetNumberOfSuccessfulPageRuns(results):
    160   return len([run for run in results.all_page_runs if run.ok or run.skipped])
    161 
    162 
    163 class TestOnlyException(Exception):
    164   pass
    165 
    166 
    167 class FailureValueMatcher(object):
    168 
    169   def __init__(self, expected_exception_message):
    170     self._expected_exception_message = expected_exception_message
    171 
    172   def __eq__(self, other):
    173     return (isinstance(other, failure.FailureValue) and
    174             other.exc_info[1].message == self._expected_exception_message)
    175 
    176 
    177 class SkipValueMatcher(object):
    178 
    179   def __eq__(self, other):
    180     return isinstance(other, skip.SkipValue)
    181 
    182 
    183 class StoryRunnerTest(unittest.TestCase):
    184 
    185   def setUp(self):
    186     self.fake_stdout = StringIO.StringIO()
    187     self.actual_stdout = sys.stdout
    188     sys.stdout = self.fake_stdout
    189     self.options = _GetOptionForUnittest()
    190     self.results = results_options.CreateResults(
    191         EmptyMetadataForTest(), self.options)
    192     self._story_runner_logging_stub = None
    193 
    194   def SuppressExceptionFormatting(self):
    195     """Fake out exception formatter to avoid spamming the unittest stdout."""
    196     story_runner.exception_formatter = FakeExceptionFormatterModule
    197     self._story_runner_logging_stub = system_stub.Override(
    198       story_runner, ['logging'])
    199 
    200   def RestoreExceptionFormatter(self):
    201     story_runner.exception_formatter = ex_formatter_module
    202     if self._story_runner_logging_stub:
    203       self._story_runner_logging_stub.Restore()
    204       self._story_runner_logging_stub = None
    205 
    206   def tearDown(self):
    207     sys.stdout = self.actual_stdout
    208     self.RestoreExceptionFormatter()
    209 
    210   def testStoriesGroupedByStateClass(self):
    211     foo_states = [FooStoryState, FooStoryState, FooStoryState,
    212                   FooStoryState, FooStoryState]
    213     mixed_states = [FooStoryState, FooStoryState, FooStoryState,
    214                     BarStoryState, FooStoryState]
    215     # StorySet's are only allowed to have one SharedState.
    216     story_set = SetupStorySet(False, foo_states)
    217     story_groups = (
    218         story_runner.StoriesGroupedByStateClass(
    219             story_set, False))
    220     self.assertEqual(len(story_groups), 1)
    221     story_set = SetupStorySet(False, mixed_states)
    222     self.assertRaises(
    223         ValueError,
    224         story_runner.StoriesGroupedByStateClass,
    225         story_set, False)
    226     # BaseStorySets are allowed to have multiple SharedStates.
    227     mixed_story_set = SetupStorySet(True, mixed_states)
    228     story_groups = (
    229         story_runner.StoriesGroupedByStateClass(
    230             mixed_story_set, True))
    231     self.assertEqual(len(story_groups), 3)
    232     self.assertEqual(story_groups[0].shared_state_class,
    233                      FooStoryState)
    234     self.assertEqual(story_groups[1].shared_state_class,
    235                      BarStoryState)
    236     self.assertEqual(story_groups[2].shared_state_class,
    237                      FooStoryState)
    238 
    239   def RunStoryTest(self, s, expected_successes):
    240     test = DummyTest()
    241     story_runner.Run(
    242         test, s, self.options, self.results)
    243     self.assertEquals(0, len(self.results.failures))
    244     self.assertEquals(expected_successes,
    245                       GetNumberOfSuccessfulPageRuns(self.results))
    246 
    247   def testRunStoryWithMissingArchiveFile(self):
    248     story_set = story_module.StorySet(archive_data_file='data/hi.json')
    249     story_set.AddStory(page_module.Page(
    250         'http://www.testurl.com', story_set, story_set.base_dir))
    251     test = DummyTest()
    252     self.assertRaises(story_runner.ArchiveError, story_runner.Run, test,
    253                       story_set, self.options, self.results)
    254 
    255   def testStoryTest(self):
    256     all_foo = [FooStoryState, FooStoryState, FooStoryState]
    257     one_bar = [FooStoryState, FooStoryState, BarStoryState]
    258     story_set = SetupStorySet(True, one_bar)
    259     self.RunStoryTest(story_set, 3)
    260     story_set = SetupStorySet(True, all_foo)
    261     self.RunStoryTest(story_set, 6)
    262     story_set = SetupStorySet(False, all_foo)
    263     self.RunStoryTest(story_set, 9)
    264     story_set = SetupStorySet(False, one_bar)
    265     test = DummyTest()
    266     self.assertRaises(ValueError, story_runner.Run, test, story_set,
    267                       self.options, self.results)
    268 
    269   def testSuccessfulTimelineBasedMeasurementTest(self):
    270     """Check that PageTest is not required for story_runner.Run.
    271 
    272     Any PageTest related calls or attributes need to only be called
    273     for PageTest tests.
    274     """
    275     class TestSharedTbmState(TestSharedState):
    276       def RunStory(self, results):
    277         pass
    278 
    279     TEST_WILL_RUN_STORY = 'test.WillRunStory'
    280     TEST_MEASURE = 'test.Measure'
    281     TEST_DID_RUN_STORY = 'test.DidRunStory'
    282 
    283     EXPECTED_CALLS_IN_ORDER = [TEST_WILL_RUN_STORY,
    284                                TEST_MEASURE,
    285                                TEST_DID_RUN_STORY]
    286 
    287     test = timeline_based_measurement.TimelineBasedMeasurement(
    288         timeline_based_measurement.Options())
    289 
    290     manager = mock.MagicMock()
    291     test.WillRunStory = mock.MagicMock()
    292     test.Measure = mock.MagicMock()
    293     test.DidRunStory = mock.MagicMock()
    294     manager.attach_mock(test.WillRunStory, TEST_WILL_RUN_STORY)
    295     manager.attach_mock(test.Measure, TEST_MEASURE)
    296     manager.attach_mock(test.DidRunStory, TEST_DID_RUN_STORY)
    297 
    298     story_set = story_module.StorySet()
    299     story_set.AddStory(DummyLocalStory(TestSharedTbmState))
    300     story_set.AddStory(DummyLocalStory(TestSharedTbmState))
    301     story_set.AddStory(DummyLocalStory(TestSharedTbmState))
    302     story_runner.Run(
    303         test, story_set, self.options, self.results)
    304     self.assertEquals(0, len(self.results.failures))
    305     self.assertEquals(3, GetNumberOfSuccessfulPageRuns(self.results))
    306 
    307     self.assertEquals(3*EXPECTED_CALLS_IN_ORDER,
    308                       [call[0] for call in manager.mock_calls])
    309 
    310   def testCallOrderBetweenStoryTestAndSharedState(self):
    311     """Check that the call order between StoryTest and SharedState is correct.
    312     """
    313     TEST_WILL_RUN_STORY = 'test.WillRunStory'
    314     TEST_MEASURE = 'test.Measure'
    315     TEST_DID_RUN_STORY = 'test.DidRunStory'
    316     STATE_WILL_RUN_STORY = 'state.WillRunStory'
    317     STATE_RUN_STORY = 'state.RunStory'
    318     STATE_DID_RUN_STORY = 'state.DidRunStory'
    319 
    320     EXPECTED_CALLS_IN_ORDER = [TEST_WILL_RUN_STORY,
    321                                STATE_WILL_RUN_STORY,
    322                                STATE_RUN_STORY,
    323                                TEST_MEASURE,
    324                                STATE_DID_RUN_STORY,
    325                                TEST_DID_RUN_STORY]
    326 
    327     class TestStoryTest(story_test.StoryTest):
    328       def WillRunStory(self, platform):
    329         pass
    330 
    331       def Measure(self, platform, results):
    332         pass
    333 
    334       def DidRunStory(self, platform):
    335         pass
    336 
    337     class TestSharedStateForStoryTest(TestSharedState):
    338       def RunStory(self, results):
    339         pass
    340 
    341     @mock.patch.object(TestStoryTest, 'WillRunStory')
    342     @mock.patch.object(TestStoryTest, 'Measure')
    343     @mock.patch.object(TestStoryTest, 'DidRunStory')
    344     @mock.patch.object(TestSharedStateForStoryTest, 'WillRunStory')
    345     @mock.patch.object(TestSharedStateForStoryTest, 'RunStory')
    346     @mock.patch.object(TestSharedStateForStoryTest, 'DidRunStory')
    347     def GetCallsInOrder(state_DidRunStory, state_RunStory, state_WillRunStory,
    348                         test_DidRunStory, test_Measure, test_WillRunStory):
    349       manager = mock.MagicMock()
    350       manager.attach_mock(test_WillRunStory, TEST_WILL_RUN_STORY)
    351       manager.attach_mock(test_Measure, TEST_MEASURE)
    352       manager.attach_mock(test_DidRunStory, TEST_DID_RUN_STORY)
    353       manager.attach_mock(state_WillRunStory, STATE_WILL_RUN_STORY)
    354       manager.attach_mock(state_RunStory, STATE_RUN_STORY)
    355       manager.attach_mock(state_DidRunStory, STATE_DID_RUN_STORY)
    356 
    357       test = TestStoryTest()
    358       story_set = story_module.StorySet()
    359       story_set.AddStory(DummyLocalStory(TestSharedStateForStoryTest))
    360       story_runner.Run(test, story_set, self.options, self.results)
    361       return [call[0] for call in manager.mock_calls]
    362 
    363     calls_in_order = GetCallsInOrder() # pylint: disable=no-value-for-parameter
    364     self.assertEquals(EXPECTED_CALLS_IN_ORDER, calls_in_order)
    365 
    366   def testTearDownStateAfterEachStoryOrStorySetRun(self):
    367     class TestSharedStateForTearDown(TestSharedState):
    368       num_of_tear_downs = 0
    369 
    370       def RunStory(self, results):
    371         pass
    372 
    373       def TearDownState(self):
    374         TestSharedStateForTearDown.num_of_tear_downs += 1
    375 
    376     story_set = story_module.StorySet()
    377     story_set.AddStory(DummyLocalStory(TestSharedStateForTearDown))
    378     story_set.AddStory(DummyLocalStory(TestSharedStateForTearDown))
    379     story_set.AddStory(DummyLocalStory(TestSharedStateForTearDown))
    380 
    381     TestSharedStateForTearDown.num_of_tear_downs = 0
    382     story_runner.Run(mock.MagicMock(), story_set, self.options, self.results)
    383     self.assertEquals(TestSharedStateForTearDown.num_of_tear_downs, 1)
    384 
    385     TestSharedStateForTearDown.num_of_tear_downs = 0
    386     story_runner.Run(mock.MagicMock(), story_set, self.options, self.results,
    387                      tear_down_after_story=True)
    388     self.assertEquals(TestSharedStateForTearDown.num_of_tear_downs, 3)
    389 
    390     self.options.pageset_repeat = 5
    391     TestSharedStateForTearDown.num_of_tear_downs = 0
    392     story_runner.Run(mock.MagicMock(), story_set, self.options, self.results,
    393                      tear_down_after_story_set=True)
    394     self.assertEquals(TestSharedStateForTearDown.num_of_tear_downs, 5)
    395 
    396   def testTearDownIsCalledOnceForEachStoryGroupWithPageSetRepeat(self):
    397     self.options.pageset_repeat = 3
    398     fooz_init_call_counter = [0]
    399     fooz_tear_down_call_counter = [0]
    400     barz_init_call_counter = [0]
    401     barz_tear_down_call_counter = [0]
    402     class FoozStoryState(FooStoryState):
    403       def __init__(self, test, options, storyz):
    404         super(FoozStoryState, self).__init__(
    405           test, options, storyz)
    406         fooz_init_call_counter[0] += 1
    407       def TearDownState(self):
    408         fooz_tear_down_call_counter[0] += 1
    409 
    410     class BarzStoryState(BarStoryState):
    411       def __init__(self, test, options, storyz):
    412         super(BarzStoryState, self).__init__(
    413           test, options, storyz)
    414         barz_init_call_counter[0] += 1
    415       def TearDownState(self):
    416         barz_tear_down_call_counter[0] += 1
    417     def AssertAndCleanUpFoo():
    418       self.assertEquals(1, fooz_init_call_counter[0])
    419       self.assertEquals(1, fooz_tear_down_call_counter[0])
    420       fooz_init_call_counter[0] = 0
    421       fooz_tear_down_call_counter[0] = 0
    422 
    423     story_set1_list = [FoozStoryState, FoozStoryState, FoozStoryState,
    424                        BarzStoryState, BarzStoryState]
    425     story_set1 = SetupStorySet(True, story_set1_list)
    426     self.RunStoryTest(story_set1, 15)
    427     AssertAndCleanUpFoo()
    428     self.assertEquals(1, barz_init_call_counter[0])
    429     self.assertEquals(1, barz_tear_down_call_counter[0])
    430     barz_init_call_counter[0] = 0
    431     barz_tear_down_call_counter[0] = 0
    432 
    433     story_set2_list = [FoozStoryState, FoozStoryState, FoozStoryState,
    434                        FoozStoryState]
    435     story_set2 = SetupStorySet(False, story_set2_list)
    436     self.RunStoryTest(story_set2, 27)
    437     AssertAndCleanUpFoo()
    438     self.assertEquals(0, barz_init_call_counter[0])
    439     self.assertEquals(0, barz_tear_down_call_counter[0])
    440 
    441   def testAppCrashExceptionCausesFailureValue(self):
    442     self.SuppressExceptionFormatting()
    443     story_set = story_module.StorySet()
    444     class SharedStoryThatCausesAppCrash(TestSharedPageState):
    445       def WillRunStory(self, story):
    446         raise exceptions.AppCrashException(msg='App Foo crashes')
    447 
    448     story_set.AddStory(DummyLocalStory(
    449           SharedStoryThatCausesAppCrash))
    450     story_runner.Run(
    451         DummyTest(), story_set, self.options, self.results)
    452     self.assertEquals(1, len(self.results.failures))
    453     self.assertEquals(0, GetNumberOfSuccessfulPageRuns(self.results))
    454     self.assertIn('App Foo crashes', self.fake_stdout.getvalue())
    455 
    456   def testExceptionRaisedInSharedStateTearDown(self):
    457     self.SuppressExceptionFormatting()
    458     story_set = story_module.StorySet()
    459     class SharedStoryThatCausesAppCrash(TestSharedPageState):
    460       def TearDownState(self):
    461         raise TestOnlyException()
    462 
    463     story_set.AddStory(DummyLocalStory(
    464           SharedStoryThatCausesAppCrash))
    465     with self.assertRaises(TestOnlyException):
    466       story_runner.Run(
    467           DummyTest(), story_set, self.options, self.results)
    468 
    469   def testUnknownExceptionIsFatal(self):
    470     self.SuppressExceptionFormatting()
    471     story_set = story_module.StorySet()
    472 
    473     class UnknownException(Exception):
    474       pass
    475 
    476     # This erroneous test is set up to raise exception for the 2nd story
    477     # run.
    478     class Test(legacy_page_test.LegacyPageTest):
    479       def __init__(self, *args):
    480         super(Test, self).__init__(*args)
    481         self.run_count = 0
    482 
    483       def RunPage(self, *_):
    484         old_run_count = self.run_count
    485         self.run_count += 1
    486         if old_run_count == 1:
    487           raise UnknownException('FooBarzException')
    488 
    489       def ValidateAndMeasurePage(self, page, tab, results):
    490         pass
    491 
    492     s1 = DummyLocalStory(TestSharedPageState)
    493     s2 = DummyLocalStory(TestSharedPageState)
    494     story_set.AddStory(s1)
    495     story_set.AddStory(s2)
    496     test = Test()
    497     with self.assertRaises(UnknownException):
    498       story_runner.Run(
    499           test, story_set, self.options, self.results)
    500     self.assertEqual(set([s2]), self.results.pages_that_failed)
    501     self.assertEqual(set([s1]), self.results.pages_that_succeeded)
    502     self.assertIn('FooBarzException', self.fake_stdout.getvalue())
    503 
    504   def testRaiseBrowserGoneExceptionFromRunPage(self):
    505     self.SuppressExceptionFormatting()
    506     story_set = story_module.StorySet()
    507 
    508     class Test(legacy_page_test.LegacyPageTest):
    509       def __init__(self, *args):
    510         super(Test, self).__init__(*args)
    511         self.run_count = 0
    512 
    513       def RunPage(self, *_):
    514         old_run_count = self.run_count
    515         self.run_count += 1
    516         if old_run_count == 0:
    517           raise exceptions.BrowserGoneException(
    518               None, 'i am a browser crash message')
    519 
    520       def ValidateAndMeasurePage(self, page, tab, results):
    521         pass
    522 
    523     story_set.AddStory(DummyLocalStory(TestSharedPageState))
    524     story_set.AddStory(DummyLocalStory(TestSharedPageState))
    525     test = Test()
    526     story_runner.Run(
    527         test, story_set, self.options, self.results)
    528     self.assertEquals(2, test.run_count)
    529     self.assertEquals(1, len(self.results.failures))
    530     self.assertEquals(1, GetNumberOfSuccessfulPageRuns(self.results))
    531 
    532   def testAppCrashThenRaiseInTearDownFatal(self):
    533     self.SuppressExceptionFormatting()
    534     story_set = story_module.StorySet()
    535 
    536     unit_test_events = []  # track what was called when
    537     class DidRunTestError(Exception):
    538       pass
    539 
    540     class TestTearDownSharedState(TestSharedPageState):
    541       def TearDownState(self):
    542         unit_test_events.append('tear-down-state')
    543         raise DidRunTestError
    544 
    545       def DumpStateUponFailure(self, story, results):
    546         unit_test_events.append('dump-state')
    547 
    548 
    549     class Test(legacy_page_test.LegacyPageTest):
    550       def __init__(self, *args):
    551         super(Test, self).__init__(*args)
    552         self.run_count = 0
    553 
    554       def RunPage(self, *_):
    555         old_run_count = self.run_count
    556         self.run_count += 1
    557         if old_run_count == 0:
    558           unit_test_events.append('app-crash')
    559           raise exceptions.AppCrashException
    560 
    561       def ValidateAndMeasurePage(self, page, tab, results):
    562         pass
    563 
    564     story_set.AddStory(DummyLocalStory(TestTearDownSharedState))
    565     story_set.AddStory(DummyLocalStory(TestTearDownSharedState))
    566     test = Test()
    567 
    568     with self.assertRaises(DidRunTestError):
    569       story_runner.Run(
    570           test, story_set, self.options, self.results)
    571     self.assertEqual(['app-crash', 'dump-state', 'tear-down-state'],
    572                      unit_test_events)
    573     # The AppCrashException gets added as a failure.
    574     self.assertEquals(1, len(self.results.failures))
    575 
    576   def testPagesetRepeat(self):
    577     story_set = story_module.StorySet()
    578 
    579     # TODO(eakuefner): Factor this out after flattening page ref in Value
    580     blank_story = DummyLocalStory(TestSharedPageState, name='blank')
    581     green_story = DummyLocalStory(TestSharedPageState, name='green')
    582     story_set.AddStory(blank_story)
    583     story_set.AddStory(green_story)
    584 
    585     class Measurement(legacy_page_test.LegacyPageTest):
    586       i = 0
    587       def RunPage(self, page, _, results):
    588         self.i += 1
    589         results.AddValue(scalar.ScalarValue(
    590             page, 'metric', 'unit', self.i,
    591             improvement_direction=improvement_direction.UP))
    592 
    593       def ValidateAndMeasurePage(self, page, tab, results):
    594         pass
    595 
    596     self.options.page_repeat = 1
    597     self.options.pageset_repeat = 2
    598     self.options.output_formats = []
    599     results = results_options.CreateResults(
    600       EmptyMetadataForTest(), self.options)
    601     story_runner.Run(
    602         Measurement(), story_set, self.options, results)
    603     summary = summary_module.Summary(results.all_page_specific_values)
    604     values = summary.interleaved_computed_per_page_values_and_summaries
    605 
    606     blank_value = list_of_scalar_values.ListOfScalarValues(
    607         blank_story, 'metric', 'unit', [1, 3],
    608         improvement_direction=improvement_direction.UP)
    609     green_value = list_of_scalar_values.ListOfScalarValues(
    610         green_story, 'metric', 'unit', [2, 4],
    611         improvement_direction=improvement_direction.UP)
    612     merged_value = list_of_scalar_values.ListOfScalarValues(
    613         None, 'metric', 'unit',
    614         [1, 3, 2, 4], std=math.sqrt(2),  # Pooled standard deviation.
    615         improvement_direction=improvement_direction.UP)
    616 
    617     self.assertEquals(4, GetNumberOfSuccessfulPageRuns(results))
    618     self.assertEquals(0, len(results.failures))
    619     self.assertEquals(3, len(values))
    620     self.assertIn(blank_value, values)
    621     self.assertIn(green_value, values)
    622     self.assertIn(merged_value, values)
    623 
    624   @decorators.Disabled('chromeos')  # crbug.com/483212
    625   def testUpdateAndCheckArchives(self):
    626     usr_stub = system_stub.Override(story_runner, ['cloud_storage'])
    627     wpr_stub = system_stub.Override(archive_info, ['cloud_storage'])
    628     archive_data_dir = os.path.join(
    629         util.GetTelemetryDir(),
    630         'telemetry', 'internal', 'testing', 'archive_files')
    631     try:
    632       story_set = story_module.StorySet()
    633       story_set.AddStory(page_module.Page(
    634           'http://www.testurl.com', story_set, story_set.base_dir))
    635       # Page set missing archive_data_file.
    636       self.assertRaises(
    637           story_runner.ArchiveError,
    638           story_runner._UpdateAndCheckArchives,
    639           story_set.archive_data_file,
    640           story_set.wpr_archive_info,
    641           story_set.stories)
    642 
    643       story_set = story_module.StorySet(
    644           archive_data_file='missing_archive_data_file.json')
    645       story_set.AddStory(page_module.Page(
    646           'http://www.testurl.com', story_set, story_set.base_dir))
    647       # Page set missing json file specified in archive_data_file.
    648       self.assertRaises(
    649           story_runner.ArchiveError,
    650           story_runner._UpdateAndCheckArchives,
    651           story_set.archive_data_file,
    652           story_set.wpr_archive_info,
    653           story_set.stories)
    654 
    655       story_set = story_module.StorySet(
    656           archive_data_file=os.path.join(archive_data_dir, 'test.json'),
    657           cloud_storage_bucket=cloud_storage.PUBLIC_BUCKET)
    658       story_set.AddStory(page_module.Page(
    659           'http://www.testurl.com', story_set, story_set.base_dir))
    660       # Page set with valid archive_data_file.
    661       self.assertTrue(story_runner._UpdateAndCheckArchives(
    662             story_set.archive_data_file, story_set.wpr_archive_info,
    663             story_set.stories))
    664       story_set.AddStory(page_module.Page(
    665           'http://www.google.com', story_set, story_set.base_dir))
    666       # Page set with an archive_data_file which exists but is missing a page.
    667       self.assertRaises(
    668           story_runner.ArchiveError,
    669           story_runner._UpdateAndCheckArchives,
    670           story_set.archive_data_file,
    671           story_set.wpr_archive_info,
    672           story_set.stories)
    673 
    674       story_set = story_module.StorySet(
    675           archive_data_file=
    676               os.path.join(archive_data_dir, 'test_missing_wpr_file.json'),
    677           cloud_storage_bucket=cloud_storage.PUBLIC_BUCKET)
    678       story_set.AddStory(page_module.Page(
    679           'http://www.testurl.com', story_set, story_set.base_dir))
    680       story_set.AddStory(page_module.Page(
    681           'http://www.google.com', story_set, story_set.base_dir))
    682       # Page set with an archive_data_file which exists and contains all pages
    683       # but fails to find a wpr file.
    684       self.assertRaises(
    685           story_runner.ArchiveError,
    686           story_runner._UpdateAndCheckArchives,
    687           story_set.archive_data_file,
    688           story_set.wpr_archive_info,
    689           story_set.stories)
    690     finally:
    691       usr_stub.Restore()
    692       wpr_stub.Restore()
    693 
    694 
    695   def _testMaxFailuresOptionIsRespectedAndOverridable(
    696       self, num_failing_stories, runner_max_failures, options_max_failures,
    697       expected_num_failures):
    698     class SimpleSharedState(story_module.SharedState):
    699       _fake_platform = FakePlatform()
    700       _current_story = None
    701 
    702       @property
    703       def platform(self):
    704         return self._fake_platform
    705 
    706       def WillRunStory(self, story):
    707         self._current_story = story
    708 
    709       def RunStory(self, results):
    710         self._current_story.Run(self)
    711 
    712       def DidRunStory(self, results):
    713         pass
    714 
    715       def CanRunStory(self, story):
    716         return True
    717 
    718       def TearDownState(self):
    719         pass
    720 
    721       def DumpStateUponFailure(self, story, results):
    722         pass
    723 
    724     class FailingStory(story_module.Story):
    725       def __init__(self):
    726         super(FailingStory, self).__init__(
    727             shared_state_class=SimpleSharedState,
    728             is_local=True)
    729         self.was_run = False
    730 
    731       def Run(self, shared_state):
    732         self.was_run = True
    733         raise legacy_page_test.Failure
    734 
    735       @property
    736       def url(self):
    737         return 'data:,'
    738 
    739     self.SuppressExceptionFormatting()
    740 
    741     story_set = story_module.StorySet()
    742     for _ in range(num_failing_stories):
    743       story_set.AddStory(FailingStory())
    744 
    745     options = _GetOptionForUnittest()
    746     options.output_formats = ['none']
    747     options.suppress_gtest_report = True
    748     if options_max_failures:
    749       options.max_failures = options_max_failures
    750 
    751     results = results_options.CreateResults(EmptyMetadataForTest(), options)
    752     story_runner.Run(
    753         DummyTest(), story_set, options,
    754         results, max_failures=runner_max_failures)
    755     self.assertEquals(0, GetNumberOfSuccessfulPageRuns(results))
    756     self.assertEquals(expected_num_failures, len(results.failures))
    757     for ii, story in enumerate(story_set.stories):
    758       self.assertEqual(story.was_run, ii < expected_num_failures)
    759 
    760   def testMaxFailuresNotSpecified(self):
    761     self._testMaxFailuresOptionIsRespectedAndOverridable(
    762         num_failing_stories=5, runner_max_failures=None,
    763         options_max_failures=None, expected_num_failures=5)
    764 
    765   def testMaxFailuresSpecifiedToRun(self):
    766     # Runs up to max_failures+1 failing tests before stopping, since
    767     # every tests after max_failures failures have been encountered
    768     # may all be passing.
    769     self._testMaxFailuresOptionIsRespectedAndOverridable(
    770         num_failing_stories=5, runner_max_failures=3,
    771         options_max_failures=None, expected_num_failures=4)
    772 
    773   def testMaxFailuresOption(self):
    774     # Runs up to max_failures+1 failing tests before stopping, since
    775     # every tests after max_failures failures have been encountered
    776     # may all be passing.
    777     self._testMaxFailuresOptionIsRespectedAndOverridable(
    778         num_failing_stories=5, runner_max_failures=3,
    779         options_max_failures=1, expected_num_failures=2)
    780 
    781   def _CreateErrorProcessingMock(self, method_exceptions=None,
    782                                  legacy_test=False):
    783     if legacy_test:
    784       test_class = legacy_page_test.LegacyPageTest
    785     else:
    786       test_class = story_test.StoryTest
    787 
    788     root_mock = mock.NonCallableMock(
    789         story=mock.NonCallableMagicMock(story_module.Story),
    790         results=mock.NonCallableMagicMock(page_test_results.PageTestResults),
    791         test=mock.NonCallableMagicMock(test_class),
    792         state=mock.NonCallableMagicMock(
    793             story_module.SharedState,
    794             CanRunStory=mock.Mock(return_value=True)))
    795 
    796     if method_exceptions:
    797       root_mock.configure_mock(**{
    798           path + '.side_effect': exception
    799           for path, exception in method_exceptions.iteritems()})
    800 
    801     return root_mock
    802 
    803   def testRunStoryAndProcessErrorIfNeeded_success(self):
    804     root_mock = self._CreateErrorProcessingMock()
    805 
    806     story_runner._RunStoryAndProcessErrorIfNeeded(
    807         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    808 
    809     self.assertEquals(root_mock.method_calls, [
    810       mock.call.test.WillRunStory(root_mock.state.platform),
    811       mock.call.state.WillRunStory(root_mock.story),
    812       mock.call.state.CanRunStory(root_mock.story),
    813       mock.call.state.RunStory(root_mock.results),
    814       mock.call.test.Measure(root_mock.state.platform, root_mock.results),
    815       mock.call.state.DidRunStory(root_mock.results),
    816       mock.call.test.DidRunStory(root_mock.state.platform)
    817     ])
    818 
    819   def testRunStoryAndProcessErrorIfNeeded_successLegacy(self):
    820     root_mock = self._CreateErrorProcessingMock(legacy_test=True)
    821 
    822     story_runner._RunStoryAndProcessErrorIfNeeded(
    823         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    824 
    825     self.assertEquals(root_mock.method_calls, [
    826       mock.call.state.WillRunStory(root_mock.story),
    827       mock.call.state.CanRunStory(root_mock.story),
    828       mock.call.state.RunStory(root_mock.results),
    829       mock.call.state.DidRunStory(root_mock.results),
    830       mock.call.test.DidRunPage(root_mock.state.platform)
    831     ])
    832 
    833   def testRunStoryAndProcessErrorIfNeeded_tryTimeout(self):
    834     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    835       'state.WillRunStory': exceptions.TimeoutException('foo')
    836     })
    837 
    838     story_runner._RunStoryAndProcessErrorIfNeeded(
    839         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    840 
    841     self.assertEquals(root_mock.method_calls, [
    842       mock.call.test.WillRunStory(root_mock.state.platform),
    843       mock.call.state.WillRunStory(root_mock.story),
    844       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results),
    845       mock.call.results.AddValue(FailureValueMatcher('foo')),
    846       mock.call.state.DidRunStory(root_mock.results),
    847       mock.call.test.DidRunStory(root_mock.state.platform)
    848     ])
    849 
    850   def testRunStoryAndProcessErrorIfNeeded_tryError(self):
    851     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    852       'state.CanRunStory': exceptions.Error('foo')
    853     })
    854 
    855     with self.assertRaisesRegexp(exceptions.Error, 'foo'):
    856       story_runner._RunStoryAndProcessErrorIfNeeded(
    857           root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    858 
    859     self.assertEquals(root_mock.method_calls, [
    860       mock.call.test.WillRunStory(root_mock.state.platform),
    861       mock.call.state.WillRunStory(root_mock.story),
    862       mock.call.state.CanRunStory(root_mock.story),
    863       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results),
    864       mock.call.results.AddValue(FailureValueMatcher('foo')),
    865       mock.call.state.DidRunStory(root_mock.results),
    866       mock.call.test.DidRunStory(root_mock.state.platform)
    867     ])
    868 
    869   def testRunStoryAndProcessErrorIfNeeded_tryUnsupportedAction(self):
    870     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    871       'state.RunStory': page_action.PageActionNotSupported('foo')
    872     })
    873 
    874     story_runner._RunStoryAndProcessErrorIfNeeded(
    875         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    876 
    877     self.assertEquals(root_mock.method_calls, [
    878       mock.call.test.WillRunStory(root_mock.state.platform),
    879       mock.call.state.WillRunStory(root_mock.story),
    880       mock.call.state.CanRunStory(root_mock.story),
    881       mock.call.state.RunStory(root_mock.results),
    882       mock.call.results.AddValue(SkipValueMatcher()),
    883       mock.call.state.DidRunStory(root_mock.results),
    884       mock.call.test.DidRunStory(root_mock.state.platform)
    885     ])
    886 
    887   def testRunStoryAndProcessErrorIfNeeded_tryUnhandlable(self):
    888     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    889       'test.WillRunStory': Exception('foo')
    890     })
    891 
    892     with self.assertRaisesRegexp(Exception, 'foo'):
    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.DumpStateUponFailure(root_mock.story, root_mock.results),
    899       mock.call.results.AddValue(FailureValueMatcher('foo')),
    900       mock.call.state.DidRunStory(root_mock.results),
    901       mock.call.test.DidRunStory(root_mock.state.platform)
    902     ])
    903 
    904   def testRunStoryAndProcessErrorIfNeeded_finallyException(self):
    905     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    906       'state.DidRunStory': Exception('bar')
    907     })
    908 
    909     with self.assertRaisesRegexp(Exception, 'bar'):
    910       story_runner._RunStoryAndProcessErrorIfNeeded(
    911           root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    912 
    913     self.assertEquals(root_mock.method_calls, [
    914       mock.call.test.WillRunStory(root_mock.state.platform),
    915       mock.call.state.WillRunStory(root_mock.story),
    916       mock.call.state.CanRunStory(root_mock.story),
    917       mock.call.state.RunStory(root_mock.results),
    918       mock.call.test.Measure(root_mock.state.platform, root_mock.results),
    919       mock.call.state.DidRunStory(root_mock.results),
    920       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results)
    921     ])
    922 
    923   def testRunStoryAndProcessErrorIfNeeded_tryTimeout_finallyException(self):
    924     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    925       'state.RunStory': exceptions.TimeoutException('foo'),
    926       'state.DidRunStory': Exception('bar')
    927     })
    928 
    929     story_runner._RunStoryAndProcessErrorIfNeeded(
    930         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    931 
    932     self.assertEquals(root_mock.method_calls, [
    933       mock.call.test.WillRunStory(root_mock.state.platform),
    934       mock.call.state.WillRunStory(root_mock.story),
    935       mock.call.state.CanRunStory(root_mock.story),
    936       mock.call.state.RunStory(root_mock.results),
    937       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results),
    938       mock.call.results.AddValue(FailureValueMatcher('foo')),
    939       mock.call.state.DidRunStory(root_mock.results)
    940     ])
    941 
    942   def testRunStoryAndProcessErrorIfNeeded_tryError_finallyException(self):
    943     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    944       'state.WillRunStory': exceptions.Error('foo'),
    945       'test.DidRunStory': Exception('bar')
    946     })
    947 
    948     with self.assertRaisesRegexp(exceptions.Error, 'foo'):
    949       story_runner._RunStoryAndProcessErrorIfNeeded(
    950           root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    951 
    952     self.assertEquals(root_mock.method_calls, [
    953       mock.call.test.WillRunStory(root_mock.state.platform),
    954       mock.call.state.WillRunStory(root_mock.story),
    955       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results),
    956       mock.call.results.AddValue(FailureValueMatcher('foo')),
    957       mock.call.state.DidRunStory(root_mock.results),
    958       mock.call.test.DidRunStory(root_mock.state.platform)
    959     ])
    960 
    961   def testRunStoryAndProcessErrorIfNeeded_tryUnsupportedAction_finallyException(
    962       self):
    963     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    964       'test.WillRunStory': page_action.PageActionNotSupported('foo'),
    965       'state.DidRunStory': Exception('bar')
    966     })
    967 
    968     story_runner._RunStoryAndProcessErrorIfNeeded(
    969         root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    970 
    971     self.assertEquals(root_mock.method_calls, [
    972       mock.call.test.WillRunStory(root_mock.state.platform),
    973       mock.call.results.AddValue(SkipValueMatcher()),
    974       mock.call.state.DidRunStory(root_mock.results)
    975     ])
    976 
    977   def testRunStoryAndProcessErrorIfNeeded_tryUnhandlable_finallyException(self):
    978     root_mock = self._CreateErrorProcessingMock(method_exceptions={
    979       'test.Measure': Exception('foo'),
    980       'test.DidRunStory': Exception('bar')
    981     })
    982 
    983     with self.assertRaisesRegexp(Exception, 'foo'):
    984       story_runner._RunStoryAndProcessErrorIfNeeded(
    985           root_mock.story, root_mock.results, root_mock.state, root_mock.test)
    986 
    987     self.assertEquals(root_mock.method_calls, [
    988       mock.call.test.WillRunStory(root_mock.state.platform),
    989       mock.call.state.WillRunStory(root_mock.story),
    990       mock.call.state.CanRunStory(root_mock.story),
    991       mock.call.state.RunStory(root_mock.results),
    992       mock.call.test.Measure(root_mock.state.platform, root_mock.results),
    993       mock.call.state.DumpStateUponFailure(root_mock.story, root_mock.results),
    994       mock.call.results.AddValue(FailureValueMatcher('foo')),
    995       mock.call.state.DidRunStory(root_mock.results),
    996       mock.call.test.DidRunStory(root_mock.state.platform)
    997     ])
    998