| # Copyright 2017 the V8 project 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 |
| import subprocess |
| import sys |
| import threading |
| import time |
| |
| from ..local import utils |
| from ..objects import output |
| |
| |
| SEM_INVALID_VALUE = -1 |
| SEM_NOGPFAULTERRORBOX = 0x0002 # Microsoft Platform SDK WinBase.h |
| |
| |
| class BaseCommand(object): |
| def __init__(self, shell, args=None, cmd_prefix=None, timeout=60, env=None, |
| verbose=False): |
| assert(timeout > 0) |
| |
| self.shell = shell |
| self.args = args or [] |
| self.cmd_prefix = cmd_prefix or [] |
| self.timeout = timeout |
| self.env = env or {} |
| self.verbose = verbose |
| |
| def execute(self, **additional_popen_kwargs): |
| if self.verbose: |
| print '# %s' % self |
| |
| process = self._start_process(**additional_popen_kwargs) |
| |
| # Variable to communicate with the timer. |
| timeout_occured = [False] |
| timer = threading.Timer( |
| self.timeout, self._on_timeout, [process, timeout_occured]) |
| timer.start() |
| |
| start_time = time.time() |
| stdout, stderr = process.communicate() |
| duration = time.time() - start_time |
| |
| timer.cancel() |
| |
| return output.Output( |
| process.returncode, |
| timeout_occured[0], |
| stdout.decode('utf-8', 'replace').encode('utf-8'), |
| stderr.decode('utf-8', 'replace').encode('utf-8'), |
| process.pid, |
| duration |
| ) |
| |
| def _start_process(self, **additional_popen_kwargs): |
| try: |
| return subprocess.Popen( |
| args=self._get_popen_args(), |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| env=self._get_env(), |
| **additional_popen_kwargs |
| ) |
| except Exception as e: |
| sys.stderr.write('Error executing: %s\n' % self) |
| raise e |
| |
| def _get_popen_args(self): |
| return self._to_args_list() |
| |
| def _get_env(self): |
| env = os.environ.copy() |
| env.update(self.env) |
| # GTest shard information is read by the V8 tests runner. Make sure it |
| # doesn't leak into the execution of gtests we're wrapping. Those might |
| # otherwise apply a second level of sharding and as a result skip tests. |
| env.pop('GTEST_TOTAL_SHARDS', None) |
| env.pop('GTEST_SHARD_INDEX', None) |
| return env |
| |
| def _kill_process(self, process): |
| raise NotImplementedError() |
| |
| def _on_timeout(self, process, timeout_occured): |
| timeout_occured[0] = True |
| try: |
| self._kill_process(process) |
| except OSError: |
| sys.stderr.write('Error: Process %s already ended.\n' % process.pid) |
| |
| def __str__(self): |
| return self.to_string() |
| |
| def to_string(self, relative=False): |
| def escape(part): |
| # Escape spaces. We may need to escape more characters for this to work |
| # properly. |
| if ' ' in part: |
| return '"%s"' % part |
| return part |
| |
| parts = map(escape, self._to_args_list()) |
| cmd = ' '.join(parts) |
| if relative: |
| cmd = cmd.replace(os.getcwd() + os.sep, '') |
| return cmd |
| |
| def _to_args_list(self): |
| return self.cmd_prefix + [self.shell] + self.args |
| |
| |
| class PosixCommand(BaseCommand): |
| def _kill_process(self, process): |
| process.kill() |
| |
| |
| class WindowsCommand(BaseCommand): |
| def _start_process(self, **kwargs): |
| # Try to change the error mode to avoid dialogs on fatal errors. Don't |
| # touch any existing error mode flags by merging the existing error mode. |
| # See http://blogs.msdn.com/oldnewthing/archive/2004/07/27/198410.aspx. |
| def set_error_mode(mode): |
| prev_error_mode = SEM_INVALID_VALUE |
| try: |
| import ctypes |
| prev_error_mode = ( |
| ctypes.windll.kernel32.SetErrorMode(mode)) #@UndefinedVariable |
| except ImportError: |
| pass |
| return prev_error_mode |
| |
| error_mode = SEM_NOGPFAULTERRORBOX |
| prev_error_mode = set_error_mode(error_mode) |
| set_error_mode(error_mode | prev_error_mode) |
| |
| try: |
| return super(WindowsCommand, self)._start_process(**kwargs) |
| finally: |
| if prev_error_mode != SEM_INVALID_VALUE: |
| set_error_mode(prev_error_mode) |
| |
| def _get_popen_args(self): |
| return subprocess.list2cmdline(self._to_args_list()) |
| |
| def _kill_process(self, process): |
| if self.verbose: |
| print 'Attempting to kill process %d' % process.pid |
| sys.stdout.flush() |
| tk = subprocess.Popen( |
| 'taskkill /T /F /PID %d' % process.pid, |
| stdout=subprocess.PIPE, |
| stderr=subprocess.PIPE, |
| ) |
| stdout, stderr = tk.communicate() |
| if self.verbose: |
| print 'Taskkill results for %d' % process.pid |
| print stdout |
| print stderr |
| print 'Return code: %d' % tk.returncode |
| sys.stdout.flush() |
| |
| |
| # Set the Command class to the OS-specific version. |
| if utils.IsWindows(): |
| Command = WindowsCommand |
| else: |
| Command = PosixCommand |