|
Packit |
f0b94e |
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
Packit |
f0b94e |
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
Packit |
f0b94e |
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
"""
|
|
Packit |
f0b94e |
Set up a browser environment before running a test.
|
|
Packit |
f0b94e |
"""
|
|
Packit |
f0b94e |
from __future__ import absolute_import, print_function
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
import os
|
|
Packit |
f0b94e |
import shutil
|
|
Packit |
f0b94e |
import tempfile
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
import mozfile
|
|
Packit |
f0b94e |
import mozinfo
|
|
Packit |
f0b94e |
import mozrunner
|
|
Packit |
f0b94e |
from mozlog import get_proxy_logger
|
|
Packit |
f0b94e |
from mozprocess import ProcessHandlerMixin
|
|
Packit |
f0b94e |
from mozprofile.profile import Profile
|
|
Packit |
f0b94e |
from talos import utils
|
|
Packit |
f0b94e |
from talos.gecko_profile import GeckoProfile
|
|
Packit |
f0b94e |
from talos.utils import TalosError, run_in_debug_mode
|
|
Packit |
f0b94e |
from talos import heavy
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
LOG = get_proxy_logger()
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
class FFSetup(object):
|
|
Packit |
f0b94e |
"""
|
|
Packit |
f0b94e |
Initialize the browser environment before running a test.
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
This prepares:
|
|
Packit |
f0b94e |
- the environment vars for running the test in the browser,
|
|
Packit |
f0b94e |
available via the instance member *env*.
|
|
Packit |
f0b94e |
- the profile used to run the test, available via the
|
|
Packit |
f0b94e |
instance member *profile_dir*.
|
|
Packit |
f0b94e |
- Gecko profiling, available via the instance member *gecko_profile*
|
|
Packit |
f0b94e |
of type :class:`GeckoProfile` or None if not used.
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
Note that the browser will be run once with the profile, to ensure
|
|
Packit |
f0b94e |
this is basically working and negate any performance noise with the
|
|
Packit |
f0b94e |
real test run (installing the profile the first time takes time).
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
This class should be used as a context manager::
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
with FFSetup(browser_config, test_config) as setup:
|
|
Packit |
f0b94e |
# setup.env is initialized, and setup.profile_dir created
|
|
Packit |
f0b94e |
pass
|
|
Packit |
f0b94e |
# here the profile is removed
|
|
Packit |
f0b94e |
"""
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def __init__(self, browser_config, test_config):
|
|
Packit |
f0b94e |
self.browser_config, self.test_config = browser_config, test_config
|
|
Packit |
f0b94e |
self._tmp_dir = tempfile.mkdtemp()
|
|
Packit |
f0b94e |
self.env = None
|
|
Packit |
f0b94e |
# The profile dir must be named 'profile' because of xperf analysis
|
|
Packit |
f0b94e |
# (in etlparser.py). TODO fix that ?
|
|
Packit |
f0b94e |
self.profile_dir = os.path.join(self._tmp_dir, 'profile')
|
|
Packit |
f0b94e |
self.gecko_profile = None
|
|
Packit |
f0b94e |
self.debug_mode = run_in_debug_mode(browser_config)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def _init_env(self):
|
|
Packit |
f0b94e |
self.env = dict(os.environ)
|
|
Packit |
f0b94e |
for k, v in self.browser_config['env'].iteritems():
|
|
Packit |
f0b94e |
self.env[k] = str(v)
|
|
Packit |
f0b94e |
self.env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
|
|
Packit |
f0b94e |
if self.browser_config['symbols_path']:
|
|
Packit |
f0b94e |
self.env['MOZ_CRASHREPORTER'] = '1'
|
|
Packit |
f0b94e |
else:
|
|
Packit |
f0b94e |
self.env['MOZ_CRASHREPORTER_DISABLE'] = '1'
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
self.env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1'
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
self.env["LD_LIBRARY_PATH"] = \
|
|
Packit |
f0b94e |
os.path.dirname(self.browser_config['browser_path'])
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def _init_profile(self):
|
|
Packit |
f0b94e |
preferences = dict(self.browser_config['preferences'])
|
|
Packit |
f0b94e |
if self.test_config.get('preferences'):
|
|
Packit |
f0b94e |
test_prefs = dict(
|
|
Packit |
f0b94e |
[(i, utils.parse_pref(j))
|
|
Packit |
f0b94e |
for i, j in self.test_config['preferences'].items()]
|
|
Packit |
f0b94e |
)
|
|
Packit |
f0b94e |
preferences.update(test_prefs)
|
|
Packit |
f0b94e |
# interpolate webserver value in prefs
|
|
Packit |
f0b94e |
webserver = self.browser_config['webserver']
|
|
Packit |
f0b94e |
if '://' not in webserver:
|
|
Packit |
f0b94e |
webserver = 'http://' + webserver
|
|
Packit |
f0b94e |
for name, value in preferences.items():
|
|
Packit |
f0b94e |
if type(value) is str:
|
|
Packit |
f0b94e |
value = utils.interpolate(value, webserver=webserver)
|
|
Packit |
f0b94e |
preferences[name] = value
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
extensions = self.browser_config['extensions'][:]
|
|
Packit |
f0b94e |
if self.test_config.get('extensions'):
|
|
Packit |
f0b94e |
extensions.extend(self.test_config['extensions'])
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
# downloading a profile instead of using the empty one
|
|
Packit |
f0b94e |
if self.test_config['profile'] is not None:
|
|
Packit |
f0b94e |
path = heavy.download_profile(self.test_config['profile'])
|
|
Packit |
f0b94e |
self.test_config['profile_path'] = path
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
profile_path = os.path.normpath(self.test_config['profile_path'])
|
|
Packit |
f0b94e |
LOG.info("Cloning profile located at %s" % profile_path)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def _feedback(directory, content):
|
|
Packit |
f0b94e |
# Called by shutil.copytree on each visited directory.
|
|
Packit |
f0b94e |
# Used here to display info.
|
|
Packit |
f0b94e |
#
|
|
Packit |
f0b94e |
# Returns the items that should be ignored by
|
|
Packit |
f0b94e |
# shutil.copytree when copying the tree, so always returns
|
|
Packit |
f0b94e |
# an empty list.
|
|
Packit |
f0b94e |
sub = directory.split(profile_path)[-1].lstrip("/")
|
|
Packit |
f0b94e |
if sub:
|
|
Packit |
f0b94e |
LOG.info("=> %s" % sub)
|
|
Packit |
f0b94e |
return []
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
profile = Profile.clone(profile_path,
|
|
Packit |
f0b94e |
self.profile_dir,
|
|
Packit |
f0b94e |
ignore=_feedback,
|
|
Packit |
f0b94e |
restore=False)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
profile.set_preferences(preferences)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
# installing addons
|
|
Packit |
f0b94e |
LOG.info("Installing Add-ons:")
|
|
Packit |
f0b94e |
LOG.info(extensions)
|
|
Packit |
f0b94e |
profile.addon_manager.install_addons(extensions)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
# installing webextensions
|
|
Packit |
f0b94e |
webextensions = self.test_config.get('webextensions', None)
|
|
Packit |
f0b94e |
if isinstance(webextensions, basestring):
|
|
Packit |
f0b94e |
webextensions = [webextensions]
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
if webextensions is not None:
|
|
Packit |
f0b94e |
LOG.info("Installing Webextensions:")
|
|
Packit |
f0b94e |
for webext in webextensions:
|
|
Packit |
f0b94e |
filename = utils.interpolate(webext)
|
|
Packit |
f0b94e |
if mozinfo.os == 'win':
|
|
Packit |
f0b94e |
filename = filename.replace('/', '\\')
|
|
Packit |
f0b94e |
if not filename.endswith('.xpi'):
|
|
Packit |
f0b94e |
continue
|
|
Packit |
f0b94e |
if not os.path.exists(filename):
|
|
Packit |
f0b94e |
continue
|
|
Packit |
f0b94e |
LOG.info(filename)
|
|
Packit |
f0b94e |
profile.addon_manager.install_from_path(filename)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def _run_profile(self):
|
|
Packit |
f0b94e |
runner_cls = mozrunner.runners.get(
|
|
Packit |
f0b94e |
mozinfo.info.get(
|
|
Packit |
f0b94e |
'appname',
|
|
Packit |
f0b94e |
'firefox'),
|
|
Packit |
f0b94e |
mozrunner.Runner)
|
|
Packit |
f0b94e |
args = [self.browser_config["extra_args"], self.browser_config["init_url"]]
|
|
Packit |
f0b94e |
runner = runner_cls(profile=self.profile_dir,
|
|
Packit |
f0b94e |
binary=self.browser_config["browser_path"],
|
|
Packit |
f0b94e |
cmdargs=args,
|
|
Packit |
f0b94e |
env=self.env,
|
|
Packit |
f0b94e |
process_class=ProcessHandlerMixin,
|
|
Packit |
f0b94e |
process_args={})
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
runner.start(outputTimeout=30)
|
|
Packit |
f0b94e |
proc = runner.process_handler
|
|
Packit |
f0b94e |
LOG.process_start(proc.pid, "%s %s" % (self.browser_config["browser_path"],
|
|
Packit |
f0b94e |
' '.join(args)))
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
exit_code = proc.wait()
|
|
Packit |
f0b94e |
except Exception:
|
|
Packit |
f0b94e |
proc.kill()
|
|
Packit |
f0b94e |
raise TalosError("Browser Failed to close properly during warmup")
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
LOG.process_exit(proc.pid, exit_code)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def _init_gecko_profile(self):
|
|
Packit |
f0b94e |
upload_dir = os.getenv('MOZ_UPLOAD_DIR')
|
|
Packit |
f0b94e |
if self.test_config.get('gecko_profile') and not upload_dir:
|
|
Packit |
f0b94e |
LOG.critical("Profiling ignored because MOZ_UPLOAD_DIR was not"
|
|
Packit |
f0b94e |
" set")
|
|
Packit |
f0b94e |
if upload_dir and self.test_config.get('gecko_profile'):
|
|
Packit |
f0b94e |
self.gecko_profile = GeckoProfile(upload_dir,
|
|
Packit |
f0b94e |
self.browser_config,
|
|
Packit |
f0b94e |
self.test_config)
|
|
Packit |
f0b94e |
self.gecko_profile.update_env(self.env)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def clean(self):
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
mozfile.remove(self._tmp_dir)
|
|
Packit |
f0b94e |
except Exception as e:
|
|
Packit |
f0b94e |
LOG.info("Exception while removing profile directory: %s" % self._tmp_dir)
|
|
Packit |
f0b94e |
LOG.info(e)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
if self.gecko_profile:
|
|
Packit |
f0b94e |
self.gecko_profile.clean()
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def collect_or_clean_ccov(self, clean=False):
|
|
Packit |
f0b94e |
# NOTE: Currently only supported when running in production
|
|
Packit |
f0b94e |
if not self.browser_config.get('develop', False):
|
|
Packit |
f0b94e |
# first see if we an find any ccov files at the ccov output dirs
|
|
Packit |
f0b94e |
if clean:
|
|
Packit |
f0b94e |
LOG.info("Cleaning ccov files before starting the talos test")
|
|
Packit |
f0b94e |
else:
|
|
Packit |
f0b94e |
LOG.info("Collecting ccov files that were generated during the talos test")
|
|
Packit |
f0b94e |
gcov_prefix = os.getenv('GCOV_PREFIX', None)
|
|
Packit |
f0b94e |
js_ccov_dir = os.getenv('JS_CODE_COVERAGE_OUTPUT_DIR', None)
|
|
Packit |
f0b94e |
gcda_archive_folder_name = 'gcda-archive'
|
|
Packit |
f0b94e |
_gcda_files_found = []
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
for _ccov_env in [gcov_prefix, js_ccov_dir]:
|
|
Packit |
f0b94e |
if _ccov_env is not None:
|
|
Packit |
f0b94e |
# ccov output dir env vars exist; now search for gcda files to remove
|
|
Packit |
f0b94e |
_ccov_path = os.path.abspath(_ccov_env)
|
|
Packit |
f0b94e |
if os.path.exists(_ccov_path):
|
|
Packit |
f0b94e |
# now walk through and look for gcda files
|
|
Packit |
f0b94e |
LOG.info("Recursive search for gcda files in: %s" % _ccov_path)
|
|
Packit |
f0b94e |
for root, dirs, files in os.walk(_ccov_path):
|
|
Packit |
f0b94e |
for next_file in files:
|
|
Packit |
f0b94e |
if next_file.endswith(".gcda"):
|
|
Packit |
f0b94e |
# don't want to move or delete files in our 'gcda-archive'
|
|
Packit |
f0b94e |
if root.find(gcda_archive_folder_name) == -1:
|
|
Packit |
f0b94e |
_gcda_files_found.append(os.path.join(root, next_file))
|
|
Packit |
f0b94e |
else:
|
|
Packit |
f0b94e |
LOG.info("The ccov env var path doesn't exist: %s" % str(_ccov_path))
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
# now clean or collect gcda files accordingly
|
|
Packit |
f0b94e |
if clean:
|
|
Packit |
f0b94e |
# remove ccov data
|
|
Packit |
f0b94e |
LOG.info("Found %d gcda files to clean. Deleting..." % (len(_gcda_files_found)))
|
|
Packit |
f0b94e |
for _gcda in _gcda_files_found:
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
mozfile.remove(_gcda)
|
|
Packit |
f0b94e |
except Exception as e:
|
|
Packit |
f0b94e |
LOG.info("Exception while removing file: %s" % _gcda)
|
|
Packit |
f0b94e |
LOG.info(e)
|
|
Packit |
f0b94e |
LOG.info("Finished cleaning ccov gcda files")
|
|
Packit |
f0b94e |
else:
|
|
Packit |
f0b94e |
# copy gcda files to archive folder to be collected later
|
|
Packit |
f0b94e |
gcda_archive_top = os.path.join(gcov_prefix,
|
|
Packit |
f0b94e |
gcda_archive_folder_name,
|
|
Packit |
f0b94e |
self.test_config['name'])
|
|
Packit |
f0b94e |
LOG.info("Found %d gcda files to collect. Moving to gcda archive %s"
|
|
Packit |
f0b94e |
% (len(_gcda_files_found), str(gcda_archive_top)))
|
|
Packit |
f0b94e |
if not os.path.exists(gcda_archive_top):
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
os.makedirs(gcda_archive_top)
|
|
Packit |
f0b94e |
except OSError:
|
|
Packit |
f0b94e |
LOG.critical("Unable to make gcda archive folder %s" % gcda_archive_top)
|
|
Packit |
f0b94e |
for _gcda in _gcda_files_found:
|
|
Packit |
f0b94e |
# want to copy the existing directory strucutre but put it under archive-dir
|
|
Packit |
f0b94e |
# need to remove preceeding '/' from _gcda file name so can join the path
|
|
Packit |
f0b94e |
gcda_archive_file = os.path.join(gcov_prefix,
|
|
Packit |
f0b94e |
gcda_archive_folder_name,
|
|
Packit |
f0b94e |
self.test_config['name'],
|
|
Packit |
f0b94e |
_gcda.strip(gcov_prefix + "//"))
|
|
Packit |
f0b94e |
gcda_archive_dest = os.path.dirname(gcda_archive_file)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
# create archive folder, mirroring structure
|
|
Packit |
f0b94e |
if not os.path.exists(gcda_archive_dest):
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
os.makedirs(gcda_archive_dest)
|
|
Packit |
f0b94e |
except OSError:
|
|
Packit |
f0b94e |
LOG.critical("Unable to make archive folder %s" % gcda_archive_dest)
|
|
Packit |
f0b94e |
# now copy the file there
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
shutil.copy(_gcda, gcda_archive_dest)
|
|
Packit |
f0b94e |
except Exception as e:
|
|
Packit |
f0b94e |
LOG.info("Error copying %s to %s" % (str(_gcda), str(gcda_archive_dest)))
|
|
Packit |
f0b94e |
LOG.info(e)
|
|
Packit |
f0b94e |
LOG.info("Finished collecting ccov gcda files. Copied to: %s" % gcda_archive_top)
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def __enter__(self):
|
|
Packit |
f0b94e |
LOG.info('Initialising browser for %s test...'
|
|
Packit |
f0b94e |
% self.test_config['name'])
|
|
Packit |
f0b94e |
self._init_env()
|
|
Packit |
f0b94e |
self._init_profile()
|
|
Packit |
f0b94e |
try:
|
|
Packit |
f0b94e |
if not self.debug_mode and self.test_config['name'] != "damp":
|
|
Packit |
f0b94e |
self._run_profile()
|
|
Packit |
f0b94e |
except BaseException:
|
|
Packit |
f0b94e |
self.clean()
|
|
Packit |
f0b94e |
raise
|
|
Packit |
f0b94e |
self._init_gecko_profile()
|
|
Packit |
f0b94e |
LOG.info('Browser initialized.')
|
|
Packit |
f0b94e |
# remove ccov files before actual tests start
|
|
Packit |
f0b94e |
if self.browser_config.get('code_coverage', False):
|
|
Packit |
f0b94e |
# if the Firefox build was instrumented for ccov, initializing the browser
|
|
Packit |
f0b94e |
# will have caused ccov to output some gcda files; in order to have valid
|
|
Packit |
f0b94e |
# ccov data for the talos test we want to remove these files before starting
|
|
Packit |
f0b94e |
# the actual talos test(s)
|
|
Packit |
f0b94e |
self.collect_or_clean_ccov(clean=True)
|
|
Packit |
f0b94e |
return self
|
|
Packit |
f0b94e |
|
|
Packit |
f0b94e |
def __exit__(self, type, value, tb):
|
|
Packit |
f0b94e |
self.clean()
|