def RunTestsSingly(exe, tests):
    for test in tests:
        filter = test
        if len(sys.argv) > 2:
            filter = filter + ":" + sys.argv[2]
        cmd = [exe, "--gtest_filter=" + filter]
        common.RunSubprocess(cmd)
 def SimpleTest(self, module, name, valgrind_test_args=None, cmd_args=None):
   cmd = self._DefaultCommand(module, name, valgrind_test_args)
   self._ReadGtestFilterFile(name, cmd)
   if cmd_args:
     cmd.extend(["--"])
     cmd.extend(cmd_args)
   return common.RunSubprocess(cmd, 0)
 def Execute(self):
   """ Execute the app to be tested after successful instrumentation.
   Full execution command-line provided by subclassers via proc."""
   logging.info("starting execution...")
   proc = self.ToolCommand()
   for var in self._env:
     common.PutEnvAndLog(var, self._env[var])
   return common.RunSubprocess(proc, self._timeout)
예제 #4
0
 def Execute(self):
     """Executes the app to be tested."""
     logging.info('starting execution...')
     proc = self._args
     self.PutEnvAndLog('G_SLICE', 'always-malloc')
     self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
     self.PutEnvAndLog('NSS_DISABLE_UNLOAD', '1')
     self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
     return common.RunSubprocess(proc, self._timeout)
예제 #5
0
 def InstrumentDll(self):
     '''Does a blocking Purify instrumentation of chrome.dll.'''
     # TODO(paulg): Make this code support any DLL.
     cmd = self._DefaultCommand("chrome")
     cmd.append("--instrument_only")
     cmd.append(os.path.join(self._options.build_dir, "chrome.dll"))
     result = common.RunSubprocess(cmd, 0)
     if result:
         logging.error("Instrumentation error: %d" % result)
     return result
예제 #6
0
 def SimpleTest(self, module, name):
     cmd = self._DefaultCommand(module, name)
     if not self._options.run_singly:
         self._ReadGtestFilterFile(name, cmd)
         cmd.append("--gtest_print_time")
         return common.RunSubprocess(cmd, 0)
     else:
         exe = cmd[-1]
         script = ["python.exe", "test_runner.py", exe]
         return self.ScriptedTest(module, exe, name, script, multi=True)
 def TestLayoutChunk(self, chunk_num, chunk_size):
   # Run tests [chunk_num*chunk_size .. (chunk_num+1)*chunk_size) from the
   # list of tests.  Wrap around to beginning of list at end.
   # If chunk_size is zero, run all tests in the list once.
   # If a text file is given as argument, it is used as the list of tests.
   #
   # Build the ginormous commandline in 'cmd'.
   # It's going to be roughly
   #  python valgrind_test.py ... python run_webkit_tests.py ...
   # but we'll use the --indirect flag to valgrind_test.py
   # to avoid valgrinding python.
   # Start by building the valgrind_test.py commandline.
   cmd = self._DefaultCommand("webkit")
   cmd.append("--trace_children")
   cmd.append("--indirect")
   # Now build script_cmd, the run_webkits_tests.py commandline
   # Store each chunk in its own directory so that we can find the data later
   chunk_dir = os.path.join("layout", "chunk_%05d" % chunk_num)
   test_shell = os.path.join(self._options.build_dir, "test_shell")
   out_dir = os.path.join(google.path_utils.ScriptDir(), "latest")
   out_dir = os.path.join(out_dir, chunk_dir)
   if os.path.exists(out_dir):
     old_files = glob.glob(os.path.join(out_dir, "*.txt"))
     for f in old_files:
       os.remove(f)
   else:
     os.makedirs(out_dir)
   script = os.path.join(self._source_dir, "webkit", "tools", "layout_tests",
                         "run_webkit_tests.py")
   script_cmd = ["python", script, "--run-singly", "-v",
                 "--noshow-results", "--time-out-ms=200000",
                 "--nocheck-sys-deps"]
   # Pass build mode to run_webkit_tests.py.  We aren't passed it directly,
   # so parse it out of build_dir.  run_webkit_tests.py can only handle
   # the two values "Release" and "Debug".
   # TODO(Hercules): unify how all our scripts pass around build mode
   # (--mode / --target / --build_dir / --debug)
   if self._options.build_dir.endswith("Debug"):
     script_cmd.append("--debug");
   if (chunk_size > 0):
     script_cmd.append("--run-chunk=%d:%d" % (chunk_num, chunk_size))
   if len(self._args):
     # if the arg is a txt file, then treat it as a list of tests
     if os.path.isfile(self._args[0]) and self._args[0][-4:] == ".txt":
       script_cmd.append("--test-list=%s" % self._args[0])
     else:
       script_cmd.extend(self._args)
   self._ReadGtestFilterFile("layout", script_cmd)
   # Now run script_cmd with the wrapper in cmd
   cmd.extend(["--"])
   cmd.extend(script_cmd)
   ret = common.RunSubprocess(cmd, 0)
   return ret
