| Index: ppapi/native_client/tools/browser_tester/browsertester/browserlauncher.py
|
| diff --git a/ppapi/native_client/tools/browser_tester/browsertester/browserlauncher.py b/ppapi/native_client/tools/browser_tester/browsertester/browserlauncher.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..07780773a880f80e4ba359ad45c478269ccc744d
|
| --- /dev/null
|
| +++ b/ppapi/native_client/tools/browser_tester/browsertester/browserlauncher.py
|
| @@ -0,0 +1,334 @@
|
| +#!/usr/bin/python
|
| +# Copyright (c) 2012 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 os.path
|
| +import re
|
| +import shutil
|
| +import sys
|
| +import tempfile
|
| +import time
|
| +
|
| +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['PPAPI_BROWSER_DEBUG'] = '1'
|
| + env['NACL_PLUGIN_DEBUG'] = '1'
|
| + env['NACL_PPAPI_PROXY_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):
|
| + 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
|
| + if self.options.prefer_portable_in_manifest:
|
| + env['NACL_PREFER_PORTABLE_IN_MANIFEST'] = '1'
|
| + 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.iteritems()])
|
| + 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, 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, 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, port):
|
| + cmd = [self.binary,
|
| + '--disable-web-resources',
|
| + '--disable-preconnect',
|
| + '--no-first-run',
|
| + '--no-default-browser-check',
|
| + '--enable-logging',
|
| + '--log-level=1',
|
| + '--safebrowsing-disable-auto-update',
|
| + # 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
|
| + # Sandboxing Chrome on Linux requires a SUIDed helper binary. This
|
| + # binary may not be installed, so disable sandboxing to avoid the
|
| + # corner cases where it may fail. This is a little scarry, because it
|
| + # means we are not testing NaCl inside the outer sandbox on Linux.
|
| + disable_sandbox |= PLATFORM == 'linux'
|
| + # 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('--register-pepper-plugins=%s;application/x-nacl'
|
| + % self.options.ppapi_plugin)
|
| + 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.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,))
|
| + cmd.extend(self.options.browser_flags)
|
| + cmd.append(url)
|
| + return cmd
|
|
|