• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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