예제 #8
0
  def Execute(self):
    ''' Execute the app to be tested after successful instrumentation.
    Full execution command-line provided by subclassers via proc.'''
    logging.info("starting execution...")

    proc = self.ValgrindCommand()
    os.putenv("G_SLICE", "always-malloc")
    logging.info("export G_SLICE=always-malloc");
    os.putenv("NSS_DISABLE_ARENA_FREE_LIST", "1")
    logging.info("export NSS_DISABLE_ARENA_FREE_LIST=1");
    os.putenv("GTEST_DEATH_TEST_USE_FORK", "1")
    logging.info("export GTEST_DEATH_TEST_USE_FORK=1");

    return common.RunSubprocess(proc, self._timeout)
예제 #9
0
 def ScriptedTest(self,
                  module,
                  exe,
                  name,
                  script,
                  multi=False,
                  cmd_args=None,
                  out_dir_extra=None):
     '''Purify a target exe, which will be executed one or more times via a
    script or driver program.
 Args:
   module - which top level component this test is from (webkit, base, etc.)
   exe - the name of the exe (it's assumed to exist in build_dir)
   name - the name of this test (used to name output files)
   script - the driver program or script.  If it's python.exe, we use
     search-path behavior to execute, otherwise we assume that it is in
     build_dir.
   multi - a boolean hint that the exe will be run multiple times, generating
     multiple output files (without this option, only the last run will be
     recorded and analyzed)
   cmd_args - extra arguments to pass to the purify_test.py script
 '''
     if out_dir_extra:
         self._report_dir = os.path.join(self._report_dir, out_dir_extra)
     cmd = self._DefaultCommand(module)
     exe = os.path.join(self._options.build_dir, exe)
     cmd.append("--exe=%s" % exe)
     cmd.append("--name=%s" % name)
     if multi:
         if out_dir_extra:
             if os.path.exists(self._report_dir):
                 old_files = glob.glob(
                     os.path.join(self._report_dir, "*.txt"))
                 for f in old_files:
                     os.remove(f)
             else:
                 os.makedirs(self._report_dir)
         out_file = os.path.join(self._report_dir, "%s%%5d.txt" % name)
         cmd.append("--out_file=%s" % out_file)
     if cmd_args:
         cmd.extend(cmd_args)
     if script[0] != "python.exe" and not os.path.exists(script[0]):
         script[0] = os.path.join(self._options.build_dir, script[0])
     cmd.extend(script)
     self._ReadGtestFilterFile(name, cmd)
     return common.RunSubprocess(cmd, 0)
