# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import unittest

from telemetry.core import browser_options
from telemetry.results import page_test_results
from telemetry.unittest_util import simple_mock
from telemetry.user_story import user_story_runner

from measurements import page_cycler


# Allow testing protected members in the unit test.
# pylint: disable=W0212

class MockMemoryMetric(object):
  """Used instead of simple_mock.MockObject so that the precise order and
  number of calls need not be specified."""
  def __init__(self):
    pass

  def Start(self, page, tab):
    pass

  def Stop(self, page, tab):
    pass

  def AddResults(self, tab, results):
    pass

  def AddSummaryResults(self, tab, results):
    pass


class FakePage(object):
  """Used to mock loading a page."""
  def __init__(self, url):
    self.url = url
    self.is_file = url.startswith('file://')


class FakeTab(object):
  """Used to mock a browser tab."""
  def __init__(self):
    self.clear_cache_calls = 0
    self.navigated_urls = []
  def ClearCache(self, force=False):
    assert force
    self.clear_cache_calls += 1
  def EvaluateJavaScript(self, _):
    return 1
  def Navigate(self, url):
    self.navigated_urls.append(url)
  def WaitForJavaScriptExpression(self, _, __):
    pass
  @property
  def browser(self):
    return FakeBrowser()

class FakeBrowser(object):
  _iteration = 0

  @property
  def cpu_stats(self):
    FakeBrowser._iteration += 1
    return {
        'Browser': {'CpuProcessTime': FakeBrowser._iteration,
                    'TotalTime': FakeBrowser._iteration * 2},
        'Renderer': {'CpuProcessTime': FakeBrowser._iteration,
                    'TotalTime': FakeBrowser._iteration * 3},
        'Gpu': {'CpuProcessTime': FakeBrowser._iteration,
                 'TotalTime': FakeBrowser._iteration * 4}
    }
  @property
  def platform(self):
    return FakePlatform()

  @property
  def http_server(self):
    class FakeHttpServer(object):
      def UrlOf(self, url_path):
        return 'http://fakeserver:99999/%s' % url_path
    return FakeHttpServer()


class FakePlatform(object):
  def GetOSName(self):
    return 'fake'
  def CanMonitorPower(self):
    return False


