| #!/usr/bin/env python |
| # Copyright 2015 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 argparse |
| import json |
| import logging |
| import os |
| import re |
| import shutil |
| import stat |
| import subprocess |
| import sys |
| import tempfile |
| import time |
| import urllib2 |
| import zipfile |
| |
| from hooks import install |
| |
| from catapult_base import xvfb |
| |
| # URL on omahaproxy.appspot.com which lists the current version for the os |
| # and channel. |
| VERSION_LOOKUP_URL = 'https://omahaproxy.appspot.com/all?os=%s&channel=%s' |
| |
| # URL on omahaproxy.appspot.com which looks up base positions from versions. |
| BASE_POS_LOOKUP_URL = 'http://omahaproxy.appspot.com/revision.json?version=%s' |
| |
| # URL on cloud storage which looks up the chromium download url from base pos. |
| CLOUD_STORAGE_LOOKUP_URL = ('https://www.googleapis.com/storage/v1/b/' |
| 'chromium-browser-snapshots/o?delimiter=/&prefix=' |
| '%s/%s&fields=items(kind,mediaLink,metadata,name,' |
| 'size,updated),kind,prefixes,nextPageToken') |
| |
| # URL on cloud storage to download chromium at a base pos from. |
| CLOUD_STORAGE_DOWNLOAD_URL = ('https://www.googleapis.com/download/storage/v1/b' |
| '/chromium-browser-snapshots/o/%s%%2F%s%%2F' |
| 'chrome-%s.zip?alt=media') |
| |
| # URL in cloud storage to download Chrome zip from. |
| CLOUDSTORAGE_URL = ('https://commondatastorage.googleapis.com/chrome-unsigned' |
| '/desktop-W15K3Y/%s/%s/chrome-%s.zip') |
| |
| # Default port to run on if not auto-assigning from OS |
| DEFAULT_PORT = '8111' |
| |
| # Mapping of sys.platform -> platform-specific names and paths. |
| PLATFORM_MAPPING = { |
| 'linux2': { |
| 'omaha': 'linux', |
| 'prefix': 'Linux_x64', |
| 'zip_prefix': 'linux', |
| 'chromepath': 'chrome-linux/chrome' |
| }, |
| 'win32': { |
| 'omaha': 'win', |
| 'prefix': 'Win', |
| 'zip_prefix': 'win32', |
| 'chromepath': 'chrome-win32\\chrome.exe', |
| }, |
| 'darwin': { |
| 'omaha': 'mac', |
| 'prefix': 'Mac', |
| 'zip_prefix': 'mac', |
| 'chromepath': ('chrome-mac/Chromium.app/Contents/MacOS/Chromium'), |
| 'version_path': 'chrome-mac/Chromium.app/Contents/Versions/', |
| 'additional_paths': [ |
| ('chrome-mac/Chromium.app/Contents/Versions/%VERSION%/' |
| 'Chromium Helper.app/Contents/MacOS/Chromium Helper'), |
| ], |
| }, |
| } |
| |
| |
| def IsDepotToolsPath(path): |
| return os.path.isfile(os.path.join(path, 'gclient')) |
| |
| |
| def FindDepotTools(): |
| # Check if depot_tools is already in PYTHONPATH |
| for path in sys.path: |
| if path.rstrip(os.sep).endswith('depot_tools') and IsDepotToolsPath(path): |
| return path |
| |
| # Check if depot_tools is in the path |
| for path in os.environ['PATH'].split(os.pathsep): |
| if IsDepotToolsPath(path): |
| return path.rstrip(os.sep) |
| |
| return None |
| |
| |
| def DownloadChromium(channel): |
| """ |
| Gets the version of Chrome current for the given channel from omahaproxy, then |
| follows instructions for downloading a prebuilt version of chromium from the |
| commit at the branch cut for that version. This downloads a chromium binary |
| which does not have any commits merged onto the branch. It is close to the |
| released Chrome, but not exact. Downloading the released Chrome is not |
| supported. |
| https://www.chromium.org/getting-involved/download-chromium |
| """ |
| # Get the version for the current channel from omahaproxy |
| platform_data = PLATFORM_MAPPING[sys.platform] |
| omaha_platform = platform_data['omaha'] |
| version_lookup_url = VERSION_LOOKUP_URL % (omaha_platform, channel) |
| print 'Getting version from %s' % version_lookup_url |
| response = urllib2.urlopen(version_lookup_url, timeout=120) |
| version = response.readlines()[1].split(',')[2] |
| |
| # Get the base position for that version from omahaproxy |
| base_pos_lookup_url = BASE_POS_LOOKUP_URL % version |
| print 'Getting base_pos from %s' % base_pos_lookup_url |
| response = urllib2.urlopen(base_pos_lookup_url, timeout=120) |
| base_pos = json.load(response)['chromium_base_position'] |
| |
| # Find the build from that base position in cloud storage. If it's not found, |
| # decrement base position until one is found. |
| cloud_storage_lookup_url = CLOUD_STORAGE_LOOKUP_URL % ( |
| platform_data['prefix'], base_pos) |
| download_url = None |
| while not download_url: |
| print 'Getting download url from %s' % cloud_storage_lookup_url |
| response = urllib2.urlopen(cloud_storage_lookup_url, timeout=120) |
| prefixes = json.load(response).get('prefixes') |
| if prefixes: |
| download_url = CLOUD_STORAGE_DOWNLOAD_URL % ( |
| platform_data['prefix'], base_pos, platform_data['zip_prefix']) |
| break |
| base_pos = int(base_pos) - 1 |
| cloud_storage_lookup_url = CLOUD_STORAGE_LOOKUP_URL % ( |
| platform_data['prefix'], base_pos) |
| |
| print 'Approximating Chrome %s with chromium from base position %s.' % ( |
| version, base_pos) |
| print 'Downloading from %s' % download_url |
| |
| tmpdir = tempfile.mkdtemp() |
| zip_path = os.path.join(tmpdir, 'chrome.zip') |
| with open(zip_path, 'wb') as local_file: |
| local_file.write(urllib2.urlopen(download_url, timeout=600).read()) |
| zf = zipfile.ZipFile(zip_path) |
| zf.extractall(path=tmpdir) |
| return tmpdir, version, download_url |
| |
| |
| def GetLocalChromePath(path_from_command_line): |
| if path_from_command_line: |
| return path_from_command_line |
| |
| if sys.platform == 'darwin': # Mac |
| chrome_path = ( |
| '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome') |
| if os.path.isfile(chrome_path): |
| return chrome_path |
| elif sys.platform.startswith('linux'): |
| found = False |
| try: |
| with open(os.devnull, 'w') as devnull: |
| found = subprocess.call(['google-chrome', '--version'], |
| stdout=devnull, stderr=devnull) == 0 |
| except OSError: |
| pass |
| if found: |
| return 'google-chrome' |
| elif sys.platform == 'win32': |
| search_paths = [os.getenv('PROGRAMFILES(X86)'), |
| os.getenv('PROGRAMFILES'), |
| os.getenv('LOCALAPPDATA')] |
| chrome_path = os.path.join('Google', 'Chrome', 'Application', 'chrome.exe') |
| for search_path in search_paths: |
| test_path = os.path.join(search_path, chrome_path) |
| if os.path.isfile(test_path): |
| return test_path |
| return None |
| |
| |
| def Main(argv): |
| try: |
| parser = argparse.ArgumentParser( |
| description='Run dev_server tests for a project.') |
| parser.add_argument('--chrome_path', type=str, |
| help='Path to Chrome browser binary.') |
| parser.add_argument('--no-use-local-chrome', |
| dest='use_local_chrome', action='store_false') |
| parser.add_argument( |
| '--no-install-hooks', dest='install_hooks', action='store_false') |
| parser.add_argument('--tests', type=str, |
| help='Set of tests to run (tracing or perf_insights)') |
| parser.add_argument('--channel', type=str, default='stable', |
| help='Chrome channel to run (stable or canary)') |
| parser.add_argument('--presentation-json', type=str, |
| help='Recipe presentation-json output file path') |
| parser.set_defaults(install_hooks=True) |
| parser.set_defaults(use_local_chrome=True) |
| args = parser.parse_args(argv[1:]) |
| |
| if args.install_hooks: |
| install.InstallHooks() |
| |
| platform_data = PLATFORM_MAPPING[sys.platform] |
| user_data_dir = tempfile.mkdtemp() |
| tmpdir = None |
| xvfb_process = None |
| |
| server_path = os.path.join(os.path.dirname( |
| os.path.abspath(__file__)), os.pardir, 'bin', 'run_dev_server') |
| # TODO(anniesullie): Make OS selection of port work on Windows. See #1235. |
| if sys.platform == 'win32': |
| port = DEFAULT_PORT |
| else: |
| port = '0' |
| server_command = [server_path, '--no-install-hooks', '--port', port] |
| if sys.platform.startswith('win'): |
| server_command = ['python.exe'] + server_command |
| print "Starting dev_server..." |
| server_process = subprocess.Popen( |
| server_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, |
| bufsize=1) |
| time.sleep(1) |
| if sys.platform != 'win32': |
| output = server_process.stderr.readline() |
| port = re.search( |
| r'Now running on http://127.0.0.1:([\d]+)', output).group(1) |
| |
| chrome_info = None |
| if args.use_local_chrome: |
| chrome_path = GetLocalChromePath(args.chrome_path) |
| if not chrome_path: |
| logging.error('Could not find path to chrome.') |
| sys.exit(1) |
| chrome_info = 'with command `%s`' % chrome_path |
| else: |
| channel = args.channel |
| if sys.platform == 'linux2' and channel == 'canary': |
| channel = 'dev' |
| assert channel in ['stable', 'beta', 'dev', 'canary'] |
| |
| |
| tmpdir, version, download_url = DownloadChromium(channel) |
| if xvfb.ShouldStartXvfb(): |
| xvfb_process = xvfb.StartXvfb() |
| chrome_path = os.path.join( |
| tmpdir, platform_data['chromepath']) |
| os.chmod(chrome_path, os.stat(chrome_path).st_mode | stat.S_IEXEC) |
| # On Mac, we need to update a file with the version in the path, and |
| # the version we downloaded could be slightly different than what we |
| # requested. Update it. |
| if platform_data.get('version_path'): |
| contents = os.listdir( |
| os.path.join(tmpdir, platform_data['version_path'])) |
| for path in contents: |
| if re.match(r'\d+\.\d+\.\d+\.\d+', path): |
| version = path |
| if platform_data.get('additional_paths'): |
| for path in platform_data.get('additional_paths'): |
| path = path.replace('%VERSION%', version) |
| path = os.path.join(tmpdir, path) |
| if os.path.exists(path): |
| os.chmod(path, os.stat(path).st_mode | stat.S_IEXEC) |
| chrome_info = version |
| chrome_command = [ |
| chrome_path, |
| '--user-data-dir=%s' % user_data_dir, |
| '--no-sandbox', |
| '--no-experiments', |
| '--no-first-run', |
| '--noerrdialogs', |
| '--window-size=1280,1024', |
| ('http://localhost:%s/%s/tests.html?' % (port, args.tests)) + |
| 'headless=true&testTypeToRun=all', |
| ] |
| print "Starting Chrome %s..." % chrome_info |
| chrome_process = subprocess.Popen( |
| chrome_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
| print "Waiting for tests to finish..." |
| server_out, server_err = server_process.communicate() |
| print "Killing Chrome..." |
| if sys.platform == 'win32': |
| # Use taskkill on Windows to make sure Chrome and all subprocesses are |
| # killed. |
| subprocess.call(['taskkill', '/F', '/T', '/PID', str(chrome_process.pid)]) |
| else: |
| chrome_process.kill() |
| if server_process.returncode != 0: |
| logging.error('Tests failed!') |
| logging.error('Server stderr:') |
| logging.error(server_err) |
| logging.error('Server stdout:') |
| logging.error(server_out) |
| else: |
| print server_out |
| if args.presentation_json: |
| with open(args.presentation_json, 'w') as recipe_out: |
| # Add a link to the buildbot status for the step saying which version |
| # of Chrome the test ran on. The actual linking feature is not used, |
| # but there isn't a way to just add text. |
| link_name = 'Chrome Version %s' % version |
| presentation_info = {'links': {link_name: download_url}} |
| json.dump(presentation_info, recipe_out) |
| finally: |
| # Wait for Chrome to be killed before deleting temp Chrome dir. Only have |
| # this timing issue on Windows. |
| if sys.platform == 'win32': |
| time.sleep(5) |
| if tmpdir: |
| try: |
| shutil.rmtree(tmpdir) |
| shutil.rmtree(user_data_dir) |
| except OSError as e: |
| logging.error('Error cleaning up temp dirs %s and %s: %s', |
| tmpdir, user_data_dir, e) |
| if xvfb_process: |
| xvfb_process.kill() |
| |
| sys.exit(server_process.returncode) |