예제 #10
0
    def Execute(self):
        """Executes the app to be tested."""
        logging.info('starting execution...')
        proc = ['sh', path_utils.ScriptDir() + '/heapcheck_std.sh']
        proc += self._args
        self.PutEnvAndLog('G_SLICE', 'always-malloc')
        self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
        self.PutEnvAndLog('NSS_DISABLE_UNLOAD', '1')
        self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
        self.PutEnvAndLog('HEAPCHECK', self._mode)
        self.PutEnvAndLog('HEAP_CHECK_ERROR_EXIT_CODE', '0')
        self.PutEnvAndLog('HEAP_CHECK_MAX_LEAKS', '-1')
        self.PutEnvAndLog('KEEP_SHADOW_STACKS', '1')
        self.PutEnvAndLog(
            'PPROF_PATH',
            path_utils.ScriptDir() +
            '/../../third_party/tcmalloc/chromium/src/pprof')
        self.PutEnvAndLog('LD_LIBRARY_PATH',
                          '/usr/lib/debug/:/usr/lib32/debug/')

        return common.RunSubprocess(proc, self._timeout)
 def Cleanup(self):
     common.Rational.Cleanup(self)
     if self._instrument_only:
         return
     cmd = self._PurifyCommand()
     # undo the /Replace=yes that was done in Instrument(), which means to
     # remove the instrumented exe, and then rename exe.Original back to exe.
     cmd.append("/UndoReplace")
     cmd.append(os.path.abspath(self._exe))
     common.RunSubprocess(cmd, self._timeout, detach=True)
     # if we overwrote an existing ini file, restore it
     ini_file = self._exe.replace(".exe", "_pure.ini")
     if os.path.isfile(ini_file):
         os.remove(ini_file)
     ini_file_orig = ini_file + ".Original"
     if os.path.isfile(ini_file_orig):
         os.rename(ini_file_orig, ini_file)
     # remove the pft file we wrote out
     pft_file = self._exe.replace(".exe", "_exe.pft")
     if os.path.isfile(pft_file):
         os.remove(pft_file)
예제 #12
0
    def Execute(self):
        """Executes the app to be tested."""
        logging.info('starting execution...')
        proc = ['sh', google.path_utils.ScriptDir() + '/heapcheck_std.sh']
        proc += self._args
        self.PutEnvAndLog('G_SLICE', 'always-malloc')
        self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
        self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
        self.PutEnvAndLog('HEAPCHECK', self._mode)
        self.PutEnvAndLog('HEAP_CHECK_MAX_LEAKS', '-1')
        self.PutEnvAndLog(
            'PPROF_PATH',
            google.path_utils.ScriptDir() +
            '/../../third_party/tcmalloc/chromium/src/pprof')

        common.RunSubprocess(proc, self._timeout)

        # Always return true, even if running the subprocess failed. We depend on
        # Analyze to determine if the run was valid. (This behaviour copied from
        # the purify_test.py script.)
        return True