class PageCyclerUnitTest(unittest.TestCase):

  def SetUpCycler(self, page_repeat=1, pageset_repeat=10, cold_load_percent=50,
                  record_v8_object_stats=False, report_speed_index=False,
                  setup_memory_module=False):
    cycler = page_cycler.PageCycler(
        page_repeat = page_repeat,
        pageset_repeat = pageset_repeat,
        cold_load_percent = cold_load_percent,
        record_v8_object_stats = record_v8_object_stats,
        report_speed_index = report_speed_index)
    options = browser_options.BrowserFinderOptions()
    options.browser_options.platform = FakePlatform()
    parser = options.CreateParser()
    user_story_runner.AddCommandLineArgs(parser)
    args = ['--page-repeat=%i' % page_repeat,
            '--pageset-repeat=%i' % pageset_repeat]
    parser.parse_args(args)
    user_story_runner.ProcessCommandLineArgs(parser, options)
    cycler.CustomizeBrowserOptions(options.browser_options)

    if setup_memory_module:
      # Mock out memory metrics; the real ones require a real browser.
      mock_memory_metric = MockMemoryMetric()

      mock_memory_module = simple_mock.MockObject()
      mock_memory_module.ExpectCall(
        'MemoryMetric').WithArgs(simple_mock.DONT_CARE).WillReturn(
        mock_memory_metric)

      real_memory_module = page_cycler.memory
      try:
        page_cycler.memory = mock_memory_module
        browser = FakeBrowser()
        cycler.WillStartBrowser(options.browser_options.platform)
        cycler.DidStartBrowser(browser)
      finally:
        page_cycler.memory = real_memory_module

    return cycler

  def testOptionsColdLoadNoArgs(self):
    cycler = self.SetUpCycler()

    self.assertEquals(cycler._cold_run_start_index, 5)

  def testOptionsColdLoadPagesetRepeat(self):
    cycler = self.SetUpCycler(pageset_repeat=20, page_repeat=2)

    self.assertEquals(cycler._cold_run_start_index, 20)

  def testOptionsColdLoadRequested(self):
    cycler = self.SetUpCycler(pageset_repeat=21, page_repeat=2,
                              cold_load_percent=40)

    self.assertEquals(cycler._cold_run_start_index, 26)

  def testCacheHandled(self):
    cycler = self.SetUpCycler(pageset_repeat=5,
                              cold_load_percent=50,
                              setup_memory_module=True)

    url_name = 'http://fakepage.com'
    page = FakePage(url_name)
    tab = FakeTab()

    for i in range(5):
      results = page_test_results.PageTestResults()
      results.WillRunPage(page)
      cycler.WillNavigateToPage(page, tab)
      self.assertEqual(max(0, i - 2), tab.clear_cache_calls,
                       'Iteration %d tab.clear_cache_calls %d' %
                       (i, tab.clear_cache_calls))
      cycler.ValidateAndMeasurePage(page, tab, results)
      results.DidRunPage(page)

      values = results.all_page_specific_values
      self.assertGreater(len(values), 2)

      self.assertEqual(values[0].page, page)
      chart_name = 'cold_times' if i == 0 or i > 2 else 'warm_times'
      self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
      self.assertEqual(values[0].units, 'ms')

      cycler.DidNavigateToPage(page, tab)

  def testColdWarm(self):
    cycler = self.SetUpCycler(pageset_repeat=3, setup_memory_module=True)
    pages = [FakePage('http://fakepage1.com'), FakePage('http://fakepage2.com')]
    tab = FakeTab()
    for i in range(3):
      for page in pages:
        results = page_test_results.PageTestResults()
        results.WillRunPage(page)
        cycler.WillNavigateToPage(page, tab)
        cycler.ValidateAndMeasurePage(page, tab, results)
        results.DidRunPage(page)

        values = results.all_page_specific_values
        self.assertGreater(len(values), 2)

        self.assertEqual(values[0].page, page)

        chart_name = 'cold_times' if i == 0 or i > 1 else 'warm_times'
        self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
        self.assertEqual(values[0].units, 'ms')

        cycler.DidNavigateToPage(page, tab)

  def testResults(self):
    cycler = self.SetUpCycler(setup_memory_module=True)

    pages = [FakePage('http://fakepage1.com'), FakePage('http://fakepage2.com')]
    tab = FakeTab()

    for i in range(2):
      for page in pages:
        results = page_test_results.PageTestResults()
        results.WillRunPage(page)
        cycler.WillNavigateToPage(page, tab)
        cycler.ValidateAndMeasurePage(page, tab, results)
        results.DidRunPage(page)

        values = results.all_page_specific_values
        self.assertEqual(4, len(values))

        self.assertEqual(values[0].page, page)
        chart_name = 'cold_times' if i == 0 else 'warm_times'
        self.assertEqual(values[0].name, '%s.page_load_time' % chart_name)
        self.assertEqual(values[0].units, 'ms')

        for value, expected in zip(values[1:], ['gpu', 'renderer', 'browser']):
          self.assertEqual(value.page, page)
          self.assertEqual(value.name,
                           'cpu_utilization.cpu_utilization_%s' % expected)
          self.assertEqual(value.units, '%')

        cycler.DidNavigateToPage(page, tab)

  def testLegacyPagesAvoidCrossRenderNavigation(self):
    # For legacy page cyclers with file URLs, verify that WillNavigateToPage
    # does an initial navigate to avoid paying for a cross-renderer navigation.
    cycler = self.SetUpCycler(setup_memory_module=True)
    pages = [FakePage('file://fakepage1.com'), FakePage('file://fakepage2.com')]
    tab = FakeTab()

    self.assertEqual([], tab.navigated_urls)
    for page in pages * 2:
      cycler.WillNavigateToPage(page, tab)
      self.assertEqual(
          ['http://fakeserver:99999/nonexistent.html'], tab.navigated_urls)
