1#!/usr/bin/env python 2# Copyright 2015 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6import argparse 7import json 8import logging 9import os 10import re 11import shutil 12import subprocess 13import sys 14import tempfile 15import time 16 17from hooks import install 18 19from py_utils import binary_manager 20from py_utils import dependency_util 21from py_utils import xvfb 22 23 24# Path to dependency manager config containing chrome binary data. 25CHROME_BINARIES_CONFIG = dependency_util.ChromeBinariesConfigPath() 26 27CHROME_CONFIG_URL = ( 28 'https://code.google.com/p/chromium/codesearch#chromium/src/third_party/' 29 'catapult/py_utils/py_utils/chrome_binaries.json') 30 31# Default port to run on if not auto-assigning from OS 32DEFAULT_PORT = '8111' 33 34# Mapping of sys.platform -> platform-specific names and paths. 35PLATFORM_MAPPING = { 36 'linux2': { 37 'omaha': 'linux', 38 'prefix': 'Linux_x64', 39 'zip_prefix': 'linux', 40 'chromepath': 'chrome-linux/chrome' 41 }, 42 'win32': { 43 'omaha': 'win', 44 'prefix': 'Win', 45 'zip_prefix': 'win32', 46 'chromepath': 'chrome-win32\\chrome.exe', 47 }, 48 'darwin': { 49 'omaha': 'mac', 50 'prefix': 'Mac', 51 'zip_prefix': 'mac', 52 'chromepath': ('chrome-mac/Chromium.app/Contents/MacOS/Chromium'), 53 'version_path': 'chrome-mac/Chromium.app/Contents/Versions/', 54 'additional_paths': [ 55 ('chrome-mac/Chromium.app/Contents/Versions/%VERSION%/' 56 'Chromium Helper.app/Contents/MacOS/Chromium Helper'), 57 ], 58 }, 59} 60 61 62def IsDepotToolsPath(path): 63 return os.path.isfile(os.path.join(path, 'gclient')) 64 65 66def FindDepotTools(): 67 # Check if depot_tools is already in PYTHONPATH 68 for path in sys.path: 69 if path.rstrip(os.sep).endswith('depot_tools') and IsDepotToolsPath(path): 70 return path 71 72 # Check if depot_tools is in the path 73 for path in os.environ['PATH'].split(os.pathsep): 74 if IsDepotToolsPath(path): 75 return path.rstrip(os.sep) 76 77 return None 78 79 80def GetLocalChromePath(path_from_command_line): 81 if path_from_command_line: 82 return path_from_command_line 83 84 if sys.platform == 'darwin': # Mac 85 chrome_path = ( 86 '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome') 87 if os.path.isfile(chrome_path): 88 return chrome_path 89 elif sys.platform.startswith('linux'): 90 found = False 91 try: 92 with open(os.devnull, 'w') as devnull: 93 found = subprocess.call(['google-chrome', '--version'], 94 stdout=devnull, stderr=devnull) == 0 95 except OSError: 96 pass 97 if found: 98 return 'google-chrome' 99 elif sys.platform == 'win32': 100 search_paths = [os.getenv('PROGRAMFILES(X86)'), 101 os.getenv('PROGRAMFILES'), 102 os.getenv('LOCALAPPDATA')] 103 chrome_path = os.path.join('Google', 'Chrome', 'Application', 'chrome.exe') 104 for search_path in search_paths: 105 test_path = os.path.join(search_path, chrome_path) 106 if os.path.isfile(test_path): 107 return test_path 108 return None 109 110 111def Main(argv): 112 try: 113 parser = argparse.ArgumentParser( 114 description='Run dev_server tests for a project.') 115 parser.add_argument('--chrome_path', type=str, 116 help='Path to Chrome browser binary.') 117 parser.add_argument('--no-use-local-chrome', 118 dest='use_local_chrome', action='store_false') 119 parser.add_argument( 120 '--no-install-hooks', dest='install_hooks', action='store_false') 121 parser.add_argument('--tests', type=str, 122 help='Set of tests to run (tracing or perf_insights)') 123 parser.add_argument('--channel', type=str, default='stable', 124 help='Chrome channel to run (stable or canary)') 125 parser.add_argument('--presentation-json', type=str, 126 help='Recipe presentation-json output file path') 127 parser.set_defaults(install_hooks=True) 128 parser.set_defaults(use_local_chrome=True) 129 args = parser.parse_args(argv[1:]) 130 131 if args.install_hooks: 132 install.InstallHooks() 133 134 user_data_dir = tempfile.mkdtemp() 135 tmpdir = None 136 xvfb_process = None 137 138 server_path = os.path.join(os.path.dirname( 139 os.path.abspath(__file__)), os.pardir, 'bin', 'run_dev_server') 140 # TODO(anniesullie): Make OS selection of port work on Windows. See #1235. 141 if sys.platform == 'win32': 142 port = DEFAULT_PORT 143 else: 144 port = '0' 145 server_command = [server_path, '--no-install-hooks', '--port', port] 146 if sys.platform.startswith('win'): 147 server_command = ['python.exe'] + server_command 148 print "Starting dev_server..." 149 server_process = subprocess.Popen( 150 server_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, 151 bufsize=1) 152 time.sleep(1) 153 if sys.platform != 'win32': 154 output = server_process.stderr.readline() 155 port = re.search( 156 r'Now running on http://127.0.0.1:([\d]+)', output).group(1) 157 158 chrome_info = None 159 if args.use_local_chrome: 160 chrome_path = GetLocalChromePath(args.chrome_path) 161 if not chrome_path: 162 logging.error('Could not find path to chrome.') 163 sys.exit(1) 164 chrome_info = 'with command `%s`' % chrome_path 165 else: 166 channel = args.channel 167 if sys.platform == 'linux2' and channel == 'canary': 168 channel = 'dev' 169 assert channel in ['stable', 'beta', 'dev', 'canary'] 170 171 print 'Fetching the %s chrome binary via the binary_manager.' % channel 172 chrome_manager = binary_manager.BinaryManager([CHROME_BINARIES_CONFIG]) 173 arch, os_name = dependency_util.GetOSAndArchForCurrentDesktopPlatform() 174 chrome_path, version = chrome_manager.FetchPathWithVersion( 175 'chrome_%s' % channel, arch, os_name) 176 if xvfb.ShouldStartXvfb(): 177 xvfb_process = xvfb.StartXvfb() 178 chrome_info = 'version %s from channel %s' % (version, channel) 179 chrome_command = [ 180 chrome_path, 181 '--user-data-dir=%s' % user_data_dir, 182 '--no-sandbox', 183 '--no-experiments', 184 '--no-first-run', 185 '--noerrdialogs', 186 '--window-size=1280,1024', 187 ('http://localhost:%s/%s/tests.html?' % (port, args.tests)) + 188 'headless=true&testTypeToRun=all', 189 ] 190 print "Starting Chrome %s..." % chrome_info 191 chrome_process = subprocess.Popen( 192 chrome_command, stdout=sys.stdout, stderr=sys.stderr) 193 print 'chrome process command: %s' % ' '.join(chrome_command) 194 print "Waiting for tests to finish..." 195 server_out, server_err = server_process.communicate() 196 print "Killing Chrome..." 197 if sys.platform == 'win32': 198 # Use taskkill on Windows to make sure Chrome and all subprocesses are 199 # killed. 200 subprocess.call(['taskkill', '/F', '/T', '/PID', str(chrome_process.pid)]) 201 else: 202 chrome_process.kill() 203 if server_process.returncode != 0: 204 logging.error('Tests failed!') 205 logging.error('Server stderr:') 206 logging.error(server_err) 207 logging.error('Server stdout:') 208 logging.error(server_out) 209 else: 210 print server_out 211 if args.presentation_json: 212 with open(args.presentation_json, 'w') as recipe_out: 213 # Add a link to the buildbot status for the step saying which version 214 # of Chrome the test ran on. The actual linking feature is not used, 215 # but there isn't a way to just add text. 216 link_name = 'Chrome Version %s' % version 217 presentation_info = {'links': {link_name: CHROME_CONFIG_URL}} 218 json.dump(presentation_info, recipe_out) 219 finally: 220 # Wait for Chrome to be killed before deleting temp Chrome dir. Only have 221 # this timing issue on Windows. 222 if sys.platform == 'win32': 223 time.sleep(5) 224 if tmpdir: 225 try: 226 shutil.rmtree(tmpdir) 227 shutil.rmtree(user_data_dir) 228 except OSError as e: 229 logging.error('Error cleaning up temp dirs %s and %s: %s', 230 tmpdir, user_data_dir, e) 231 if xvfb_process: 232 xvfb_process.kill() 233 234 sys.exit(server_process.returncode) 235