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