blob: 05b72cb7c5da202d5a97aa53f3d0437b506cf739 [file] [log] [blame]
# Copyright 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 copy
import logging
import optparse
import os
import random
import sys
import tempfile
import time
from telemetry import decorators
from telemetry.core import browser_finder
from telemetry.core import browser_info
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
from telemetry.page import page_runner_repeat
from telemetry.page import page_test
from telemetry.page.actions import navigate
from telemetry.page.actions import page_action
from telemetry.results import results_options
from telemetry.util import cloud_storage
from telemetry.util import exception_formatter
class _RunState(object):
def __init__(self):
self.browser = 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
self.repeat_state = None
def StartBrowserIfNeeded(self, test, page_set, page, possible_browser,
credentials_path, archive_path, finder_options):
started_browser = not self.browser
# Create a browser.
if not self.browser:
test.CustomizeBrowserOptionsForSinglePage(page, finder_options)
self.browser = possible_browser.Create()
self.browser.credentials.credentials_path = credentials_path
# 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
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)
logging.info('OS: %s %s',
self.browser.platform.GetOSName(),
self.browser.platform.GetOSVersionName())
if self.browser.supports_system_info:
system_info = self.browser.GetSystemInfo()
if system_info.model_name:
logging.info('Model: %s', system_info.model_name)
if system_info.gpu:
for i, device in enumerate(system_info.gpu.devices):
logging.info('GPU device %d: %s', i, device)
if system_info.gpu.aux_attributes:
logging.info('GPU Attributes:')
for k, v in sorted(system_info.gpu.aux_attributes.iteritems()):
logging.info(' %-20s: %s', k, v)
if system_info.gpu.feature_status:
logging.info('Feature Status:')
for k, v in sorted(system_info.gpu.feature_status.iteritems()):
logging.info(' %-20s: %s', k, v)
if system_info.gpu.driver_bug_workarounds:
logging.info('Driver Bug Workarounds:')
for workaround in system_info.gpu.driver_bug_workarounds:
logging.info(' %s', workaround)
else:
logging.info('No GPU devices')
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 and test.close_tabs_before_run:
# Create a tab if there's none.
if len(self.browser.tabs) == 0:
self.browser.tabs.New()
# Ensure only one tab is open, unless the test is a multi-tab test.
if not test.is_multi_tab_test:
while len(self.browser.tabs) > 1:
self.browser.tabs[-1].Close()
# Must wait for tab to commit otherwise it can commit after the next
# navigation has begun and RenderFrameHostManager::DidNavigateMainFrame()
# will cancel the next navigation because it's pending. This manifests as
# the first navigation in a PageSet freezing indefinitly because the
# navigation was silently cancelled when |self.browser.tabs[0]| was
# committed. Only do this when we just started the browser, otherwise
# there are cases where previous pages in a PageSet never complete
# loading so we'll wait forever.
if started_browser:
self.browser.tabs[0].WaitForDocumentReadyStateToBeComplete()
def StopBrowser(self):
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, finder_options):
if not self.profiler_dir:
self.profiler_dir = tempfile.mkdtemp()
output_file = os.path.join(self.profiler_dir, page.file_safe_name)
is_repeating = (finder_options.page_repeat != 1 or
finder_options.pageset_repeat != 1)
if is_repeating:
output_file = util.GetSequentialFileName(output_file)
self.browser.StartProfiling(finder_options.profiler, output_file)
def StopProfiling(self):
if self.browser:
self.browser.StopProfiling()
class PageState(object):
def __init__(self, page, tab):
self.page = page
self.tab = tab
self._did_login = False
def PreparePage(self, test=None):
if self.page.is_file:
server_started = self.tab.browser.SetHTTPServerDirectories(
self.page.page_set.serving_dirs | set([self.page.serving_dir]))
if server_started and test:
test.DidStartHTTPServer(self.tab)
if self.page.credentials:
if not self.tab.browser.credentials.LoginNeeded(
self.tab, self.page.credentials):
raise page_test.Failure('Login as ' + self.page.credentials + ' failed')
self._did_login = True
if test:
if test.clear_cache_before_each_run:
self.tab.ClearCache(force=True)
def ImplicitPageNavigation(self, test=None):
"""Executes the implicit navigation that occurs for every page iteration.
This function will be called once per page before any actions are executed.
"""
if test:
test.WillNavigateToPage(self.page, self.tab)
test.RunNavigateSteps(self.page, self.tab)
test.DidNavigateToPage(self.page, self.tab)
else:
i = navigate.NavigateAction()
i.RunAction(self.page, self.tab, None)
def CleanUpPage(self, test):
test.CleanUpAfterPage(self.page, self.tab)
if self.page.credentials and self._did_login:
self.tab.browser.credentials.LoginNoLongerNeeded(
self.tab, self.page.credentials)
def AddCommandLineArgs(parser):
page_filter.PageFilter.AddCommandLineArgs(parser)
results_options.AddResultsOptions(parser)
# Page set options
group = optparse.OptionGroup(parser, 'Page set ordering and repeat options')
group.add_option('--pageset-shuffle', action='store_true',
dest='pageset_shuffle',
help='Shuffle the order of pages within a pageset.')
group.add_option('--pageset-shuffle-order-file',
dest='pageset_shuffle_order_file', default=None,
help='Filename of an output of a previously run test on the current '
'pageset. The tests will run in the same order again, overriding '
'what is specified by --page-repeat and --pageset-repeat.')
group.add_option('--page-repeat', default=1, type='int',
help='Number of times to repeat each individual page '
'before proceeding with the next page in the pageset.')
group.add_option('--pageset-repeat', default=1, type='int',
help='Number of times to repeat the entire pageset.')
parser.add_option_group(group)
# WPR options
group = optparse.OptionGroup(parser, 'Web Page Replay options')
group.add_option('--use-live-sites',
dest='use_live_sites', action='store_true',
help='Run against live sites and ignore the Web Page Replay archives.')
parser.add_option_group(group)
parser.add_option('-d', '--also-run-disabled-tests',
dest='run_disabled_tests',
action='store_true', default=False,
help='Ignore @Disabled and @Enabled restrictions.')
def ProcessCommandLineArgs(parser, args):
page_filter.PageFilter.ProcessCommandLineArgs(parser, args)
# Page set options
if args.pageset_shuffle_order_file and not args.pageset_shuffle:
parser.error('--pageset-shuffle-order-file requires --pageset-shuffle.')
if args.page_repeat < 1:
parser.error('--page-repeat must be a positive integer.')
if args.pageset_repeat < 1:
parser.error('--pageset-repeat must be a positive integer.')
def _PrepareAndRunPage(test, page_set, expectations, finder_options,
browser_options, page, credentials_path,
possible_browser, results, state):
if finder_options.use_live_sites:
browser_options.wpr_mode = wpr_modes.WPR_OFF
elif browser_options.wpr_mode != wpr_modes.WPR_RECORD:
browser_options.wpr_mode = (
wpr_modes.WPR_REPLAY
if page.archive_path and os.path.isfile(page.archive_path)
else wpr_modes.WPR_OFF)
tries = test.attempts
while tries:
tries -= 1
try:
results_for_current_run = copy.copy(results)
if test.RestartBrowserBeforeEachPage() or page.startup_url:
state.StopBrowser()
# If we are restarting the browser for each page customize the per page
# options for just the current page before starting the browser.
state.StartBrowserIfNeeded(test, page_set, page, possible_browser,
credentials_path, page.archive_path,
finder_options)
if not page.CanRunOnBrowser(browser_info.BrowserInfo(state.browser)):
logging.info('Skip test for page %s because browser is not supported.'
% page.url)
return results
expectation = expectations.GetExpectationForPage(state.browser, page)
_WaitForThermalThrottlingIfNeeded(state.browser.platform)
if finder_options.profiler:
state.StartProfiling(page, finder_options)
try:
_RunPage(test, page, state, expectation,
results_for_current_run, finder_options)
_CheckThermalThrottling(state.browser.platform)
except exceptions.TabCrashException as e:
if test.is_multi_tab_test:
logging.error('Aborting multi-tab test after tab %s crashed',
page.url)
raise
logging.warning(str(e))
state.StopBrowser()
if finder_options.profiler:
state.StopProfiling()
if (test.StopBrowserAfterPage(state.browser, page)):
state.StopBrowser()
if state.first_page[page]:
state.first_page[page] = False
if test.discard_first_result:
return results
return results_for_current_run
except exceptions.BrowserGoneException as e:
state.StopBrowser()
if not tries:
logging.error('Aborting after too many retries')
raise
if test.is_multi_tab_test:
logging.error('Aborting multi-tab test after browser crashed')
raise
logging.warning(str(e))
def _UpdatePageSetArchivesIfChanged(page_set):
# Attempt to download the credentials file.
if page_set.credentials_path:
try:
cloud_storage.GetIfChanged(
os.path.join(page_set.base_dir, page_set.credentials_path))
except (cloud_storage.CredentialsError, cloud_storage.PermissionError,
cloud_storage.CloudStorageError) as e:
logging.warning('Cannot retrieve credential file %s due to cloud storage '
'error %s', page_set.credentials_path, str(e))
# Scan every serving directory for .sha1 files
# and download them from Cloud Storage. Assume all data is public.
all_serving_dirs = page_set.serving_dirs.copy()
# Add individual page dirs to all serving dirs.
for page in page_set:
if page.is_file:
all_serving_dirs.add(page.serving_dir)
# Scan all serving dirs.
for serving_dir in all_serving_dirs:
if os.path.splitdrive(serving_dir)[1] == '/':
raise ValueError('Trying to serve root directory from HTTP server.')
for dirpath, _, filenames in os.walk(serving_dir):
for filename in filenames:
path, extension = os.path.splitext(
os.path.join(dirpath, filename))
if extension != '.sha1':
continue
cloud_storage.GetIfChanged(path, page_set.bucket)
def Run(test, page_set, expectations, finder_options):
"""Runs a given test against a given page_set with the given options."""
results = results_options.PrepareResults(test, finder_options)
test.ValidatePageSet(page_set)
# Create a possible_browser with the given options.
try:
possible_browser = browser_finder.FindBrowser(finder_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(finder_options)) +
'\n')
sys.exit(-1)
browser_options = possible_browser.finder_options.browser_options
browser_options.browser_type = possible_browser.browser_type
test.CustomizeBrowserOptions(browser_options)
should_run = decorators.IsEnabled(test, possible_browser)
should_run = should_run or finder_options.run_disabled_tests
if not should_run:
logging.warning('You are trying to run a disabled test.')
logging.warning('Pass --also-run-disabled-tests to squelch this message.')
return results
# Reorder page set based on options.
pages = _ShuffleAndFilterPageSet(page_set, finder_options)
if (not finder_options.use_live_sites and
browser_options.wpr_mode != wpr_modes.WPR_RECORD):
_UpdatePageSetArchivesIfChanged(page_set)
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.
browser_options.browser_user_agent_type = page_set.user_agent_type or None
if finder_options.profiler:
profiler_class = profiler_finder.FindProfiler(finder_options.profiler)
profiler_class.CustomizeBrowserOptions(browser_options.browser_type,
finder_options)
for page in list(pages):
if not test.CanRunForPage(page):
results.StartTest(page)
logging.debug('Skipping test: it cannot run for %s', page.url)
results.AddSkip(page, 'Test cannot run')
results.StopTest(page)
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(finder_options)
state.repeat_state = page_runner_repeat.PageRunnerRepeatState(
finder_options)
state.repeat_state.WillRunPageSet()
while state.repeat_state.ShouldRepeatPageSet() and not test.IsExiting():
for page in pages:
state.repeat_state.WillRunPage()
test.WillRunPageRepeats(page)
while state.repeat_state.ShouldRepeatPage():
results.StartTest(page)
try:
results = _PrepareAndRunPage(
test, page_set, expectations, finder_options, browser_options,
page, credentials_path, possible_browser, results, state)
finally:
state.repeat_state.DidRunPage()
results.StopTest(page)
test.DidRunPageRepeats(page)
if (not test.max_failures is None and
len(results.failures) > test.max_failures):
logging.error('Too many failures. Aborting.')
test.RequestExit()
if test.IsExiting():
break
state.repeat_state.DidRunPageSet()
test.DidRunTest(state.browser, results)
finally:
state.StopBrowser()
return results
def _ShuffleAndFilterPageSet(page_set, finder_options):
if finder_options.pageset_shuffle_order_file:
return page_set.ReorderPageSet(finder_options.pageset_shuffle_order_file)
pages = [page for page in page_set.pages[:]
if not page.disabled and page_filter.PageFilter.IsSelected(page)]
if finder_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 --use-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 --use-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 --use-live-sites.')
for page in pages_missing_archive_path + pages_missing_archive_data:
results.StartTest(page)
results.AddFailureMessage(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, state, expectation, results, finder_options):
if expectation == 'skip':
logging.debug('Skipping test: Skip expectation for %s', page.url)
results.AddSkip(page, 'Skipped by test expectations')
return
logging.info('Running %s', page.url)
page_state = PageState(page, test.TabForPage(page, state.browser))
def ProcessError():
if expectation == 'fail':
msg = 'Expected exception while running %s' % page.url
results.AddSuccess(page)
else:
msg = 'Exception while running %s' % page.url
results.AddFailure(page, sys.exc_info())
exception_formatter.PrintFormattedException(msg=msg)
try:
page_state.PreparePage(test)
if state.repeat_state.ShouldNavigate(
finder_options.skip_navigate_on_repeat):
page_state.ImplicitPageNavigation(test)
test.RunPage(page, page_state.tab, results)
util.CloseConnections(page_state.tab)
except page_test.TestNotSupportedOnPlatformFailure:
raise
except page_test.Failure:
if expectation == 'fail':
exception_formatter.PrintFormattedException(
msg='Expected failure while running %s' % page.url)
results.AddSuccess(page)
else:
exception_formatter.PrintFormattedException(
msg='Failure while running %s' % page.url)
results.AddFailure(page, sys.exc_info())
except (util.TimeoutException, exceptions.LoginException,
exceptions.ProfilingException):
ProcessError()
except (exceptions.TabCrashException, exceptions.BrowserGoneException):
ProcessError()
# Run() catches these exceptions to relaunch the tab/browser, so re-raise.
raise
except page_action.PageActionNotSupported as e:
results.AddSkip(page, 'Unsupported page action: %s' % e)
except Exception:
exception_formatter.PrintFormattedException(
msg='Unhandled exception while running %s' % page.url)
results.AddFailure(page, sys.exc_info())
else:
if expectation == 'fail':
logging.warning('%s was expected to fail, but passed.\n', page.url)
results.AddSuccess(page)
finally:
page_state.CleanUpPage(test)
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.warning('Device is thermally throttled before running '
'performance tests, results will vary.')
def _CheckThermalThrottling(platform):
if not platform.CanMonitorThermalThrottling():
return
if platform.HasBeenThermallyThrottled():
logging.warning('Device has been thermally throttled during '
'performance tests, results will vary.')