tfiala created this revision.
tfiala added reviewers: zturner, labath.
tfiala added a subscriber: lldb-commits.
Also addresses a PEP8 name violation, converting lldb_utils.OptionalWith to
lldb_utils.optional_with.
http://reviews.llvm.org/D19216
Files:
packages/Python/lldbsuite/test/dosep.py
packages/Python/lldbsuite/test/test_runner/__init__.py
packages/Python/lldbsuite/test/test_runner/lib/lldb_utils.py
packages/Python/lldbsuite/test/test_runner/lib/process_control.py
packages/Python/lldbsuite/test/test_runner/lldb_utils.py
packages/Python/lldbsuite/test/test_runner/process_control.py
Index: packages/Python/lldbsuite/test/test_runner/process_control.py
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/test_runner/process_control.py
@@ -0,0 +1,706 @@
+"""
+The LLVM Compiler Infrastructure
+
+This file is distributed under the University of Illinois Open Source
+License. See LICENSE.TXT for details.
+
+Provides classes used by the test results reporting infrastructure
+within the LLDB test suite.
+
+
+This module provides process-management support for the LLDB test
+running infrastructure.
+"""
+
+# System imports
+import os
+import re
+import signal
+import subprocess
+import sys
+import threading
+
+
+class CommunicatorThread(threading.Thread):
+ """Provides a thread class that communicates with a subprocess."""
+ def __init__(self, process, event, output_file):
+ super(CommunicatorThread, self).__init__()
+ # Don't let this thread prevent shutdown.
+ self.daemon = True
+ self.process = process
+ self.pid = process.pid
+ self.event = event
+ self.output_file = output_file
+ self.output = None
+
+ def run(self):
+ try:
+ # Communicate with the child process.
+ # This will not complete until the child process terminates.
+ self.output = self.process.communicate()
+ except Exception as exception: # pylint: disable=broad-except
+ if self.output_file:
+ self.output_file.write(
+ "exception while using communicate() for pid: {}\n".format(
+ exception))
+ finally:
+ # Signal that the thread's run is complete.
+ self.event.set()
+
+
+# Provides a regular expression for matching gtimeout-based durations.
+TIMEOUT_REGEX = re.compile(r"(^\d+)([smhd])?$")
+
+
+def timeout_to_seconds(timeout):
+ """Converts timeout/gtimeout timeout values into seconds.
+
+ @param timeout a timeout in the form of xm representing x minutes.
+
+ @return None if timeout is None, or the number of seconds as a float
+ if a valid timeout format was specified.
+ """
+ if timeout is None:
+ return None
+ else:
+ match = TIMEOUT_REGEX.match(timeout)
+ if match:
+ value = float(match.group(1))
+ units = match.group(2)
+ if units is None:
+ # default is seconds. No conversion necessary.
+ return value
+ elif units == 's':
+ # Seconds. No conversion necessary.
+ return value
+ elif units == 'm':
+ # Value is in minutes.
+ return 60.0 * value
+ elif units == 'h':
+ # Value is in hours.
+ return (60.0 * 60.0) * value
+ elif units == 'd':
+ # Value is in days.
+ return 24 * (60.0 * 60.0) * value
+ else:
+ raise Exception("unexpected units value '{}'".format(units))
+ else:
+ raise Exception("could not parse TIMEOUT spec '{}'".format(
+ timeout))
+
+
+class ProcessHelper(object):
+ """Provides an interface for accessing process-related functionality.
+
+ This class provides a factory method that gives the caller a
+ platform-specific implementation instance of the class.
+
+ Clients of the class should stick to the methods provided in this
+ base class.
+
+ @see ProcessHelper.process_helper()
+ """
+ def __init__(self):
+ super(ProcessHelper, self).__init__()
+
+ @classmethod
+ def process_helper(cls):
+ """Returns a platform-specific ProcessHelper instance.
+ @return a ProcessHelper instance that does the right thing for
+ the current platform.
+ """
+
+ # If you add a new platform, create an instance here and
+ # return it.
+ if os.name == "nt":
+ return WindowsProcessHelper()
+ else:
+ # For all POSIX-like systems.
+ return UnixProcessHelper()
+
+ def create_piped_process(self, command, new_process_group=True):
+ # pylint: disable=no-self-use,unused-argument
+ # As expected. We want derived classes to implement this.
+ """Creates a subprocess.Popen-based class with I/O piped to the parent.
+
+ @param command the command line list as would be passed to
+ subprocess.Popen(). Use the list form rather than the string form.
+
+ @param new_process_group indicates if the caller wants the
+ process to be created in its own process group. Each OS handles
+ this concept differently. It provides a level of isolation and
+ can simplify or enable terminating the process tree properly.
+
+ @return a subprocess.Popen-like object.
+ """
+ raise Exception("derived class must implement")
+
+ def supports_soft_terminate(self):
+ # pylint: disable=no-self-use
+ # As expected. We want derived classes to implement this.
+ """Indicates if the platform supports soft termination.
+
+ Soft termination is the concept of a terminate mechanism that
+ allows the target process to shut down nicely, but with the
+ catch that the process might choose to ignore it.
+
+ Platform supporter note: only mark soft terminate as supported
+ if the target process has some way to evade the soft terminate
+ request; otherwise, just support the hard terminate method.
+
+ @return True if the platform supports a soft terminate mechanism.
+ """
+ # By default, we do not support a soft terminate mechanism.
+ return False
+
+ def soft_terminate(self, popen_process, log_file=None, want_core=True):
+ # pylint: disable=no-self-use,unused-argument
+ # As expected. We want derived classes to implement this.
+ """Attempts to terminate the process in a polite way.
+
+ This terminate method is intended to give the child process a
+ chance to clean up and exit on its own, possibly with a request
+ to drop a core file or equivalent (i.e. [mini-]crashdump, crashlog,
+ etc.) If new_process_group was set in the process creation method
+ and the platform supports it, this terminate call will attempt to
+ kill the whole process tree rooted in this child process.
+
+ @param popen_process the subprocess.Popen-like object returned
+ by one of the process-creation methods of this class.
+
+ @param log_file file-like object used to emit error-related
+ logging info. May be None if no error-related info is desired.
+
+ @param want_core True if the caller would like to get a core
+ dump (or the analogous crash report) from the terminated process.
+ """
+ popen_process.terminate()
+
+ def hard_terminate(self, popen_process, log_file=None):
+ # pylint: disable=no-self-use,unused-argument
+ # As expected. We want derived classes to implement this.
+ """Attempts to terminate the process immediately.
+
+ This terminate method is intended to kill child process in
+ a manner in which the child process has no ability to block,
+ and also has no ability to clean up properly. If new_process_group
+ was specified when creating the process, and if the platform
+ implementation supports it, this will attempt to kill the
+ whole process tree rooted in the child process.
+
+ @param popen_process the subprocess.Popen-like object returned
+ by one of the process-creation methods of this class.
+
+ @param log_file file-like object used to emit error-related
+ logging info. May be None if no error-related info is desired.
+ """
+ popen_process.kill()
+
+ def was_soft_terminate(self, returncode, with_core):
+ # pylint: disable=no-self-use,unused-argument
+ # As expected. We want derived classes to implement this.
+ """Returns if Popen-like object returncode matches soft terminate.
+
+ @param returncode the returncode from the Popen-like object that
+ terminated with a given return code.
+
+ @param with_core indicates whether the returncode should match
+ a core-generating return signal.
+
+ @return True when the returncode represents what the system would
+ issue when a soft_terminate() with the given with_core arg occurred;
+ False otherwise.
+ """
+ if not self.supports_soft_terminate():
+ # If we don't support soft termination on this platform,
+ # then this should always be False.
+ return False
+ else:
+ # Once a platform claims to support soft terminate, it
+ # needs to be able to identify it by overriding this method.
+ raise Exception("platform needs to implement")
+
+ def was_hard_terminate(self, returncode):
+ # pylint: disable=no-self-use,unused-argument
+ # As expected. We want derived classes to implement this.
+ """Returns if Popen-like object returncode matches that of a hard
+ terminate attempt.
+
+ @param returncode the returncode from the Popen-like object that
+ terminated with a given return code.
+
+ @return True when the returncode represents what the system would
+ issue when a hard_terminate() occurred; False
+ otherwise.
+ """
+ raise Exception("platform needs to implement")
+
+ def soft_terminate_signals(self):
+ # pylint: disable=no-self-use
+ """Retrieve signal numbers that can be sent to soft terminate.
+ @return a list of signal numbers that can be sent to soft terminate
+ a process, or None if not applicable.
+ """
+ return None
+
+ def is_exceptional_exit(self, popen_status):
+ """Returns whether the program exit status is exceptional.
+
+ Returns whether the return code from a Popen process is exceptional
+ (e.g. signals on POSIX systems).
+
+ Derived classes should override this if they can detect exceptional
+ program exit.
+
+ @return True if the given popen_status represents an exceptional
+ program exit; False otherwise.
+ """
+ return False
+
+ def exceptional_exit_details(self, popen_status):
+ """Returns the normalized exceptional exit code and a description.
+
+ Given an exceptional exit code, returns the integral value of the
+ exception (e.g. signal number for POSIX) and a description (e.g.
+ signal name on POSIX) for the result.
+
+ Derived classes should override this if they can detect exceptional
+ program exit.
+
+ It is fine to not implement this so long as is_exceptional_exit()
+ always returns False.
+
+ @return (normalized exception code, symbolic exception description)
+ """
+ raise Exception("exception_exit_details() called on unsupported class")
+
+
+class UnixProcessHelper(ProcessHelper):
+ """Provides a ProcessHelper for Unix-like operating systems.
+
+ This implementation supports anything that looks Posix-y
+ (e.g. Darwin, Linux, *BSD, etc.)
+ """
+ def __init__(self):
+ super(UnixProcessHelper, self).__init__()
+
+ @classmethod
+ def _create_new_process_group(cls):
+ """Creates a new process group for the calling process."""
+ os.setpgid(os.getpid(), os.getpid())
+
+ def create_piped_process(self, command, new_process_group=True):
+ # Determine what to run after the fork but before the exec.
+ if new_process_group:
+ preexec_func = self._create_new_process_group
+ else:
+ preexec_func = None
+
+ # Create the process.
+ process = subprocess.Popen(
+ command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True, # Elicits automatic byte -> string decoding in Py3
+ close_fds=True,
+ preexec_fn=preexec_func)
+
+ # Remember whether we're using process groups for this
+ # process.
+ process.using_process_groups = new_process_group
+ return process
+
+ def supports_soft_terminate(self):
+ # POSIX does support a soft terminate via:
+ # * SIGTERM (no core requested)
+ # * SIGQUIT (core requested if enabled, see ulimit -c)
+ return True
+
+ @classmethod
+ def _validate_pre_terminate(cls, popen_process, log_file):
+ # Validate args.
+ if popen_process is None:
+ raise ValueError("popen_process is None")
+
+ # Ensure we have something that looks like a valid process.
+ if popen_process.pid < 1:
+ if log_file:
+ log_file.write("skipping soft_terminate(): no process id")
+ return False
+
+ # We only do the process liveness check if we're not using
+ # process groups. With process groups, checking if the main
+ # inferior process is dead and short circuiting here is no
+ # good - children of it in the process group could still be
+ # alive, and they should be killed during a timeout.
+ if not popen_process.using_process_groups:
+ # Don't kill if it's already dead.
+ popen_process.poll()
+ if popen_process.returncode is not None:
+ # It has a returncode. It has already stopped.
+ if log_file:
+ log_file.write(
+ "requested to terminate pid {} but it has already "
+ "terminated, returncode {}".format(
+ popen_process.pid, popen_process.returncode))
+ # Move along...
+ return False
+
+ # Good to go.
+ return True
+
+ def _kill_with_signal(self, popen_process, log_file, signum):
+ # Validate we're ready to terminate this.
+ if not self._validate_pre_terminate(popen_process, log_file):
+ return
+
+ # Choose kill mechanism based on whether we're targeting
+ # a process group or just a process.
+ if popen_process.using_process_groups:
+ # if log_file:
+ # log_file.write(
+ # "sending signum {} to process group {} now\n".format(
+ # signum, popen_process.pid))
+ os.killpg(popen_process.pid, signum)
+ else:
+ # if log_file:
+ # log_file.write(
+ # "sending signum {} to process {} now\n".format(
+ # signum, popen_process.pid))
+ os.kill(popen_process.pid, signum)
+
+ def soft_terminate(self, popen_process, log_file=None, want_core=True):
+ # Choose signal based on desire for core file.
+ if want_core:
+ # SIGQUIT will generate core by default. Can be caught.
+ signum = signal.SIGQUIT
+ else:
+ # SIGTERM is the traditional nice way to kill a process.
+ # Can be caught, doesn't generate a core.
+ signum = signal.SIGTERM
+
+ self._kill_with_signal(popen_process, log_file, signum)
+
+ def hard_terminate(self, popen_process, log_file=None):
+ self._kill_with_signal(popen_process, log_file, signal.SIGKILL)
+
+ def was_soft_terminate(self, returncode, with_core):
+ if with_core:
+ return returncode == -signal.SIGQUIT
+ else:
+ return returncode == -signal.SIGTERM
+
+ def was_hard_terminate(self, returncode):
+ return returncode == -signal.SIGKILL
+
+ def soft_terminate_signals(self):
+ return [signal.SIGQUIT, signal.SIGTERM]
+
+ def is_exceptional_exit(self, popen_status):
+ return popen_status < 0
+
+ @classmethod
+ def _signal_names_by_number(cls):
+ return dict(
+ (k, v) for v, k in reversed(sorted(signal.__dict__.items()))
+ if v.startswith('SIG') and not v.startswith('SIG_'))
+
+ def exceptional_exit_details(self, popen_status):
+ signo = -popen_status
+ signal_names_by_number = self._signal_names_by_number()
+ signal_name = signal_names_by_number.get(signo, "")
+ return signo, signal_name
+
+
+class WindowsProcessHelper(ProcessHelper):
+ """Provides a Windows implementation of the ProcessHelper class."""
+ def __init__(self):
+ super(WindowsProcessHelper, self).__init__()
+
+ def create_piped_process(self, command, new_process_group=True):
+ if new_process_group:
+ # We need this flag if we want os.kill() to work on the subprocess.
+ creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP
+ else:
+ creation_flags = 0
+
+ return subprocess.Popen(
+ command,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ universal_newlines=True, # Elicits automatic byte -> string decoding in Py3
+ creationflags=creation_flags)
+
+ def was_hard_terminate(self, returncode):
+ return returncode != 0
+
+
+class ProcessDriver(object):
+ """Drives a child process, notifies on important events, and can timeout.
+
+ Clients are expected to derive from this class and override the
+ on_process_started and on_process_exited methods if they want to
+ hook either of those.
+
+ This class supports timing out the child process in a platform-agnostic
+ way. The on_process_exited method is informed if the exit was natural
+ or if it was due to a timeout.
+ """
+ def __init__(self, soft_terminate_timeout=10.0):
+ super(ProcessDriver, self).__init__()
+ self.process_helper = ProcessHelper.process_helper()
+ self.pid = None
+ # Create the synchronization event for notifying when the
+ # inferior dotest process is complete.
+ self.done_event = threading.Event()
+ self.io_thread = None
+ self.process = None
+ # Number of seconds to wait for the soft terminate to
+ # wrap up, before moving to more drastic measures.
+ # Might want this longer if core dumps are generated and
+ # take a long time to write out.
+ self.soft_terminate_timeout = soft_terminate_timeout
+ # Number of seconds to wait for the hard terminate to
+ # wrap up, before giving up on the io thread. This should
+ # be fast.
+ self.hard_terminate_timeout = 5.0
+ self.returncode = None
+
+ # =============================================
+ # Methods for subclasses to override if desired.
+ # =============================================
+
+ def on_process_started(self):
+ pass
+
+ def on_process_exited(self, command, output, was_timeout, exit_status):
+ pass
+
+ def write(self, content):
+ # pylint: disable=no-self-use
+ # Intended - we want derived classes to be able to override
+ # this and use any self state they may contain.
+ sys.stdout.write(content)
+
+ # ==============================================================
+ # Operations used to drive processes. Clients will want to call
+ # one of these.
+ # ==============================================================
+
+ def run_command(self, command):
+ # Start up the child process and the thread that does the
+ # communication pump.
+ self._start_process_and_io_thread(command)
+
+ # Wait indefinitely for the child process to finish
+ # communicating. This indicates it has closed stdout/stderr
+ # pipes and is done.
+ self.io_thread.join()
+ self.returncode = self.process.wait()
+ if self.returncode is None:
+ raise Exception(
+ "no exit status available for pid {} after the "
+ " inferior dotest.py should have completed".format(
+ self.process.pid))
+
+ # Notify of non-timeout exit.
+ self.on_process_exited(
+ command,
+ self.io_thread.output,
+ False,
+ self.returncode)
+
+ def run_command_with_timeout(self, command, timeout, want_core):
+ # Figure out how many seconds our timeout description is requesting.
+ timeout_seconds = timeout_to_seconds(timeout)
+
+ # Start up the child process and the thread that does the
+ # communication pump.
+ self._start_process_and_io_thread(command)
+
+ self._wait_with_timeout(timeout_seconds, command, want_core)
+
+ # ================
+ # Internal details.
+ # ================
+
+ def _start_process_and_io_thread(self, command):
+ # Create the process.
+ self.process = self.process_helper.create_piped_process(command)
+ self.pid = self.process.pid
+ self.on_process_started()
+
+ # Ensure the event is cleared that is used for signaling
+ # from the communication() thread when communication is
+ # complete (i.e. the inferior process has finished).
+ self.done_event.clear()
+
+ self.io_thread = CommunicatorThread(
+ self.process, self.done_event, self.write)
+ self.io_thread.start()
+
+ def _attempt_soft_kill(self, want_core):
+ # The inferior dotest timed out. Attempt to clean it
+ # with a non-drastic method (so it can clean up properly
+ # and/or generate a core dump). Often the OS can't guarantee
+ # that the process will really terminate after this.
+ self.process_helper.soft_terminate(
+ self.process,
+ want_core=want_core,
+ log_file=self)
+
+ # Now wait up to a certain timeout period for the io thread
+ # to say that the communication ended. If that wraps up
+ # within our soft terminate timeout, we're all done here.
+ self.io_thread.join(self.soft_terminate_timeout)
+ if not self.io_thread.is_alive():
+ # stdout/stderr were closed on the child process side. We
+ # should be able to wait and reap the child process here.
+ self.returncode = self.process.wait()
+ # We terminated, and the done_trying result is n/a
+ terminated = True
+ done_trying = None
+ else:
+ self.write("soft kill attempt of process {} timed out "
+ "after {} seconds\n".format(
+ self.process.pid, self.soft_terminate_timeout))
+ terminated = False
+ done_trying = False
+ return terminated, done_trying
+
+ def _attempt_hard_kill(self):
+ # Instruct the process to terminate and really force it to
+ # happen. Don't give the process a chance to ignore.
+ self.process_helper.hard_terminate(
+ self.process,
+ log_file=self)
+
+ # Reap the child process. This should not hang as the
+ # hard_kill() mechanism is supposed to really kill it.
+ # Improvement option:
+ # If this does ever hang, convert to a self.process.poll()
+ # loop checking on self.process.returncode until it is not
+ # None or the timeout occurs.
+ self.returncode = self.process.wait()
+
+ # Wait a few moments for the io thread to finish...
+ self.io_thread.join(self.hard_terminate_timeout)
+ if self.io_thread.is_alive():
+ # ... but this is not critical if it doesn't end for some
+ # reason.
+ self.write(
+ "hard kill of process {} timed out after {} seconds waiting "
+ "for the io thread (ignoring)\n".format(
+ self.process.pid, self.hard_terminate_timeout))
+
+ # Set if it terminated. (Set up for optional improvement above).
+ terminated = self.returncode is not None
+ # Nothing else to try.
+ done_trying = True
+
+ return terminated, done_trying
+
+ def _attempt_termination(self, attempt_count, want_core):
+ if self.process_helper.supports_soft_terminate():
+ # When soft termination is supported, we first try to stop
+ # the process with a soft terminate. Failing that, we try
+ # the hard terminate option.
+ if attempt_count == 1:
+ return self._attempt_soft_kill(want_core)
+ elif attempt_count == 2:
+ return self._attempt_hard_kill()
+ else:
+ # We don't have anything else to try.
+ terminated = self.returncode is not None
+ done_trying = True
+ return terminated, done_trying
+ else:
+ # We only try the hard terminate option when there
+ # is no soft terminate available.
+ if attempt_count == 1:
+ return self._attempt_hard_kill()
+ else:
+ # We don't have anything else to try.
+ terminated = self.returncode is not None
+ done_trying = True
+ return terminated, done_trying
+
+ def _wait_with_timeout(self, timeout_seconds, command, want_core):
+ # Allow up to timeout seconds for the io thread to wrap up.
+ # If that completes, the child process should be done.
+ completed_normally = self.done_event.wait(timeout_seconds)
+ if completed_normally:
+ # Reap the child process here.
+ self.returncode = self.process.wait()
+ else:
+ # Prepare to stop the process
+ process_terminated = completed_normally
+ terminate_attempt_count = 0
+
+ # Try as many attempts as we support for trying to shut down
+ # the child process if it's not already shut down.
+ while not process_terminated:
+ terminate_attempt_count += 1
+ # Attempt to terminate.
+ process_terminated, done_trying = self._attempt_termination(
+ terminate_attempt_count, want_core)
+ # Check if there's nothing more to try.
+ if done_trying:
+ # Break out of our termination attempt loop.
+ break
+
+ # At this point, we're calling it good. The process
+ # finished gracefully, was shut down after one or more
+ # attempts, or we failed but gave it our best effort.
+ self.on_process_exited(
+ command,
+ self.io_thread.output,
+ not completed_normally,
+ self.returncode)
+
+
+def patched_init(self, *args, **kwargs):
+ self.original_init(*args, **kwargs)
+ # Initialize our condition variable that protects wait()/poll().
+ self.wait_condition = threading.Condition()
+
+
+def patched_wait(self, *args, **kwargs):
+ self.wait_condition.acquire()
+ try:
+ result = self.original_wait(*args, **kwargs)
+ # The process finished. Signal the condition.
+ self.wait_condition.notify_all()
+ return result
+ finally:
+ self.wait_condition.release()
+
+
+def patched_poll(self, *args, **kwargs):
+ self.wait_condition.acquire()
+ try:
+ result = self.original_poll(*args, **kwargs)
+ if self.returncode is not None:
+ # We did complete, and we have the return value.
+ # Signal the event to indicate we're done.
+ self.wait_condition.notify_all()
+ return result
+ finally:
+ self.wait_condition.release()
+
+
+def patch_up_subprocess_popen():
+ subprocess.Popen.original_init = subprocess.Popen.__init__
+ subprocess.Popen.__init__ = patched_init
+
+ subprocess.Popen.original_wait = subprocess.Popen.wait
+ subprocess.Popen.wait = patched_wait
+
+ subprocess.Popen.original_poll = subprocess.Popen.poll
+ subprocess.Popen.poll = patched_poll
+
+# Replace key subprocess.Popen() threading-unprotected methods with
+# threading-protected versions.
+patch_up_subprocess_popen()
Index: packages/Python/lldbsuite/test/test_runner/lldb_utils.py
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/test_runner/lldb_utils.py
@@ -0,0 +1,66 @@
+"""
+The LLVM Compiler Infrastructure
+
+This file is distributed under the University of Illinois Open Source
+License. See LICENSE.TXT for details.
+
+Provides classes used by the test results reporting infrastructure
+within the LLDB test suite.
+
+
+This module contains utilities used by the lldb test framework.
+"""
+
+
+class optional_with(object):
+ # pylint: disable=too-few-public-methods
+ # This is a wrapper - it is not meant to provide any extra methods.
+ """Provides a wrapper for objects supporting "with", allowing None.
+
+ This lets a user use the "with object" syntax for resource usage
+ (e.g. locks) even when the wrapped with object is None.
+
+ e.g.
+
+ wrapped_lock = optional_with(thread.Lock())
+ with wrapped_lock:
+ # Do something while the lock is obtained.
+ pass
+
+ might_be_none = None
+ wrapped_none = optional_with(might_be_none)
+ with wrapped_none:
+ # This code here still works.
+ pass
+
+ This prevents having to write code like this when
+ a lock is optional:
+
+ if lock:
+ lock.acquire()
+
+ try:
+ code_fragment_always_run()
+ finally:
+ if lock:
+ lock.release()
+
+ And I'd posit it is safer, as it becomes impossible to
+ forget the try/finally using optional_with(), since
+ the with syntax can be used.
+ """
+ def __init__(self, wrapped_object):
+ self.wrapped_object = wrapped_object
+
+ def __enter__(self):
+ if self.wrapped_object is not None:
+ return self.wrapped_object.__enter__()
+ else:
+ return self
+
+ def __exit__(self, the_type, value, traceback):
+ if self.wrapped_object is not None:
+ return self.wrapped_object.__exit__(the_type, value, traceback)
+ else:
+ # Don't suppress any exceptions
+ return False
Index: packages/Python/lldbsuite/test/test_runner/lib/process_control.py
===================================================================
--- packages/Python/lldbsuite/test/test_runner/lib/process_control.py
+++ /dev/null
@@ -1,705 +0,0 @@
-"""
-The LLVM Compiler Infrastructure
-
-This file is distributed under the University of Illinois Open Source
-License. See LICENSE.TXT for details.
-
-Provides classes used by the test results reporting infrastructure
-within the LLDB test suite.
-
-
-This module provides process-management support for the LLDB test
-running infrasructure.
-"""
-
-# System imports
-import os
-import re
-import signal
-import subprocess
-import sys
-import threading
-
-
-class CommunicatorThread(threading.Thread):
- """Provides a thread class that communicates with a subprocess."""
- def __init__(self, process, event, output_file):
- super(CommunicatorThread, self).__init__()
- # Don't let this thread prevent shutdown.
- self.daemon = True
- self.process = process
- self.pid = process.pid
- self.event = event
- self.output_file = output_file
- self.output = None
-
- def run(self):
- try:
- # Communicate with the child process.
- # This will not complete until the child process terminates.
- self.output = self.process.communicate()
- except Exception as exception: # pylint: disable=broad-except
- if self.output_file:
- self.output_file.write(
- "exception while using communicate() for pid: {}\n".format(
- exception))
- finally:
- # Signal that the thread's run is complete.
- self.event.set()
-
-
-# Provides a regular expression for matching gtimeout-based durations.
-TIMEOUT_REGEX = re.compile(r"(^\d+)([smhd])?$")
-
-
-def timeout_to_seconds(timeout):
- """Converts timeout/gtimeout timeout values into seconds.
-
- @param timeout a timeout in the form of xm representing x minutes.
-
- @return None if timeout is None, or the number of seconds as a float
- if a valid timeout format was specified.
- """
- if timeout is None:
- return None
- else:
- match = TIMEOUT_REGEX.match(timeout)
- if match:
- value = float(match.group(1))
- units = match.group(2)
- if units is None:
- # default is seconds. No conversion necessary.
- return value
- elif units == 's':
- # Seconds. No conversion necessary.
- return value
- elif units == 'm':
- # Value is in minutes.
- return 60.0 * value
- elif units == 'h':
- # Value is in hours.
- return (60.0 * 60.0) * value
- elif units == 'd':
- # Value is in days.
- return 24 * (60.0 * 60.0) * value
- else:
- raise Exception("unexpected units value '{}'".format(units))
- else:
- raise Exception("could not parse TIMEOUT spec '{}'".format(
- timeout))
-
-
-class ProcessHelper(object):
- """Provides an interface for accessing process-related functionality.
-
- This class provides a factory method that gives the caller a
- platform-specific implementation instance of the class.
-
- Clients of the class should stick to the methods provided in this
- base class.
-
- @see ProcessHelper.process_helper()
- """
- def __init__(self):
- super(ProcessHelper, self).__init__()
-
- @classmethod
- def process_helper(cls):
- """Returns a platform-specific ProcessHelper instance.
- @return a ProcessHelper instance that does the right thing for
- the current platform.
- """
-
- # If you add a new platform, create an instance here and
- # return it.
- if os.name == "nt":
- return WindowsProcessHelper()
- else:
- # For all POSIX-like systems.
- return UnixProcessHelper()
-
- def create_piped_process(self, command, new_process_group=True):
- # pylint: disable=no-self-use,unused-argument
- # As expected. We want derived classes to implement this.
- """Creates a subprocess.Popen-based class with I/O piped to the parent.
-
- @param command the command line list as would be passed to
- subprocess.Popen(). Use the list form rather than the string form.
-
- @param new_process_group indicates if the caller wants the
- process to be created in its own process group. Each OS handles
- this concept differently. It provides a level of isolation and
- can simplify or enable terminating the process tree properly.
-
- @return a subprocess.Popen-like object.
- """
- raise Exception("derived class must implement")
-
- def supports_soft_terminate(self):
- # pylint: disable=no-self-use
- # As expected. We want derived classes to implement this.
- """Indicates if the platform supports soft termination.
-
- Soft termination is the concept of a terminate mechanism that
- allows the target process to shut down nicely, but with the
- catch that the process might choose to ignore it.
-
- Platform supporter note: only mark soft terminate as supported
- if the target process has some way to evade the soft terminate
- request; otherwise, just support the hard terminate method.
-
- @return True if the platform supports a soft terminate mechanism.
- """
- # By default, we do not support a soft terminate mechanism.
- return False
-
- def soft_terminate(self, popen_process, log_file=None, want_core=True):
- # pylint: disable=no-self-use,unused-argument
- # As expected. We want derived classes to implement this.
- """Attempts to terminate the process in a polite way.
-
- This terminate method is intended to give the child process a
- chance to clean up and exit on its own, possibly with a request
- to drop a core file or equivalent (i.e. [mini-]crashdump, crashlog,
- etc.) If new_process_group was set in the process creation method
- and the platform supports it, this terminate call will attempt to
- kill the whole process tree rooted in this child process.
-
- @param popen_process the subprocess.Popen-like object returned
- by one of the process-creation methods of this class.
-
- @param log_file file-like object used to emit error-related
- logging info. May be None if no error-related info is desired.
-
- @param want_core True if the caller would like to get a core
- dump (or the analogous crash report) from the terminated process.
- """
- popen_process.terminate()
-
- def hard_terminate(self, popen_process, log_file=None):
- # pylint: disable=no-self-use,unused-argument
- # As expected. We want derived classes to implement this.
- """Attempts to terminate the process immediately.
-
- This terminate method is intended to kill child process in
- a manner in which the child process has no ability to block,
- and also has no ability to clean up properly. If new_process_group
- was specified when creating the process, and if the platform
- implementation supports it, this will attempt to kill the
- whole process tree rooted in the child process.
-
- @param popen_process the subprocess.Popen-like object returned
- by one of the process-creation methods of this class.
-
- @param log_file file-like object used to emit error-related
- logging info. May be None if no error-related info is desired.
- """
- popen_process.kill()
-
- def was_soft_terminate(self, returncode, with_core):
- # pylint: disable=no-self-use,unused-argument
- # As expected. We want derived classes to implement this.
- """Returns if Popen-like object returncode matches soft terminate.
-
- @param returncode the returncode from the Popen-like object that
- terminated with a given return code.
-
- @param with_core indicates whether the returncode should match
- a core-generating return signal.
-
- @return True when the returncode represents what the system would
- issue when a soft_terminate() with the given with_core arg occurred;
- False otherwise.
- """
- if not self.supports_soft_terminate():
- # If we don't support soft termination on this platform,
- # then this should always be False.
- return False
- else:
- # Once a platform claims to support soft terminate, it
- # needs to be able to identify it by overriding this method.
- raise Exception("platform needs to implement")
-
- def was_hard_terminate(self, returncode):
- # pylint: disable=no-self-use,unused-argument
- # As expected. We want derived classes to implement this.
- """Returns if Popen-like object returncode matches that of a hard
- terminate attempt.
-
- @param returncode the returncode from the Popen-like object that
- terminated with a given return code.
-
- @return True when the returncode represents what the system would
- issue when a hard_terminate() occurred; False
- otherwise.
- """
- raise Exception("platform needs to implement")
-
- def soft_terminate_signals(self):
- # pylint: disable=no-self-use
- """Retrieve signal numbers that can be sent to soft terminate.
- @return a list of signal numbers that can be sent to soft terminate
- a process, or None if not applicable.
- """
- return None
-
- def is_exceptional_exit(self, popen_status):
- """Returns whether the program exit status is exceptional.
-
- Returns whether the return code from a Popen process is exceptional
- (e.g. signals on POSIX systems).
-
- Derived classes should override this if they can detect exceptional
- program exit.
-
- @return True if the given popen_status represents an exceptional
- program exit; False otherwise.
- """
- return False
-
- def exceptional_exit_details(self, popen_status):
- """Returns the normalized exceptional exit code and a description.
-
- Given an exceptional exit code, returns the integral value of the
- exception (e.g. signal number for POSIX) and a description (e.g.
- signal name on POSIX) for the result.
-
- Derived classes should override this if they can detect exceptional
- program exit.
-
- It is fine to not implement this so long as is_exceptional_exit()
- always returns False.
-
- @return (normalized exception code, symbolic exception description)
- """
- raise Exception("exception_exit_details() called on unsupported class")
-
-
-class UnixProcessHelper(ProcessHelper):
- """Provides a ProcessHelper for Unix-like operating systems.
-
- This implementation supports anything that looks Posix-y
- (e.g. Darwin, Linux, *BSD, etc.)
- """
- def __init__(self):
- super(UnixProcessHelper, self).__init__()
-
- @classmethod
- def _create_new_process_group(cls):
- """Creates a new process group for the calling process."""
- os.setpgid(os.getpid(), os.getpid())
-
- def create_piped_process(self, command, new_process_group=True):
- # Determine what to run after the fork but before the exec.
- if new_process_group:
- preexec_func = self._create_new_process_group
- else:
- preexec_func = None
-
- # Create the process.
- process = subprocess.Popen(
- command,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True, # Elicits automatic byte -> string decoding in Py3
- close_fds=True,
- preexec_fn=preexec_func)
-
- # Remember whether we're using process groups for this
- # process.
- process.using_process_groups = new_process_group
- return process
-
- def supports_soft_terminate(self):
- # POSIX does support a soft terminate via:
- # * SIGTERM (no core requested)
- # * SIGQUIT (core requested if enabled, see ulimit -c)
- return True
-
- @classmethod
- def _validate_pre_terminate(cls, popen_process, log_file):
- # Validate args.
- if popen_process is None:
- raise ValueError("popen_process is None")
-
- # Ensure we have something that looks like a valid process.
- if popen_process.pid < 1:
- if log_file:
- log_file.write("skipping soft_terminate(): no process id")
- return False
-
- # We only do the process liveness check if we're not using
- # process groups. With process groups, checking if the main
- # inferior process is dead and short circuiting here is no
- # good - children of it in the process group could still be
- # alive, and they should be killed during a timeout.
- if not popen_process.using_process_groups:
- # Don't kill if it's already dead.
- popen_process.poll()
- if popen_process.returncode is not None:
- # It has a returncode. It has already stopped.
- if log_file:
- log_file.write(
- "requested to terminate pid {} but it has already "
- "terminated, returncode {}".format(
- popen_process.pid, popen_process.returncode))
- # Move along...
- return False
-
- # Good to go.
- return True
-
- def _kill_with_signal(self, popen_process, log_file, signum):
- # Validate we're ready to terminate this.
- if not self._validate_pre_terminate(popen_process, log_file):
- return
-
- # Choose kill mechanism based on whether we're targeting
- # a process group or just a process.
- if popen_process.using_process_groups:
- # if log_file:
- # log_file.write(
- # "sending signum {} to process group {} now\n".format(
- # signum, popen_process.pid))
- os.killpg(popen_process.pid, signum)
- else:
- # if log_file:
- # log_file.write(
- # "sending signum {} to process {} now\n".format(
- # signum, popen_process.pid))
- os.kill(popen_process.pid, signum)
-
- def soft_terminate(self, popen_process, log_file=None, want_core=True):
- # Choose signal based on desire for core file.
- if want_core:
- # SIGQUIT will generate core by default. Can be caught.
- signum = signal.SIGQUIT
- else:
- # SIGTERM is the traditional nice way to kill a process.
- # Can be caught, doesn't generate a core.
- signum = signal.SIGTERM
-
- self._kill_with_signal(popen_process, log_file, signum)
-
- def hard_terminate(self, popen_process, log_file=None):
- self._kill_with_signal(popen_process, log_file, signal.SIGKILL)
-
- def was_soft_terminate(self, returncode, with_core):
- if with_core:
- return returncode == -signal.SIGQUIT
- else:
- return returncode == -signal.SIGTERM
-
- def was_hard_terminate(self, returncode):
- return returncode == -signal.SIGKILL
-
- def soft_terminate_signals(self):
- return [signal.SIGQUIT, signal.SIGTERM]
-
- def is_exceptional_exit(self, popen_status):
- return popen_status < 0
-
- @classmethod
- def _signal_names_by_number(cls):
- return dict(
- (k, v) for v, k in reversed(sorted(signal.__dict__.items()))
- if v.startswith('SIG') and not v.startswith('SIG_'))
-
- def exceptional_exit_details(self, popen_status):
- signo = -popen_status
- signal_names_by_number = self._signal_names_by_number()
- signal_name = signal_names_by_number.get(signo, "")
- return (signo, signal_name)
-
-class WindowsProcessHelper(ProcessHelper):
- """Provides a Windows implementation of the ProcessHelper class."""
- def __init__(self):
- super(WindowsProcessHelper, self).__init__()
-
- def create_piped_process(self, command, new_process_group=True):
- if new_process_group:
- # We need this flag if we want os.kill() to work on the subprocess.
- creation_flags = subprocess.CREATE_NEW_PROCESS_GROUP
- else:
- creation_flags = 0
-
- return subprocess.Popen(
- command,
- stdin=subprocess.PIPE,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- universal_newlines=True, # Elicits automatic byte -> string decoding in Py3
- creationflags=creation_flags)
-
- def was_hard_terminate(self, returncode):
- return returncode != 0
-
-
-class ProcessDriver(object):
- """Drives a child process, notifies on important events, and can timeout.
-
- Clients are expected to derive from this class and override the
- on_process_started and on_process_exited methods if they want to
- hook either of those.
-
- This class supports timing out the child process in a platform-agnostic
- way. The on_process_exited method is informed if the exit was natural
- or if it was due to a timeout.
- """
- def __init__(self, soft_terminate_timeout=10.0):
- super(ProcessDriver, self).__init__()
- self.process_helper = ProcessHelper.process_helper()
- self.pid = None
- # Create the synchronization event for notifying when the
- # inferior dotest process is complete.
- self.done_event = threading.Event()
- self.io_thread = None
- self.process = None
- # Number of seconds to wait for the soft terminate to
- # wrap up, before moving to more drastic measures.
- # Might want this longer if core dumps are generated and
- # take a long time to write out.
- self.soft_terminate_timeout = soft_terminate_timeout
- # Number of seconds to wait for the hard terminate to
- # wrap up, before giving up on the io thread. This should
- # be fast.
- self.hard_terminate_timeout = 5.0
- self.returncode = None
-
- # =============================================
- # Methods for subclasses to override if desired.
- # =============================================
-
- def on_process_started(self):
- pass
-
- def on_process_exited(self, command, output, was_timeout, exit_status):
- pass
-
- def write(self, content):
- # pylint: disable=no-self-use
- # Intended - we want derived classes to be able to override
- # this and use any self state they may contain.
- sys.stdout.write(content)
-
- # ==============================================================
- # Operations used to drive processes. Clients will want to call
- # one of these.
- # ==============================================================
-
- def run_command(self, command):
- # Start up the child process and the thread that does the
- # communication pump.
- self._start_process_and_io_thread(command)
-
- # Wait indefinitely for the child process to finish
- # communicating. This indicates it has closed stdout/stderr
- # pipes and is done.
- self.io_thread.join()
- self.returncode = self.process.wait()
- if self.returncode is None:
- raise Exception(
- "no exit status available for pid {} after the "
- " inferior dotest.py should have completed".format(
- self.process.pid))
-
- # Notify of non-timeout exit.
- self.on_process_exited(
- command,
- self.io_thread.output,
- False,
- self.returncode)
-
- def run_command_with_timeout(self, command, timeout, want_core):
- # Figure out how many seconds our timeout description is requesting.
- timeout_seconds = timeout_to_seconds(timeout)
-
- # Start up the child process and the thread that does the
- # communication pump.
- self._start_process_and_io_thread(command)
-
- self._wait_with_timeout(timeout_seconds, command, want_core)
-
- # ================
- # Internal details.
- # ================
-
- def _start_process_and_io_thread(self, command):
- # Create the process.
- self.process = self.process_helper.create_piped_process(command)
- self.pid = self.process.pid
- self.on_process_started()
-
- # Ensure the event is cleared that is used for signaling
- # from the communication() thread when communication is
- # complete (i.e. the inferior process has finished).
- self.done_event.clear()
-
- self.io_thread = CommunicatorThread(
- self.process, self.done_event, self.write)
- self.io_thread.start()
-
- def _attempt_soft_kill(self, want_core):
- # The inferior dotest timed out. Attempt to clean it
- # with a non-drastic method (so it can clean up properly
- # and/or generate a core dump). Often the OS can't guarantee
- # that the process will really terminate after this.
- self.process_helper.soft_terminate(
- self.process,
- want_core=want_core,
- log_file=self)
-
- # Now wait up to a certain timeout period for the io thread
- # to say that the communication ended. If that wraps up
- # within our soft terminate timeout, we're all done here.
- self.io_thread.join(self.soft_terminate_timeout)
- if not self.io_thread.is_alive():
- # stdout/stderr were closed on the child process side. We
- # should be able to wait and reap the child process here.
- self.returncode = self.process.wait()
- # We terminated, and the done_trying result is n/a
- terminated = True
- done_trying = None
- else:
- self.write("soft kill attempt of process {} timed out "
- "after {} seconds\n".format(
- self.process.pid, self.soft_terminate_timeout))
- terminated = False
- done_trying = False
- return terminated, done_trying
-
- def _attempt_hard_kill(self):
- # Instruct the process to terminate and really force it to
- # happen. Don't give the process a chance to ignore.
- self.process_helper.hard_terminate(
- self.process,
- log_file=self)
-
- # Reap the child process. This should not hang as the
- # hard_kill() mechanism is supposed to really kill it.
- # Improvement option:
- # If this does ever hang, convert to a self.process.poll()
- # loop checking on self.process.returncode until it is not
- # None or the timeout occurs.
- self.returncode = self.process.wait()
-
- # Wait a few moments for the io thread to finish...
- self.io_thread.join(self.hard_terminate_timeout)
- if self.io_thread.is_alive():
- # ... but this is not critical if it doesn't end for some
- # reason.
- self.write(
- "hard kill of process {} timed out after {} seconds waiting "
- "for the io thread (ignoring)\n".format(
- self.process.pid, self.hard_terminate_timeout))
-
- # Set if it terminated. (Set up for optional improvement above).
- terminated = self.returncode is not None
- # Nothing else to try.
- done_trying = True
-
- return terminated, done_trying
-
- def _attempt_termination(self, attempt_count, want_core):
- if self.process_helper.supports_soft_terminate():
- # When soft termination is supported, we first try to stop
- # the process with a soft terminate. Failing that, we try
- # the hard terminate option.
- if attempt_count == 1:
- return self._attempt_soft_kill(want_core)
- elif attempt_count == 2:
- return self._attempt_hard_kill()
- else:
- # We don't have anything else to try.
- terminated = self.returncode is not None
- done_trying = True
- return terminated, done_trying
- else:
- # We only try the hard terminate option when there
- # is no soft terminate available.
- if attempt_count == 1:
- return self._attempt_hard_kill()
- else:
- # We don't have anything else to try.
- terminated = self.returncode is not None
- done_trying = True
- return terminated, done_trying
-
- def _wait_with_timeout(self, timeout_seconds, command, want_core):
- # Allow up to timeout seconds for the io thread to wrap up.
- # If that completes, the child process should be done.
- completed_normally = self.done_event.wait(timeout_seconds)
- if completed_normally:
- # Reap the child process here.
- self.returncode = self.process.wait()
- else:
- # Prepare to stop the process
- process_terminated = completed_normally
- terminate_attempt_count = 0
-
- # Try as many attempts as we support for trying to shut down
- # the child process if it's not already shut down.
- while not process_terminated:
- terminate_attempt_count += 1
- # Attempt to terminate.
- process_terminated, done_trying = self._attempt_termination(
- terminate_attempt_count, want_core)
- # Check if there's nothing more to try.
- if done_trying:
- # Break out of our termination attempt loop.
- break
-
- # At this point, we're calling it good. The process
- # finished gracefully, was shut down after one or more
- # attempts, or we failed but gave it our best effort.
- self.on_process_exited(
- command,
- self.io_thread.output,
- not completed_normally,
- self.returncode)
-
-
-def patched_init(self, *args, **kwargs):
- self.original_init(*args, **kwargs)
- # Initialize our condition variable that protects wait()/poll().
- self.wait_condition = threading.Condition()
-
-
-def patched_wait(self, *args, **kwargs):
- self.wait_condition.acquire()
- try:
- result = self.original_wait(*args, **kwargs)
- # The process finished. Signal the condition.
- self.wait_condition.notify_all()
- return result
- finally:
- self.wait_condition.release()
-
-
-def patched_poll(self, *args, **kwargs):
- self.wait_condition.acquire()
- try:
- result = self.original_poll(*args, **kwargs)
- if self.returncode is not None:
- # We did complete, and we have the return value.
- # Signal the event to indicate we're done.
- self.wait_condition.notify_all()
- return result
- finally:
- self.wait_condition.release()
-
-
-def patch_up_subprocess_popen():
- subprocess.Popen.original_init = subprocess.Popen.__init__
- subprocess.Popen.__init__ = patched_init
-
- subprocess.Popen.original_wait = subprocess.Popen.wait
- subprocess.Popen.wait = patched_wait
-
- subprocess.Popen.original_poll = subprocess.Popen.poll
- subprocess.Popen.poll = patched_poll
-
-# Replace key subprocess.Popen() threading-unprotected methods with
-# threading-protected versions.
-patch_up_subprocess_popen()
Index: packages/Python/lldbsuite/test/test_runner/lib/lldb_utils.py
===================================================================
--- packages/Python/lldbsuite/test/test_runner/lib/lldb_utils.py
+++ /dev/null
@@ -1,66 +0,0 @@
-"""
-The LLVM Compiler Infrastructure
-
-This file is distributed under the University of Illinois Open Source
-License. See LICENSE.TXT for details.
-
-Provides classes used by the test results reporting infrastructure
-within the LLDB test suite.
-
-
-This module contains utilities used by the lldb test framwork.
-"""
-
-
-class OptionalWith(object):
- # pylint: disable=too-few-public-methods
- # This is a wrapper - it is not meant to provide any extra methods.
- """Provides a wrapper for objects supporting "with", allowing None.
-
- This lets a user use the "with object" syntax for resource usage
- (e.g. locks) even when the wrapped with object is None.
-
- e.g.
-
- wrapped_lock = OptionalWith(thread.Lock())
- with wrapped_lock:
- # Do something while the lock is obtained.
- pass
-
- might_be_none = None
- wrapped_none = OptionalWith(might_be_none)
- with wrapped_none:
- # This code here still works.
- pass
-
- This prevents having to write code like this when
- a lock is optional:
-
- if lock:
- lock.acquire()
-
- try:
- code_fragament_always_run()
- finally:
- if lock:
- lock.release()
-
- And I'd posit it is safer, as it becomes impossible to
- forget the try/finally using OptionalWith(), since
- the with syntax can be used.
- """
- def __init__(self, wrapped_object):
- self.wrapped_object = wrapped_object
-
- def __enter__(self):
- if self.wrapped_object is not None:
- return self.wrapped_object.__enter__()
- else:
- return self
-
- def __exit__(self, the_type, value, traceback):
- if self.wrapped_object is not None:
- return self.wrapped_object.__exit__(the_type, value, traceback)
- else:
- # Don't suppress any exceptions
- return False
Index: packages/Python/lldbsuite/test/test_runner/__init__.py
===================================================================
--- /dev/null
+++ packages/Python/lldbsuite/test/test_runner/__init__.py
@@ -0,0 +1,2 @@
+import lldb_utils
+import process_control
Index: packages/Python/lldbsuite/test/dosep.py
===================================================================
--- packages/Python/lldbsuite/test/dosep.py
+++ packages/Python/lldbsuite/test/dosep.py
@@ -59,12 +59,8 @@
from .result_formatter import EventBuilder
-
-# Todo: Convert this folder layout to be relative-import friendly and
-# don't hack up sys.path like this
-sys.path.append(os.path.join(os.path.dirname(__file__), "test_runner", "lib"))
-import lldb_utils
-import process_control
+from .test_runner import lldb_utils
+from .test_runner import process_control
# Status codes for running command with timeout.
eTimedOut, ePassed, eFailed = 124, 0, 1
@@ -173,7 +173,7 @@
super(DoTestProcessDriver, self).__init__(
soft_terminate_timeout=soft_terminate_timeout)
self.output_file = output_file
- self.output_lock = lldb_utils.OptionalWith(output_file_lock)
+ self.output_lock = lldb_utils.optional_with(output_file_lock)
self.pid_events = pid_events
self.results = None
self.file_name = file_name
_______________________________________________
lldb-commits mailing list
[email protected]
http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits