blob: 1df51dec457283f4f00680c9383ec7e09c2e86ee [file] [log] [blame]
# Copyright (c) 2012 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 collections
import glob
import logging
import os
import sys
import tempfile
import time
import traceback
import random
from telemetry.core import browser_finder
from telemetry.core import exceptions
from telemetry.core import util
from telemetry.core import wpr_modes
from telemetry.core.platform.profiler import profiler_finder
from telemetry.page import page_filter as page_filter_module
from telemetry.page import page_measurement_results
from telemetry.page import page_runner_repeat
from telemetry.page import page_test
class _RunState(object):
def __init__(self):
self.browser = None
self.tab = None
self._append_to_existing_wpr = False
self._last_archive_path = None
self._first_browser = True
self.first_page = collections.defaultdict(lambda: True)
self.profiler_dir = None
def StartBrowser(self, test, page_set, page, possible_browser,
credentials_path, archive_path):
# Create a browser.
if not self.browser:
assert not self.tab
self.browser = possible_browser.Create()
self.browser.credentials.credentials_path = credentials_path
test.WillStartBrowser(self.browser)
self.browser.Start()
test.DidStartBrowser(self.browser)
if self._first_browser:
self._first_browser = False
self.browser.credentials.WarnIfMissingCredentials(page_set)
# Set up WPR path on the new browser.
self.browser.SetReplayArchivePath(archive_path,
self._append_to_existing_wpr,
page_set.make_javascript_deterministic)
self._last_archive_path = page.archive_path
else:
# Set up WPR path if it changed.
if page.archive_path and self._last_archive_path != page.archive_path:
self.browser.SetReplayArchivePath(
page.archive_path,
self._append_to_existing_wpr,
page_set.make_javascript_deterministic)
self._last_archive_path = page.archive_path
if self.browser.supports_tab_control:
# Create a tab if there's none.
if len(self.browser.tabs) == 0:
self.browser.tabs.New()
# Ensure only one tab is open.
while len(self.browser.tabs) > 1:
self.browser.tabs[-1].Close()
if not self.tab:
self.tab = self.browser.tabs[0]
if self.first_page[page]:
self.first_page[page] = False
def StopBrowser(self):
if self.tab:
self.tab.Disconnect()
self.tab = None
if self.browser:
self.browser.Close()
self.browser = None
# Restarting the state will also restart the wpr server. If we're
# recording, we need to continue adding into the same wpr archive,
# not overwrite it.
self._append_to_existing_wpr = True
def StartProfiling(self, page, options):
if not self.profiler_dir:
self.profiler_dir = tempfile.mkdtemp()
output_file = os.path.join(self.profiler_dir, page.url_as_file_safe_name)
if options.repeat_options.IsRepeating():
output_file = _GetSequentialFileName(output_file)
self.browser.StartProfiling(options.profiler, output_file)
def StopProfiling(self):
self.browser.StopProfiling()
class PageState(object):
def __init__(self):
self._did_login = False
def PreparePage(self, page, tab, test=None):
if page.is_file:
serving_dirs, filename = page.serving_dirs_and_file
if tab.browser.SetHTTPServerDirectories(serving_dirs) and test:
test.DidStartHTTPServer(tab)
target_side_url = tab.browser.http_server.UrlOf(filename)
else:
target_side_url = page.url
if page.credentials:
if not tab.browser.credentials.LoginNeeded(tab, page.credentials):
raise page_test.Failure('Login as ' + page.credentials + ' failed')
self._did_login = True
if test:
if test.clear_cache_before_each_run:
tab.ClearCache()
test.WillNavigateToPage(page, tab)
tab.Navigate(target_side_url, page.script_to_evaluate_on_commit)
if test:
test.DidNavigateToPage(page, tab)
page.WaitToLoad(tab, 60)
tab.WaitForDocumentReadyStateToBeInteractiveOrBetter()
def CleanUpPage(self, page, tab):
if page.credentials and self._did_login:
tab.browser.credentials.LoginNoLongerNeeded(tab, page.credentials)
def AddCommandLineOptions(parser):
page_filter_module.PageFilter.AddCommandLineOptions(parser)
def _LogStackTrace(title, browser):
if browser:
stack_trace = browser.GetStackTrace()
else:
stack_trace = 'Browser object is empty, no stack trace.'
stack_trace = (('\nStack Trace:\n') +
('*' * 80) +
'\n\t' + stack_trace.replace('\n', '\n\t') + '\n' +
('*' * 80))
logging.warning('%s%s', title, stack_trace)
def _PrepareAndRunPage(test, page_set, expectations, options, page,
credentials_path, possible_browser, results, state):
if options.wpr_mode != wpr_modes.WPR_RECORD:
if page.archive_path and os.path.isfile(page.archive_path):
possible_browser.options.wpr_mode = wpr_modes.WPR_REPLAY
else:
possible_browser.options.wpr_mode = wpr_modes.WPR_OFF
results_for_current_run = results
if state.first_page[page] and test.discard_first_result:
# If discarding results, substitute a dummy object.
results_for_current_run = page_measurement_results.PageMeasurementResults()
results_for_current_run.StartTest(page)
tries = 3
while tries:
try:
state.StartBrowser(test, page_set, page, possible_browser,
credentials_path, page.archive_path)
_WaitForThermalThrottlingIfNeeded(state.browser.platform)
if options.profiler:
state.StartProfiling(page, options)
expectation = expectations.GetExpectationForPage(
state.browser.platform, page)
try:
_RunPage(test, page, state.tab, expectation,
results_for_current_run, options)
_CheckThermalThrottling(state.browser.platform)
except exceptions.TabCrashException:
_LogStackTrace('Tab crashed: %s' % page.url, state.browser)
state.StopBrowser()
if options.profiler:
state.StopProfiling()
if test.NeedsBrowserRestartAfterEachRun(state.tab):
state.StopBrowser()
break
except exceptions.BrowserGoneException:
_LogStackTrace('Browser crashed', state.browser)
logging.warning('Lost connection to browser. Retrying.')
state.StopBrowser()
tries -= 1
if not tries:
logging.error('Lost connection to browser 3 times. Failing.')
raise
results_for_current_run.StopTest(page)
def Run(test, page_set, expectations, options):
"""Runs a given test against a given page_set with the given options."""
results = test.PrepareResults(options)
# Create a possible_browser with the given options.
test.CustomizeBrowserOptions(options)
if options.profiler:
profiler_class = profiler_finder.FindProfiler(options.profiler)
profiler_class.CustomizeBrowserOptions(options)
try:
possible_browser = browser_finder.FindBrowser(options)
except browser_finder.BrowserTypeRequiredException, e:
sys.stderr.write(str(e) + '\n')
sys.exit(1)
if not possible_browser:
sys.stderr.write(
'No browser found. Available browsers:\n' +
'\n'.join(browser_finder.GetAllAvailableBrowserTypes(options)) + '\n')
sys.exit(1)
# Reorder page set based on options.
pages = _ShuffleAndFilterPageSet(page_set, options)
if (not options.allow_live_sites and
options.wpr_mode != wpr_modes.WPR_RECORD):
pages = _CheckArchives(page_set, pages, results)
# Verify credentials path.
credentials_path = None
if page_set.credentials_path:
credentials_path = os.path.join(os.path.dirname(page_set.file_path),
page_set.credentials_path)
if not os.path.exists(credentials_path):
credentials_path = None
# Set up user agent.
if page_set.user_agent_type:
options.browser_user_agent_type = page_set.user_agent_type
for page in pages:
test.CustomizeBrowserOptionsForPage(page, possible_browser.options)
for page in list(pages):
if not test.CanRunForPage(page):
logging.warning('Skipping test: it cannot run for %s', page.url)
results.AddSkip(page, 'Test cannot run')
pages.remove(page)
if not pages:
return results
state = _RunState()
# TODO(dtu): Move results creation and results_for_current_run into RunState.
try:
test.WillRunTest(state.tab)
repeat_state = page_runner_repeat.PageRunnerRepeatState(
options.repeat_options)
repeat_state.WillRunPageSet()
while repeat_state.ShouldRepeatPageSet():
for page in pages:
repeat_state.WillRunPage()
while repeat_state.ShouldRepeatPage():
# execute test on page
_PrepareAndRunPage(test, page_set, expectations, options, page,
credentials_path, possible_browser, results, state)
repeat_state.DidRunPage()
repeat_state.DidRunPageSet()
test.DidRunTest(state.tab, results)
finally:
state.StopBrowser()
return results
def _ShuffleAndFilterPageSet(page_set, options):
if options.pageset_shuffle_order_file and not options.pageset_shuffle:
raise Exception('--pageset-shuffle-order-file requires --pageset-shuffle.')
if options.pageset_shuffle_order_file:
return page_set.ReorderPageSet(options.pageset_shuffle_order_file)
page_filter = page_filter_module.PageFilter(options)
pages = [page for page in page_set.pages[:]
if not page.disabled and page_filter.IsSelected(page)]
if options.pageset_shuffle:
random.Random().shuffle(pages)
return pages
def _CheckArchives(page_set, pages, results):
"""Returns a subset of pages that are local or have WPR archives.
Logs warnings if any are missing."""
page_set_has_live_sites = False
for page in pages:
if not page.is_local:
page_set_has_live_sites = True
break
# Potential problems with the entire page set.
if page_set_has_live_sites:
if not page_set.archive_data_file:
logging.warning('The page set is missing an "archive_data_file" '
'property. Skipping any live sites. To include them, '
'pass the flag --allow-live-sites.')
if not page_set.wpr_archive_info:
logging.warning('The archive info file is missing. '
'To fix this, either add svn-internal to your '
'.gclient using http://goto/read-src-internal, '
'or create a new archive using record_wpr.')
# Potential problems with individual pages.
pages_missing_archive_path = []
pages_missing_archive_data = []
for page in pages:
if page.is_local:
continue
if not page.archive_path:
pages_missing_archive_path.append(page)
elif not os.path.isfile(page.archive_path):
pages_missing_archive_data.append(page)
if pages_missing_archive_path:
logging.warning('The page set archives for some pages do not exist. '
'Skipping those pages. To fix this, record those pages '
'using record_wpr. To ignore this warning and run '
'against live sites, pass the flag --allow-live-sites.')
if pages_missing_archive_data:
logging.warning('The page set archives for some pages are missing. '
'Someone forgot to check them in, or they were deleted. '
'Skipping those pages. To fix this, record those pages '
'using record_wpr. To ignore this warning and run '
'against live sites, pass the flag --allow-live-sites.')
for page in pages_missing_archive_path + pages_missing_archive_data:
results.StartTest(page)
results.AddErrorMessage(page, 'Page set archive doesn\'t exist.')
results.StopTest(page)
return [page for page in pages if page not in
pages_missing_archive_path + pages_missing_archive_data]
def _RunPage(test, page, tab, expectation, results, options):
logging.info('Running %s' % page.url)
page_state = PageState()
try:
page_state.PreparePage(page, tab, test)
test.Run(options, page, tab, results)
util.CloseConnections(tab)
except page_test.Failure:
logging.warning('%s:\n%s', page.url, traceback.format_exc())
if expectation == 'fail':
logging.info('Failure was expected\n')
results.AddSuccess(page)
else:
results.AddFailure(page, sys.exc_info())
except (util.TimeoutException, exceptions.LoginException,
exceptions.ProfilingException):
logging.error('%s:\n%s', page.url, traceback.format_exc())
results.AddError(page, sys.exc_info())
except (exceptions.TabCrashException, exceptions.BrowserGoneException):
logging.error('%s:\n%s', page.url, traceback.format_exc())
results.AddError(page, sys.exc_info())
# Run() catches these exceptions to relaunch the tab/browser, so re-raise.
raise
except Exception:
raise
else:
if expectation == 'fail':
logging.warning('%s was expected to fail, but passed.\n', page.url)
results.AddSuccess(page)
finally:
page_state.CleanUpPage(page, tab)
def _GetSequentialFileName(base_name):
"""Returns the next sequential file name based on |base_name| and the
existing files."""
index = 0
while True:
output_name = '%s_%03d' % (base_name, index)
if not glob.glob(output_name + '.*'):
break
index = index + 1
return output_name
def _WaitForThermalThrottlingIfNeeded(platform):
if not platform.CanMonitorThermalThrottling():
return
thermal_throttling_retry = 0
while (platform.IsThermallyThrottled() and
thermal_throttling_retry < 3):
logging.warning('Thermally throttled, waiting (%d)...',
thermal_throttling_retry)
thermal_throttling_retry += 1
time.sleep(thermal_throttling_retry * 2)
if thermal_throttling_retry and platform.IsThermallyThrottled():
logging.error('Device is thermally throttled before running '
'performance tests, results will vary.')
def _CheckThermalThrottling(platform):
if not platform.CanMonitorThermalThrottling():
return
if platform.HasBeenThermallyThrottled():
logging.error('Device has been thermally throttled during '
'performance tests, results will vary.')