1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
|
#!/usr/bin/env python
# Copyright 2012 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import os.path
import re
import shutil
import sys
import tempfile
import time
import urlparse
import browserprocess
class LaunchFailure(Exception):
pass
def GetPlatform():
if sys.platform == 'darwin':
platform = 'mac'
elif sys.platform.startswith('linux'):
platform = 'linux'
elif sys.platform in ('cygwin', 'win32'):
platform = 'windows'
else:
raise LaunchFailure('Unknown platform: %s' % sys.platform)
return platform
PLATFORM = GetPlatform()
def SelectRunCommand():
# The subprocess module added support for .kill in Python 2.6
assert (sys.version_info[0] >= 3 or (sys.version_info[0] == 2 and
sys.version_info[1] >= 6))
if PLATFORM == 'linux':
return browserprocess.RunCommandInProcessGroup
else:
return browserprocess.RunCommandWithSubprocess
RunCommand = SelectRunCommand()
def RemoveDirectory(path):
retry = 5
sleep_time = 0.25
while True:
try:
shutil.rmtree(path)
except Exception:
# Windows processes sometime hang onto files too long
if retry > 0:
retry -= 1
time.sleep(sleep_time)
sleep_time *= 2
else:
# No luck - don't mask the error
raise
else:
# succeeded
break
# In Windows, subprocess seems to have an issue with file names that
# contain spaces.
def EscapeSpaces(path):
if PLATFORM == 'windows' and ' ' in path:
return '"%s"' % path
return path
def MakeEnv(options):
env = dict(os.environ)
# Enable PPAPI Dev interfaces for testing.
env['NACL_ENABLE_PPAPI_DEV'] = str(options.enable_ppapi_dev)
if options.debug:
env['NACL_PLUGIN_DEBUG'] = '1'
# env['NACL_SRPC_DEBUG'] = '1'
return env
class BrowserLauncher(object):
WAIT_TIME = 20
WAIT_STEPS = 80
SLEEP_TIME = float(WAIT_TIME) / WAIT_STEPS
def __init__(self, options):
self.options = options
self.profile = None
self.binary = None
self.tool_log_dir = None
def KnownPath(self):
raise NotImplementedError
def BinaryName(self):
raise NotImplementedError
def CreateProfile(self):
raise NotImplementedError
def MakeCmd(self, url, host, port):
raise NotImplementedError
def CreateToolLogDir(self):
self.tool_log_dir = tempfile.mkdtemp(prefix='vglogs_')
return self.tool_log_dir
def FindBinary(self):
if self.options.browser_path:
return self.options.browser_path
else:
path = self.KnownPath()
if path is None or not os.path.exists(path):
raise LaunchFailure('Cannot find the browser directory')
binary = os.path.join(path, self.BinaryName())
if not os.path.exists(binary):
raise LaunchFailure('Cannot find the browser binary')
return binary
def WaitForProcessDeath(self):
self.browser_process.Wait(self.WAIT_STEPS, self.SLEEP_TIME)
def Cleanup(self):
self.browser_process.Kill()
RemoveDirectory(self.profile)
if self.tool_log_dir is not None:
RemoveDirectory(self.tool_log_dir)
def MakeProfileDirectory(self):
self.profile = tempfile.mkdtemp(prefix='browserprofile_')
return self.profile
def SetStandardStream(self, env, var_name, redirect_file, is_output):
if redirect_file is None:
return
file_prefix = 'file:'
dev_prefix = 'dev:'
debug_warning = 'DEBUG_ONLY:'
# logic must match src/trusted/service_runtime/nacl_resource.*
# resource specification notation. file: is the default
# interpretation, so we must have an exhaustive list of
# alternative schemes accepted. if we remove the file-is-default
# interpretation, replace with
# is_file = redirect_file.startswith(file_prefix)
# and remove the list of non-file schemes.
is_file = (not (redirect_file.startswith(dev_prefix) or
redirect_file.startswith(debug_warning + dev_prefix)))
if is_file:
if redirect_file.startswith(file_prefix):
bare_file = redirect_file[len(file_prefix)]
else:
bare_file = redirect_file
# why always abspath? does chrome chdir or might it in the
# future? this means we do not test/use the relative path case.
redirect_file = file_prefix + os.path.abspath(bare_file)
else:
bare_file = None # ensure error if used without checking is_file
env[var_name] = redirect_file
if is_output:
# sel_ldr appends program output to the file so we need to clear it
# in order to get the stable result.
if is_file:
if os.path.exists(bare_file):
os.remove(bare_file)
parent_dir = os.path.dirname(bare_file)
# parent directory may not exist.
if not os.path.exists(parent_dir):
os.makedirs(parent_dir)
def Launch(self, cmd, env):
browser_path = cmd[0]
if not os.path.exists(browser_path):
raise LaunchFailure('Browser does not exist %r'% browser_path)
if not os.access(browser_path, os.X_OK):
raise LaunchFailure('Browser cannot be executed %r (Is this binary on an '
'NFS volume?)' % browser_path)
if self.options.sel_ldr:
env['NACL_SEL_LDR'] = self.options.sel_ldr
if self.options.sel_ldr_bootstrap:
env['NACL_SEL_LDR_BOOTSTRAP'] = self.options.sel_ldr_bootstrap
if self.options.irt_library:
env['NACL_IRT_LIBRARY'] = self.options.irt_library
self.SetStandardStream(env, 'NACL_EXE_STDIN',
self.options.nacl_exe_stdin, False)
self.SetStandardStream(env, 'NACL_EXE_STDOUT',
self.options.nacl_exe_stdout, True)
self.SetStandardStream(env, 'NACL_EXE_STDERR',
self.options.nacl_exe_stderr, True)
print('ENV:', ' '.join(['='.join(pair) for pair in env.items()]))
print('LAUNCHING: %s' % ' '.join(cmd))
sys.stdout.flush()
self.browser_process = RunCommand(cmd, env=env)
def IsRunning(self):
return self.browser_process.IsRunning()
def GetReturnCode(self):
return self.browser_process.GetReturnCode()
def Run(self, url, host, port):
self.binary = EscapeSpaces(self.FindBinary())
self.profile = self.CreateProfile()
if self.options.tool is not None:
self.tool_log_dir = self.CreateToolLogDir()
cmd = self.MakeCmd(url, host, port)
self.Launch(cmd, MakeEnv(self.options))
def EnsureDirectory(path):
if not os.path.exists(path):
os.makedirs(path)
def EnsureDirectoryForFile(path):
EnsureDirectory(os.path.dirname(path))
class ChromeLauncher(BrowserLauncher):
def KnownPath(self):
if PLATFORM == 'linux':
# TODO(ncbray): look in path?
return '/opt/google/chrome'
elif PLATFORM == 'mac':
return '/Applications/Google Chrome.app/Contents/MacOS'
else:
homedir = os.path.expanduser('~')
path = os.path.join(homedir, r'AppData\Local\Google\Chrome\Application')
return path
def BinaryName(self):
if PLATFORM == 'mac':
return 'Google Chrome'
elif PLATFORM == 'windows':
return 'chrome.exe'
else:
return 'chrome'
def MakeEmptyJSONFile(self, path):
EnsureDirectoryForFile(path)
f = open(path, 'w')
f.write('{}')
f.close()
def CreateProfile(self):
profile = self.MakeProfileDirectory()
# Squelch warnings by creating bogus files.
self.MakeEmptyJSONFile(os.path.join(profile, 'Default', 'Preferences'))
self.MakeEmptyJSONFile(os.path.join(profile, 'Local State'))
return profile
def NetLogName(self):
return os.path.join(self.profile, 'netlog.json')
def MakeCmd(self, url, host, port):
cmd = [self.binary,
# --enable-logging enables stderr output from Chromium subprocesses
# on Windows (see
# https://code.google.com/p/chromium/issues/detail?id=171836)
'--enable-logging',
# This prevents Chrome from making "hidden" network requests at
# startup and navigation. These requests could be a source of
# non-determinism, and they also add noise to the netlogs.
'--disable-features=NetworkPrediction',
# This is speculative, sync should not occur with a clean profile.
'--disable-sync',
'--no-first-run',
'--no-default-browser-check',
'--log-level=1',
'--disable-default-apps',
# Suppress metrics reporting. This prevents misconfigured bots,
# people testing at their desktop, etc from poisoning the UMA data.
'--metrics-recording-only',
# Chrome explicitly blacklists some ports as "unsafe" because
# certain protocols use them. Chrome gives an error like this:
# Error 312 (net::ERR_UNSAFE_PORT): Unknown error
# Unfortunately, the browser tester can randomly choose a
# blacklisted port. To work around this, the tester whitelists
# whatever port it is using.
'--explicitly-allowed-ports=%d' % port,
'--user-data-dir=%s' % self.profile]
# Log network requests to assist debugging.
cmd.append('--log-net-log=%s' % self.NetLogName())
if self.options.ppapi_plugin is None:
cmd.append('--enable-nacl')
disable_sandbox = False
# Chrome process can't access file within sandbox
disable_sandbox |= self.options.nacl_exe_stdin is not None
disable_sandbox |= self.options.nacl_exe_stdout is not None
disable_sandbox |= self.options.nacl_exe_stderr is not None
if disable_sandbox:
cmd.append('--no-sandbox')
else:
cmd.append('--allow-command-line-plugins')
cmd.append('--register-pepper-plugins=%s;%s'
% (self.options.ppapi_plugin,
self.options.ppapi_plugin_mimetype))
cmd.append('--no-sandbox')
if self.options.browser_extensions:
cmd.append('--load-extension=%s' %
','.join(self.options.browser_extensions))
cmd.append('--enable-experimental-extension-apis')
if self.options.enable_crash_reporter:
cmd.append('--enable-crash-reporter-for-testing')
if self.options.tool == 'memcheck':
cmd = ['src/third_party/valgrind/memcheck.sh',
'-v',
'--xml=yes',
'--leak-check=no',
'--gen-suppressions=all',
'--num-callers=30',
'--trace-children=yes',
'--nacl-file=%s' % (self.options.files[0],),
'--suppressions=' +
'../tools/valgrind/memcheck/suppressions.txt',
'--xml-file=%s/xml.%%p' % (self.tool_log_dir,),
'--log-file=%s/log.%%p' % (self.tool_log_dir,)] + cmd
elif self.options.tool == 'tsan':
cmd = ['src/third_party/valgrind/tsan.sh',
'-v',
'--num-callers=30',
'--trace-children=yes',
'--nacl-file=%s' % (self.options.files[0],),
'--ignore=../tools/valgrind/tsan/ignores.txt',
'--suppressions=../tools/valgrind/tsan/suppressions.txt',
'--log-file=%s/log.%%p' % (self.tool_log_dir,)] + cmd
elif self.options.tool != None:
raise LaunchFailure('Invalid tool name "%s"' % (self.options.tool,))
if self.options.enable_sockets:
cmd.append('--allow-nacl-socket-api=%s' % host)
cmd.extend(self.options.browser_flags)
cmd.append(url)
return cmd
|