예제 #13
0
    def Execute(self):
        """Executes the app to be tested."""
        logging.info('starting execution...')
        proc = ['sh', path_utils.ScriptDir() + '/heapcheck_std.sh']
        proc += self._args
        self.PutEnvAndLog('G_SLICE', 'always-malloc')
        self.PutEnvAndLog('NSS_DISABLE_ARENA_FREE_LIST', '1')
        self.PutEnvAndLog('NSS_DISABLE_UNLOAD', '1')
        self.PutEnvAndLog('GTEST_DEATH_TEST_USE_FORK', '1')
        self.PutEnvAndLog('HEAPCHECK', self._mode)
        self.PutEnvAndLog('HEAP_CHECK_MAX_LEAKS', '-1')
        self.PutEnvAndLog('KEEP_SHADOW_STACKS', '1')
        self.PutEnvAndLog(
            'PPROF_PATH',
            path_utils.ScriptDir() +
            '/../../third_party/tcmalloc/chromium/src/pprof')
        self.PutEnvAndLog('LD_LIBRARY_PATH',
                          '/usr/lib/debug/:/usr/lib32/debug/')

        common.RunSubprocess(proc, self._timeout)

        # Always return true, even if running the subprocess failed. We depend on
        # Analyze to determine if the run was valid.
        return True
  def ToolCommand(self):
    """Get the tool command to run."""
    # WINHEAP is what Dr. Memory supports as there are issues w/ both
    # jemalloc (http://code.google.com/p/drmemory/issues/detail?id=320) and
    # tcmalloc (http://code.google.com/p/drmemory/issues/detail?id=314)
    add_env = {
      "CHROME_ALLOCATOR" : "WINHEAP",
      "JSIMD_FORCEMMX"   : "1",  # http://code.google.com/p/drmemory/issues/detail?id=540
    }
    for k,v in add_env.iteritems():
      logging.info("export %s=%s", k, v)
      os.putenv(k, v)

    drmem_cmd = os.getenv("DRMEMORY_COMMAND")
    if not drmem_cmd:
      raise RuntimeError, "Please set DRMEMORY_COMMAND environment variable " \
                          "with the path to drmemory.exe"
    proc = drmem_cmd.split(" ")

    # By default, don't run python (this will exclude python's children as well)
    # to reduce runtime.  We're not really interested in spending time finding
    # bugs in the python implementation.
    # With file-based config we must update the file every time, and
    # it will affect simultaneous drmem uses by this user.  While file-based
    # config has many advantages, here we may want this-instance-only
    # (http://code.google.com/p/drmemory/issues/detail?id=334).
    drconfig_cmd = [ proc[0].replace("drmemory.exe", "drconfig.exe") ]
    drconfig_cmd += ["-quiet"] # suppress errors about no 64-bit libs
    run_drconfig = True
    if self._options.follow_python:
      logging.info("Following python children")
      # -unreg fails if not already registered so query for that first
      query_cmd = drconfig_cmd + ["-isreg", "python.exe"]
      query_proc = subprocess.Popen(query_cmd, stdout=subprocess.PIPE,
                                    shell=True)
      (query_out, query_err) = query_proc.communicate()
      if re.search("exe not registered", query_out):
        run_drconfig = False # all set
      else:
        drconfig_cmd += ["-unreg", "python.exe"]
    else:
      logging.info("Excluding python children")
      drconfig_cmd += ["-reg", "python.exe", "-norun"]
    if run_drconfig:
      drconfig_retcode = common.RunSubprocess(drconfig_cmd, self._timeout)
      if drconfig_retcode:
        logging.error("Configuring whether to follow python children failed " \
                      "with %d.", drconfig_retcode)
        raise RuntimeError, "Configuring python children failed "

    suppression_count = 0
    supp_files = self._options.suppressions
    if self.full_mode:
      supp_files += [s.replace(".txt", "_full.txt") for s in supp_files]
    for suppression_file in supp_files:
      if os.path.exists(suppression_file):
        suppression_count += 1
        proc += ["-suppress", common.NormalizeWindowsPath(suppression_file)]

    if not suppression_count:
      logging.warning("WARNING: NOT USING SUPPRESSIONS!")

    # Un-comment to dump Dr.Memory events on error
    #proc += ["-dr_ops", "-dumpcore_mask", "-dr_ops", "0x8bff"]

    # Un-comment and comment next line to debug Dr.Memory
    #proc += ["-dr_ops", "-no_hide"]
    #proc += ["-dr_ops", "-msgbox_mask", "-dr_ops", "15"]
    #Proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "15"]
    # Ensure we see messages about Dr. Memory crashing!
    proc += ["-dr_ops", "-stderr_mask", "-dr_ops", "12"]

    if self._options.use_debug:
      proc += ["-debug"]

    proc += ["-logdir", common.NormalizeWindowsPath(self.log_dir)]

    if self.log_parent_dir:
      # gpu process on Windows Vista+ runs at Low Integrity and can only
      # write to certain directories (http://crbug.com/119131)
      symcache_dir = os.path.join(self.log_parent_dir, "drmemory.symcache")
    elif self._options.build_dir:
      # The other case is only possible with -t cmdline.
      # Anyways, if we omit -symcache_dir the -logdir's value is used which
      # should be fine.
      symcache_dir = os.path.join(self._options.build_dir, "drmemory.symcache")
    if symcache_dir:
      if not os.path.exists(symcache_dir):
        try:
          os.mkdir(symcache_dir)
        except OSError:
          logging.warning("Can't create symcache dir?")
      if os.path.exists(symcache_dir):
        proc += ["-symcache_dir", common.NormalizeWindowsPath(symcache_dir)]

    # Use -no_summary to suppress DrMemory's summary and init-time
    # notifications.  We generate our own with drmemory_analyze.py.
    proc += ["-batch", "-no_summary"]

    # Un-comment to disable interleaved output.  Will also suppress error
    # messages normally printed to stderr.
    #proc += ["-quiet", "-no_results_to_stderr"]

    proc += ["-callstack_max_frames", "40"]

    # disable leak scan for now
    proc += ["-no_count_leaks", "-no_leak_scan"]

    # crbug.com/413215, no heap mismatch check for Windows release build binary
    if common.IsWindows() and "Release" in self._options.build_dir:
        proc += ["-no_check_delete_mismatch"]

    # make callstacks easier to read
    proc += ["-callstack_srcfile_prefix",
             "build\\src,chromium\\src,crt_build\\self_x86"]
    proc += ["-callstack_modname_hide",
             "*drmemory*,chrome.dll"]

    boring_callers = common.BoringCallers(mangled=False, use_re_wildcards=False)
    # TODO(timurrrr): In fact, we want "starting from .." instead of "below .."
    proc += ["-callstack_truncate_below", ",".join(boring_callers)]

    if self.pattern_mode:
      proc += ["-pattern", "0xf1fd", "-no_count_leaks", "-redzone_size", "0x20"]
    elif not self.full_mode:
      proc += ["-light"]

    proc += self._tool_flags

    # Dr.Memory requires -- to separate tool flags from the executable name.
    proc += ["--"]

    if self._options.indirect or self._options.indirect_webkit_layout:
      # TODO(timurrrr): reuse for TSan on Windows
      wrapper_path = os.path.join(self._source_dir,
                                  "tools", "valgrind", "browser_wrapper_win.py")
      wrapper = " ".join(["python", wrapper_path] + proc)
      self.CreateBrowserWrapper(wrapper)
      logging.info("browser wrapper = " + " ".join(proc))
      if self._options.indirect_webkit_layout:
        proc = self._args
        # Layout tests want forward slashes.
        wrapper = wrapper.replace('\\', '/')
        proc += ["--wrapper", wrapper]
        return proc
      else:
        proc = []

    # Note that self._args begins with the name of the exe to be run.
    self._args[0] = common.NormalizeWindowsPath(self._args[0])
    proc += self._args
    return proc
  def PrepareForTestMac(self):
    """Runs dsymutil if needed.

    Valgrind for Mac OS X requires that debugging information be in a .dSYM
    bundle generated by dsymutil.  It is not currently able to chase DWARF
    data into .o files like gdb does, so executables without .dSYM bundles or
    with the Chromium-specific "fake_dsym" bundles generated by
    build/mac/strip_save_dsym won't give source file and line number
    information in valgrind.

    This function will run dsymutil if the .dSYM bundle is missing or if
    it looks like a fake_dsym.  A non-fake dsym that already exists is assumed
    to be up-to-date.
    """
    test_command = self._args[0]
    dsym_bundle = self._args[0] + '.dSYM'
    dsym_file = os.path.join(dsym_bundle, 'Contents', 'Resources', 'DWARF',
                             os.path.basename(test_command))
    dsym_info_plist = os.path.join(dsym_bundle, 'Contents', 'Info.plist')

    needs_dsymutil = True
    saved_test_command = None

    if os.path.exists(dsym_file) and os.path.exists(dsym_info_plist):
      # Look for the special fake_dsym tag in dsym_info_plist.
      dsym_info_plist_contents = open(dsym_info_plist).read()

      if not re.search('^\s*<key>fake_dsym</key>$', dsym_info_plist_contents,
                       re.MULTILINE):
        # fake_dsym is not set, this is a real .dSYM bundle produced by
        # dsymutil.  dsymutil does not need to be run again.
        needs_dsymutil = False
      else:
        # fake_dsym is set.  dsym_file is a copy of the original test_command
        # before it was stripped.  Copy it back to test_command so that
        # dsymutil has unstripped input to work with.  Move the stripped
        # test_command out of the way, it will be restored when this is
        # done.
        saved_test_command = test_command + '.stripped'
        os.rename(test_command, saved_test_command)
        shutil.copyfile(dsym_file, test_command)
        shutil.copymode(saved_test_command, test_command)

    if needs_dsymutil:
      if self._options.generate_dsym:
        # Remove the .dSYM bundle if it exists.
        shutil.rmtree(dsym_bundle, True)

        dsymutil_command = ['dsymutil', test_command]

        # dsymutil is crazy slow.  Ideally we'd have a timeout here,
        # but common.RunSubprocess' timeout is only checked
        # after each line of output; dsymutil is silent
        # until the end, and is then killed, which is silly.
        common.RunSubprocess(dsymutil_command)

        if saved_test_command:
          os.rename(saved_test_command, test_command)
      else:
        logging.info("No real .dSYM for test_command.  Line numbers will "
                     "not be shown.  Either tell xcode to generate .dSYM "
                     "file, or use --generate_dsym option to this tool.")
