1# Copyright 2013 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
5import unittest
6
7from telemetry.core import browser_options
8from telemetry.page import page_runner
9from telemetry.results import page_test_results
10from telemetry.unittest import simple_mock
11
12from measurements import page_cycler
13
14
15# Allow testing protected members in the unit test.
16# pylint: disable=W0212
17
18class MockMemoryMetric(object):
19  """Used instead of simple_mock.MockObject so that the precise order and
20  number of calls need not be specified."""
21  def __init__(self):
22    pass
23
24  def Start(self, page, tab):
25    pass
26
27  def Stop(self, page, tab):
28    pass
29
30  def AddResults(self, tab, results):
31    pass
32
33  def AddSummaryResults(self, tab, results):
34    pass
35
36
37class FakePage(object):
38  """Used to mock loading a page."""
39  def __init__(self, url):
40    self.url = url
41    self.is_file = url.startswith('file://')
42
43
44class FakeTab(object):
45  """Used to mock a browser tab."""
46  def __init__(self):
47    self.clear_cache_calls = 0
48    self.navigated_urls = []
49  def ClearCache(self, force=False):
50    assert force
51    self.clear_cache_calls += 1
52  def EvaluateJavaScript(self, _):
53    return 1
54  def Navigate(self, url):
55    self.navigated_urls.append(url)
56  def WaitForJavaScriptExpression(self, _, __):
57    pass
58  @property
59  def browser(self):
60    return FakeBrowser()
61
62class FakeBrowser(object):
63  _iteration = 0
64
65  @property
66  def cpu_stats(self):
67    FakeBrowser._iteration += 1
68    return {
69        'Browser': {'CpuProcessTime': FakeBrowser._iteration,
70                    'TotalTime': FakeBrowser._iteration * 2},
71        'Renderer': {'CpuProcessTime': FakeBrowser._iteration,
72                    'TotalTime': FakeBrowser._iteration * 3},
73        'Gpu': {'CpuProcessTime': FakeBrowser._iteration,
74                 'TotalTime': FakeBrowser._iteration * 4}
75    }
76  @property
77  def platform(self):
78    return FakePlatform()
79
80  @property
81  def http_server(self):
82    class FakeHttpServer(object):
83      def UrlOf(self, url_path):
84        return 'http://fakeserver:99999/%s' % url_path
85    return FakeHttpServer()
86
87
88class FakePlatform(object):
89  def GetOSName(self):
90    return 'fake'
91  def CanMonitorPower(self):
92    return False
93
94
95class PageCyclerUnitTest(unittest.TestCase):
96
97  def SetUpCycler(self, args, setup_memory_module=False):
98    cycler = page_cycler.PageCycler()
99    options = browser_options.BrowserFinderOptions()
100    options.browser_options.platform = FakePlatform()
101    parser = options.CreateParser()
102    page_runner.AddCommandLineArgs(parser)
103    cycler.AddCommandLineArgs(parser)
104    cycler.SetArgumentDefaults(parser)
105    parser.parse_args(args)
106    page_runner.ProcessCommandLineArgs(parser, options)
107    cycler.ProcessCommandLineArgs(parser, options)
108    cycler.CustomizeBrowserOptions(options.browser_options)
109
110    if setup_memory_module:
111      # Mock out memory metrics; the real ones require a real browser.
112      mock_memory_metric = MockMemoryMetric()
113
114      mock_memory_module = simple_mock.MockObject()
115      mock_memory_module.ExpectCall(
116        'MemoryMetric').WithArgs(simple_mock.DONT_CARE).WillReturn(
117        mock_memory_metric)
118
119      real_memory_module = page_cycler.memory
120      try:
121        page_cycler.memory = mock_memory_module
122        browser = FakeBrowser()
123        cycler.WillStartBrowser(options.browser_options.platform)
124        cycler.DidStartBrowser(browser)
125      finally:
126        page_cycler.memory = real_memory_module
127
128    return cycler
129
130  def testOptionsColdLoadNoArgs(self):
131    cycler = self.SetUpCycler([])
132
133    self.assertEquals(cycler._cold_run_start_index, 5)
134
135  def testOptionsColdLoadPagesetRepeat(self):
136    cycler = self.SetUpCycler(['--pageset-repeat=20', '--page-repeat=2'])
137
138    self.assertEquals(cycler._cold_run_start_index, 20)
139
140  def testOptionsColdLoadRequested(self):
141    cycler = self.SetUpCycler(['--pageset-repeat=21', '--page-repeat=2',
142                               '--cold-load-percent=40'])
143
144    self.assertEquals(cycler._cold_run_start_index, 26)
145
146  def testCacheHandled(self):
147    cycler = self.SetUpCycler(['--pageset-repeat=5',
148                               '--cold-load-percent=50'],
149                              True)
150
151    url_name = 'http://fakepage.com'
152    page = FakePage(url_name)
153    tab = FakeTab()
154
155    for i in range(5):
156      results = page_test_results.PageTestResults()
157      results.WillRunPage(page)
158      cycler.WillNavigateToPage(page, tab)
159      self.assertEqual(max(0, i - 2), tab.clear_cache_calls,
160                       'Iteration %d tab.clear_cache_calls %d' %
161                       (i, tab.clear_cache_calls))
162      cycler.ValidateAndMeasurePage(page, tab, results)
163      results.DidRunPage(page)
164
165      values = results.all_page_specific_values
166      self.assertGreater(len(values), 2)
167
168      self.assertEqual(values[0].page, page)
169      chart_name = 'cold_times' if i == 0 or i > 2 else 'warm_times'
170      self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
171      self.assertEqual(values[0].units, 'ms')
172
173      cycler.DidNavigateToPage(page, tab)
174
175  def testColdWarm(self):
176    cycler = self.SetUpCycler(['--pageset-repeat=3'], True)
177    pages = [FakePage('http://fakepage1.com'), FakePage('http://fakepage2.com')]
178    tab = FakeTab()
179    for i in range(3):
180      for page in pages:
181        results = page_test_results.PageTestResults()
182        results.WillRunPage(page)
183        cycler.WillNavigateToPage(page, tab)
184        cycler.ValidateAndMeasurePage(page, tab, results)
185        results.DidRunPage(page)
186
187        values = results.all_page_specific_values
188        self.assertGreater(len(values), 2)
189
190        self.assertEqual(values[0].page, page)
191
192        chart_name = 'cold_times' if i == 0 or i > 1 else 'warm_times'
193        self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
194        self.assertEqual(values[0].units, 'ms')
195
196        cycler.DidNavigateToPage(page, tab)
197
198  def testResults(self):
199    cycler = self.SetUpCycler([], True)
200
201    pages = [FakePage('http://fakepage1.com'), FakePage('http://fakepage2.com')]
202    tab = FakeTab()
203
204    for i in range(2):
205      for page in pages:
206        results = page_test_results.PageTestResults()
207        results.WillRunPage(page)
208        cycler.WillNavigateToPage(page, tab)
209        cycler.ValidateAndMeasurePage(page, tab, results)
210        results.DidRunPage(page)
211
212        values = results.all_page_specific_values
213        self.assertEqual(4, len(values))
214
215        self.assertEqual(values[0].page, page)
216        chart_name = 'cold_times' if i == 0 else 'warm_times'
217        self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
218        self.assertEqual(values[0].units, 'ms')
219
220        for value, expected in zip(values[1:], ['gpu', 'renderer', 'browser']):
221          self.assertEqual(value.page, page)
222          self.assertEqual(value.name,
223                           'cpu_utilization.cpu_utilization_%s' % expected)
224          self.assertEqual(value.units, '%')
225
226        cycler.DidNavigateToPage(page, tab)
227
228  def testLegacyPagesAvoidCrossRenderNavigation(self):
229    # For legacy page cyclers with file URLs, verify that WillNavigateToPage
230    # does an initial navigate to avoid paying for a cross-renderer navigation.
231    cycler = self.SetUpCycler([], True)
232    pages = [FakePage('file://fakepage1.com'), FakePage('file://fakepage2.com')]
233    tab = FakeTab()
234
235    self.assertEqual([], tab.navigated_urls)
236    for page in pages * 2:
237      cycler.WillNavigateToPage(page, tab)
238      self.assertEqual(
239          ['http://fakeserver:99999/nonexistent.html'], tab.navigated_urls)
240