mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-06-01 09:29:26 +00:00
435 lines
17 KiB
Python
435 lines
17 KiB
Python
#!/usr/bin/env python
|
|
# ***** BEGIN LICENSE BLOCK *****
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
# You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
# ***** END LICENSE BLOCK *****
|
|
"""firefox_ui_updates.py
|
|
|
|
Author: Armen Zambrano G.
|
|
"""
|
|
import copy
|
|
import os
|
|
import re
|
|
import urllib
|
|
import urllib2
|
|
import sys
|
|
|
|
# load modules from parent dir
|
|
sys.path.insert(1, os.path.dirname(sys.path[0]))
|
|
|
|
from mozharness.base.script import PreScriptAction
|
|
from mozharness.mozilla.testing.firefox_ui_tests import FirefoxUITests
|
|
|
|
INSTALLER_SUFFIXES = ('.tar.bz2', '.zip', '.dmg', '.exe', '.apk', '.tar.gz')
|
|
|
|
class FirefoxUIUpdates(FirefoxUITests):
|
|
# This will be a list containing one item per release based on configs
|
|
# from tools/release/updates/*cfg
|
|
releases = None
|
|
channel = None
|
|
harness_extra_args = [
|
|
[['--update-allow-mar-channel'], {
|
|
'dest': 'update_allow_mar_channel',
|
|
'help': 'Additional MAR channel to be allowed for updates, e.g. '
|
|
'"firefox-mozilla-beta" for updating a release build to '
|
|
'the latest beta build.',
|
|
}],
|
|
[['--update-channel'], {
|
|
'dest': 'update_channel',
|
|
'help': 'Update channel to use.',
|
|
}],
|
|
[['--update-target-version'], {
|
|
'dest': 'update_target_version',
|
|
'help': 'Version of the updated build.',
|
|
}],
|
|
[['--update-target-buildid'], {
|
|
'dest': 'update_target_buildid',
|
|
'help': 'Build ID of the updated build',
|
|
}],
|
|
[['--symbols-path=SYMBOLS_PATH'], {
|
|
'dest': 'symbols_path',
|
|
'help': 'absolute path to directory containing breakpad '
|
|
'symbols, or the url of a zip file containing symbols.',
|
|
}],
|
|
]
|
|
|
|
|
|
def __init__(self):
|
|
config_options = [
|
|
[['--tools-repo'], {
|
|
'dest': 'tools_repo',
|
|
'default': 'http://hg.mozilla.org/build/tools',
|
|
'help': 'Which tools repo to check out',
|
|
}],
|
|
[['--tools-tag'], {
|
|
'dest': 'tools_tag',
|
|
'help': 'Which revision/tag to use for the tools repository.',
|
|
}],
|
|
[['--update-verify-config'], {
|
|
'dest': 'update_verify_config',
|
|
'help': 'Which update verify config file to use.',
|
|
}],
|
|
[['--this-chunk'], {
|
|
'dest': 'this_chunk',
|
|
'default': 1,
|
|
'help': 'What chunk of locales to process.',
|
|
}],
|
|
[['--total-chunks'], {
|
|
'dest': 'total_chunks',
|
|
'default': 1,
|
|
'help': 'Total chunks to dive the locales into.',
|
|
}],
|
|
[['--dry-run'], {
|
|
'dest': 'dry_run',
|
|
'default': False,
|
|
'help': 'Only show what was going to be tested.',
|
|
}],
|
|
[["--build-number"], {
|
|
"dest": "build_number",
|
|
"help": "Build number of release, eg: 2",
|
|
}],
|
|
# These are options when we don't use the releng update config file
|
|
[['--installer-url'], {
|
|
'dest': 'installer_url',
|
|
'help': 'Point to an installer to download and test against.',
|
|
}],
|
|
[['--installer-path'], {
|
|
'dest': 'installer_path',
|
|
'help': 'Point to an installer to test against.',
|
|
}],
|
|
] + copy.deepcopy(self.harness_extra_args)
|
|
|
|
super(FirefoxUIUpdates, self).__init__(
|
|
config_options=config_options,
|
|
all_actions=[
|
|
'clobber',
|
|
'checkout',
|
|
'create-virtualenv',
|
|
'determine-testing-configuration',
|
|
'run-tests',
|
|
],
|
|
append_env_variables_from_configs=True,
|
|
)
|
|
|
|
dirs = self.query_abs_dirs()
|
|
|
|
if self.config.get('tools_tag') is None:
|
|
# We want to make sure that anyone trying to reproduce a job will
|
|
# is using the exact tools tag for reproducibility's sake
|
|
self.fatal('Make sure to specify the --tools-tag')
|
|
|
|
self.tools_repo = self.config['tools_repo']
|
|
self.tools_tag = self.config['tools_tag']
|
|
|
|
if self.config.get('update_verify_config'):
|
|
self.updates_config_file = os.path.join(
|
|
dirs['abs_tools_dir'], 'release', 'updates',
|
|
self.config['update_verify_config']
|
|
)
|
|
else:
|
|
self.fatal('Make sure to specify --update-verify-config. '
|
|
'See under the directory release/updates in %s.' % self.tools_repo)
|
|
|
|
self.installer_url = self.config.get('installer_url')
|
|
self.installer_path = self.config.get('installer_path')
|
|
|
|
if self.installer_path:
|
|
if not os.path.exists(self.installer_path):
|
|
self.critical('Please make sure that the path to the installer exists.')
|
|
exit(1)
|
|
|
|
assert 'update_verify_config' in self.config or self.installer_url or self.installer_path, \
|
|
'Either specify --update-verify-config, --installer-url or --installer-path.'
|
|
|
|
|
|
def query_abs_dirs(self):
|
|
if self.abs_dirs:
|
|
return self.abs_dirs
|
|
abs_dirs = super(FirefoxUIUpdates, self).query_abs_dirs()
|
|
|
|
dirs = {
|
|
'abs_tools_dir': os.path.join(abs_dirs['abs_work_dir'], 'tools'),
|
|
}
|
|
|
|
abs_dirs.update(dirs)
|
|
self.abs_dirs = abs_dirs
|
|
return self.abs_dirs
|
|
|
|
def checkout(self):
|
|
'''
|
|
This checkouts the tools repo because it contains the configuration
|
|
files about which locales to test.
|
|
|
|
We also checkout firefox_ui_tests and update to the right branch
|
|
for it.
|
|
'''
|
|
super(FirefoxUIUpdates, self).checkout()
|
|
dirs = self.query_abs_dirs()
|
|
|
|
self.vcs_checkout(
|
|
repo=self.tools_repo,
|
|
dest=dirs['abs_tools_dir'],
|
|
revision=self.tools_tag,
|
|
vcs='hgtool'
|
|
)
|
|
|
|
|
|
def determine_testing_configuration(self):
|
|
'''
|
|
This method builds a testing matrix either based on an update verification
|
|
configuration file under the tools repo (release/updates/*.cfg)
|
|
OR it skips it when we use --installer-url --installer-path
|
|
|
|
Each release info line of the update verification files look similar to the following.
|
|
|
|
NOTE: This shows each pair of information as a new line but in reality
|
|
there is one white space separting them. We only show the values we care for.
|
|
|
|
release="38.0"
|
|
platform="Linux_x86_64-gcc3"
|
|
build_id="20150429135941"
|
|
locales="ach af ... zh-TW"
|
|
channel="beta-localtest"
|
|
from="/firefox/releases/38.0b9/linux-x86_64/%locale%/firefox-38.0b9.tar.bz2"
|
|
ftp_server_from="http://stage.mozilla.org/pub/mozilla.org"
|
|
|
|
We will store this information in self.releases as a list of releases.
|
|
|
|
NOTE: We will talk of full and quick releases. Full release info normally contains a subset
|
|
of all locales (except for the most recent releases). A quick release has all locales,
|
|
however, it misses the fields 'from' and 'ftp_server_from'.
|
|
Both pairs of information complement each other but differ in such manner.
|
|
'''
|
|
if self.installer_url or self.installer_path:
|
|
return
|
|
|
|
dirs = self.query_abs_dirs()
|
|
assert os.path.exists(dirs['abs_tools_dir']), \
|
|
"Without the tools/ checkout we can't use releng's config parser."
|
|
|
|
# Import the config parser
|
|
sys.path.insert(1, os.path.join(dirs['abs_tools_dir'], 'lib', 'python'))
|
|
from release.updates.verify import UpdateVerifyConfig
|
|
|
|
uvc = UpdateVerifyConfig()
|
|
uvc.read(self.updates_config_file)
|
|
self.channel = uvc.channel
|
|
|
|
# Filter out any releases that are less than Goanna 38
|
|
uvc.releases = [r for r in uvc.releases \
|
|
if int(r["release"].split('.')[0]) >= 38]
|
|
|
|
temp_releases = []
|
|
for rel_info in uvc.releases:
|
|
# This is the full release info
|
|
if 'from' in rel_info and rel_info['from'] is not None:
|
|
# Let's find the associated quick release which contains the remaining locales
|
|
# for all releases except for the most recent release which contain all locales
|
|
quick_release = uvc.getRelease(build_id=rel_info['build_id'], from_path=None)
|
|
if quick_release != {}:
|
|
rel_info['locales'] = sorted(rel_info['locales'] + quick_release['locales'])
|
|
temp_releases.append(rel_info)
|
|
|
|
uvc.releases = temp_releases
|
|
chunked_config = uvc.getChunk(
|
|
chunks=int(self.config['total_chunks']),
|
|
thisChunk=int(self.config['this_chunk'])
|
|
)
|
|
|
|
self.releases = chunked_config.releases
|
|
|
|
|
|
def _modify_url(self, rel_info):
|
|
# This is a temporary hack to find crash symbols. It should be replaced
|
|
# with something that doesn't make wild guesses about where symbol
|
|
# packages are.
|
|
# We want this:
|
|
# https://ftp.mozilla.org/pub/mozilla.org/firefox/candidates/40.0b1-candidates/build1/mac/en-US/Firefox%2040.0b1.crashreporter-symbols.zip
|
|
# https://ftp.mozilla.org/pub/mozilla.org//firefox/releases/40.0b1/mac/en-US/Firefox%2040.0b1.crashreporter-symbols.zip
|
|
installer_from = rel_info['from']
|
|
version = (re.search('/firefox/releases/(%s.*)\/.*\/.*\/.*' % rel_info['release'], installer_from)).group(1)
|
|
|
|
temp_from = installer_from.replace(version, '%s-candidates/build%s' % (version, self.config["build_number"]), 1).replace('releases', 'candidates')
|
|
|
|
return rel_info["ftp_server_from"] + urllib.quote(temp_from.replace('%locale%', 'en-US'))
|
|
|
|
|
|
def _query_symbols_url(self, installer_url):
|
|
for suffix in INSTALLER_SUFFIXES:
|
|
if installer_url.endswith(suffix):
|
|
symbols_url = installer_url[:-len(suffix)] + '.crashreporter-symbols.zip'
|
|
continue
|
|
|
|
if symbols_url:
|
|
if not symbols_url.startswith('http'):
|
|
return symbols_url
|
|
|
|
try:
|
|
# Let's see if the symbols are available
|
|
return urllib2.urlopen(symbols_url)
|
|
|
|
except urllib2.HTTPError, e:
|
|
self.warning("%s - %s" % (str(e), symbols_url))
|
|
return None
|
|
else:
|
|
self.fatal("Can't figure out symbols_url from installer_url %s!" % installer_url)
|
|
|
|
|
|
@PreScriptAction('run-tests')
|
|
def _pre_run_tests(self, action):
|
|
if self.releases is None and not (self.installer_url or self.installer_path):
|
|
self.fatal('You need to call --determine-testing-configuration as well.')
|
|
|
|
|
|
def _run_test(self, installer_path, symbols_url=None, update_channel=None, cleanup=True,
|
|
marionette_port=2828):
|
|
'''
|
|
All required steps for running the tests against an installer.
|
|
'''
|
|
dirs = self.query_abs_dirs()
|
|
env = self.query_env(avoid_host_env=True)
|
|
bin_dir = os.path.dirname(self.query_python_path())
|
|
fx_ui_tests_bin = os.path.join(bin_dir, 'firefox-ui-update')
|
|
goanna_log=os.path.join(dirs['abs_work_dir'], 'goanna.log')
|
|
|
|
# Build the command
|
|
cmd = [
|
|
fx_ui_tests_bin,
|
|
'--installer', installer_path,
|
|
# Log to stdout until tests are stable.
|
|
'--goanna-log=-',
|
|
'--address=localhost:%s' % marionette_port,
|
|
]
|
|
|
|
if symbols_url:
|
|
cmd += ['--symbols-path', symbols_url]
|
|
|
|
for arg in self.harness_extra_args:
|
|
dest = arg[1]['dest']
|
|
if dest in self.config:
|
|
cmd += [' '.join(arg[0]), self.config[dest]]
|
|
|
|
if update_channel:
|
|
cmd += ['--update-channel', update_channel]
|
|
|
|
return_code = self.run_command(cmd, cwd=dirs['abs_work_dir'],
|
|
output_timeout=300,
|
|
env=env)
|
|
|
|
# Return more output if we fail
|
|
if return_code != 0:
|
|
if os.path.exists(goanna_log):
|
|
contents = self.read_from_file(goanna_log, verbose=False)
|
|
self.warning('== Dumping goanna output ==')
|
|
self.warning(contents)
|
|
self.warning('== End of goanna output ==')
|
|
else:
|
|
# We're outputting to stdout with --goanna-log=- so there is not log to
|
|
# complaing about. Remove the commented line below when changing
|
|
# this behaviour.
|
|
# self.warning('No goanna.log was found: %s' % goanna_log)
|
|
pass
|
|
|
|
if cleanup:
|
|
for filepath in (installer_path, goanna_log):
|
|
if os.path.exists(filepath):
|
|
self.debug('Removing %s' % filepath)
|
|
os.remove(filepath)
|
|
|
|
return return_code
|
|
|
|
|
|
def run_tests(self):
|
|
dirs = self.query_abs_dirs()
|
|
|
|
if self.installer_url or self.installer_path:
|
|
if self.installer_url:
|
|
self.installer_path = self.download_file(
|
|
self.installer_url,
|
|
parent_dir=dirs['abs_work_dir']
|
|
)
|
|
|
|
symbols_url = self._query_symbols_url(installer_url=self.installer_path)
|
|
|
|
return self._run_test(
|
|
installer_path=self.installer_path,
|
|
symbols_url=symbols_url,
|
|
cleanup=False
|
|
)
|
|
|
|
else:
|
|
results = {}
|
|
|
|
for rel_info in sorted(self.releases, key=lambda release: release['build_id']):
|
|
build_id = rel_info['build_id']
|
|
results[build_id] = {}
|
|
|
|
self.info('About to run %s %s - %s locales' % (
|
|
build_id,
|
|
rel_info['from'],
|
|
len(rel_info['locales'])
|
|
))
|
|
|
|
if self.config['dry_run']:
|
|
continue
|
|
|
|
# Each locale gets a fresh port to avoid address in use errors in case of
|
|
# tests that time out unexpectedly.
|
|
marionette_port = 2827
|
|
for locale in rel_info['locales']:
|
|
self.info("Running %s %s" % (build_id, locale))
|
|
|
|
# Safe temp hack to determine symbols URL from en-US build1 in the candidates dir
|
|
ftp_candidates_installer_url = self._modify_url(rel_info)
|
|
symbols_url = self._query_symbols_url(installer_url=ftp_candidates_installer_url)
|
|
|
|
# Determine from where to download the file
|
|
url = '%s/%s' % (
|
|
rel_info['ftp_server_from'],
|
|
urllib.quote(rel_info['from'].replace('%locale%', locale))
|
|
)
|
|
installer_path = self.download_file(
|
|
url=url,
|
|
parent_dir=dirs['abs_work_dir']
|
|
)
|
|
|
|
marionette_port += 1
|
|
|
|
retcode = self._run_test(
|
|
installer_path=installer_path,
|
|
symbols_url=symbols_url,
|
|
update_channel=self.channel,
|
|
marionette_port=marionette_port)
|
|
|
|
if retcode != 0:
|
|
self.warning('FAIL: firefox-ui-update has failed.' )
|
|
self.info('You can run the following command on the same machine to reproduce the issue:')
|
|
self.info('python scripts/firefox_ui_updates.py --cfg generic_releng_config.py '
|
|
'--firefox-ui-branch %s --update-verify-config %s '
|
|
'--tools-tag %s --installer-url %s '
|
|
'--determine-testing-configuration --run-tests '
|
|
% (self.firefox_ui_branch, self.updates_config_file, self.tools_tag, url))
|
|
self.info('If you want to run this on your development machine:')
|
|
self.info('python scripts/firefox_ui_updates.py '
|
|
'--firefox-ui-branch %s --update-verify-config %s '
|
|
'--tools-tag %s --installer-url %s '
|
|
'--cfg developer_config.py '
|
|
% (self.firefox_ui_branch, self.updates_config_file, self.tools_tag, url))
|
|
|
|
results[build_id][locale] = retcode
|
|
|
|
self.info("Firefox UI update tests failed locales:")
|
|
for build_id in sorted(results.keys()):
|
|
self.info(build_id)
|
|
failed_locales = []
|
|
for locale in sorted(results[build_id].keys()):
|
|
if results[build_id][locale] != 0:
|
|
failed_locales.append(locale)
|
|
self.info(" %s" % (', '.join(failed_locales)))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
myScript = FirefoxUIUpdates()
|
|
myScript.run_and_exit()
|