blob: 7b48def5048096f7993e9fbb35bf54495a74d1d6 [file]
# System modules
import os
import sys
import time
import signal
# LLDB Modules
import lldb
from .lldbtest import *
from . import lldbutil
from lldbsuite.test.decorators import *
@skipIfRemote
@skipIfWindows
@add_test_categories(["pexpect"])
class PExpectTest(TestBase):
NO_DEBUG_INFO_TESTCASE = True
PROMPT = "(lldb) "
# Override this value in a subclass to make the test fail faster and make
# debugging less tedious.
TIMEOUT = 60
def expect_prompt(self):
self.child.expect_exact(self.PROMPT)
def launch(
self,
executable=None,
extra_args=None,
dimensions=None,
run_under=None,
post_spawn=None,
encoding=None,
use_colors=False,
):
# Using a log file is incompatible with using utf-8 as the encoding.
logfile = (
getattr(sys.stdout, "buffer", sys.stdout)
if (self.TraceOn() and not encoding)
else None
)
args = []
if run_under is not None:
args += run_under
args += [lldbtest_config.lldbExec, "--no-lldbinit"]
if not use_colors:
args.append("--no-use-colors")
for cmd in self.setUpCommands():
if "use-color false" in cmd and use_colors:
continue
args += ["-O", cmd]
if executable is not None:
args += ["--file", executable]
if extra_args is not None:
args.extend(extra_args)
env = dict(os.environ)
env["TERM"] = "vt100"
env["HOME"] = self.getBuildDir()
timeout = self.TIMEOUT
import pexpect
self.child = pexpect.spawn(
args[0],
args=args[1:],
logfile=logfile,
timeout=timeout,
dimensions=dimensions,
env=env,
encoding=encoding,
)
# The interval in which we poll the LLDB process if we are
# closing/terminating the process.
self.polling_interval = 0.05
# Timeout for each attempt to shut down LLDB at the end
# of the test. Note that there can be multiple attempts
# to shut down LLDB.
self.shutdown_timeout = timeout / 10
if post_spawn is not None:
post_spawn()
self.expect_prompt()
for cmd in self.setUpCommands():
if "use-color false" in cmd and use_colors:
continue
self.child.expect_exact(cmd)
self.expect_prompt()
if executable is not None:
self.child.expect_exact("target create")
self.child.expect_exact("Current executable set to")
self.expect_prompt()
def expect(self, cmd, substrs=None):
self.assertNotIn("\n", cmd)
# If 'substrs' is a string then this code would just check that every
# character of the string is in the output.
assert not isinstance(substrs, str), "substrs must be a collection of strings"
self.child.sendline(cmd)
if substrs is not None:
for s in substrs:
self.child.expect_exact(s)
self.expect_prompt()
def tearDown(self):
# Ensure the child is always cleaned up, even if the test didn't call
# quit() explicitly or failed before reaching it.
if self.child is not None:
self.quit(gracefully=True)
super().tearDown()
def _poll_until_dead(self, proc):
"""Poll proc.isalive() for up to shutdown_timeout seconds.
Returns True if the process exited within that window."""
deadline = time.monotonic() + self.shutdown_timeout
while time.monotonic() < deadline:
if not proc.isalive():
return True
time.sleep(self.polling_interval)
return not proc.isalive()
def _terminate_child(self, force=False):
"""Terminate the child using signals."""
proc = self.child.ptyproc
if not proc.isalive():
return True
try:
# List of signals to try, starting from a friendly
# SIGHUP, SIGINT to SIGKILL if all else fails.
signals = [signal.SIGHUP, signal.SIGINT]
if force:
signals.append(signal.SIGKILL)
for sig in signals:
proc.kill(sig)
if self._poll_until_dead(proc):
return True
return False
except OSError:
# This handles a TOCTOU issue where we think the process is
# considered as 'isalive', then is actually killed by the
# kernel and then we try to kill it.
time.sleep(self.polling_interval)
return not proc.isalive()
def _close_child(self, force=False):
"""Close the connection to the child and terminate it if needed."""
ptyproc = self.child.ptyproc
if ptyproc.closed:
return
# Close the PTY master fd first.
try:
self.child.sendeof()
ptyproc.fileobj.close()
except OSError:
# This could happen if LLDB shut down at the same time.
pass
if not force:
# Poll briefly for the process to respond to the EOF before
# escalating to signals.
self._poll_until_dead(ptyproc)
if ptyproc.isalive():
# Kill the child with signals. This should always succeed.
assert self._terminate_child(force=force)
# Make sure the object is now marked as closed.
ptyproc.fd = -1
ptyproc.closed = True
def quit(self, gracefully=True):
if self.child is None:
return
try:
self._close_child(force=not gracefully)
except OSError:
# This can happen if LLDB quit itself because it is running in
# batch mode.
pass
self.child = None
def cursor_forward_escape_seq(self, chars_to_move):
"""
Returns the escape sequence to move the cursor forward/right
by a certain amount of characters.
"""
return b"\x1b\\[" + str(chars_to_move).encode("utf-8") + b"C"