예제 #16
0
    def ToolCommand(self):
        """Get the tool command to run."""
        tool_name = self.ToolName()

        # WINHEAP is what Dr. Memory supports as there are issues w/ both
        # jemalloc (http://code.google.com/p/drmemory/issues/detail?id=320) and
        # tcmalloc (http://code.google.com/p/drmemory/issues/detail?id=314)
        add_env = {
            "CHROME_ALLOCATOR": "WINHEAP",
            "JSIMD_FORCEMMX":
            "1",  # http://code.google.com/p/drmemory/issues/detail?id=540
        }
        for k, v in add_env.iteritems():
            logging.info("export %s=%s", k, v)
            os.putenv(k, v)

        drmem_cmd = os.getenv("DRMEMORY_COMMAND")
        if not drmem_cmd:
            raise RuntimeError, "Please set DRMEMORY_COMMAND environment variable " \
                                "with the path to drmemory.exe"
        proc = drmem_cmd.split(" ")

        # By default, don't run python (this will exclude python's children as well)
        # to reduce runtime.  We're not really interested in spending time finding
        # bugs in the python implementation.
        # With file-based config we must update the file every time, and
        # it will affect simultaneous drmem uses by this user.  While file-based
        # config has many advantages, here we may want this-instance-only
        # (http://code.google.com/p/drmemory/issues/detail?id=334).
        drconfig_cmd = [proc[0].replace("drmemory.exe", "drconfig.exe")]
        drconfig_cmd += ["-quiet"]  # suppress errors about no 64-bit libs
        run_drconfig = True
        if self._options.follow_python:
            logging.info("Following python children")
            # -unreg fails if not already registered so query for that first
            query_cmd = drconfig_cmd + ["-isreg", "python.exe"]
            query_proc = subprocess.Popen(query_cmd,
                                          stdout=subprocess.PIPE,
                                          shell=True)
            (query_out, query_err) = query_proc.communicate()
            if re.search("exe not registered", query_out):
                run_drconfig = False  # all set
            else:
                drconfig_cmd += ["-unreg", "python.exe"]
        else:
            logging.info("Excluding python children")
            drconfig_cmd += ["-reg", "python.exe", "-norun"]
        if run_drconfig:
            drconfig_retcode = common.RunSubprocess(drconfig_cmd,
                                                    self._timeout)
            if drconfig_retcode:
                logging.error("Configuring whether to follow python children failed " \
                              "with %d.", drconfig_retcode)
                raise RuntimeError, "Configuring python children failed "

        suppression_count = 0
        for suppression_file in self._options.suppressions:
            if os.path.exists(suppression_file):
                suppression_count += 1
                proc += ["-suppress", suppression_file]

        if not suppression_count:
            logging.warning("WARNING: NOT USING SUPPRESSIONS!")

        # Un-comment to dump Dr.Memory events on error
        #proc += ["-dr_ops", "-dumpcore_mask 0x8bff"]

        # Un-comment to debug Dr.Memory
        #proc += ["-dr_ops", "-no_hide -msgbox_mask 15"]

        if self._options.use_debug:
            proc += ["-debug"]

        proc += ["-logdir", self.log_dir]
        proc += ["-batch", "-quiet", "-no_results_to_stderr"]

        proc += ["-callstack_max_frames", "40"]

        # make callstacks easier to read
        proc += [
            "-callstack_srcfile_prefix",
            "build\\src,chromium\\src,crt_build\\self_x86"
        ]
        proc += [
            "-callstack_truncate_below", "main,BaseThreadInitThunk," +
            "testing*Test*Run*,testing::internal::Handle*Exceptions*," +
            "MessageLoop::Run," +
            "RunnableMethod*,RunnableFunction*,DispatchToMethod*"
        ]
        proc += ["-callstack_modname_hide", "*.exe,chrome.dll"]

        if not self.handle_uninits_and_leaks:
            proc += ["-no_check_uninitialized", "-no_count_leaks"]

        proc += self._tool_flags

        # Dr.Memory requires -- to separate tool flags from the executable name.
        proc += ["--"]

        if self._options.indirect:
            self.CreateBrowserWrapper(" ".join(proc))
            proc = []

        # Note that self._args begins with the name of the exe to be run.
        proc += self._args
        return proc