summaryrefslogtreecommitdiffstats
path: root/gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py
diff options
context:
space:
mode:
authorpatrick <patrick@openbsd.org>2020-08-03 14:33:06 +0000
committerpatrick <patrick@openbsd.org>2020-08-03 14:33:06 +0000
commit061da546b983eb767bad15e67af1174fb0bcf31c (patch)
tree83c78b820819d70aa40c36d90447978b300078c5 /gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py
parentImport LLVM 10.0.0 release including clang, lld and lldb. (diff)
downloadwireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.tar.xz
wireguard-openbsd-061da546b983eb767bad15e67af1174fb0bcf31c.zip
Import LLVM 10.0.0 release including clang, lld and lldb.
ok hackroom tested by plenty
Diffstat (limited to 'gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py')
-rw-r--r--gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py2520
1 files changed, 2520 insertions, 0 deletions
diff --git a/gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py b/gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py
new file mode 100644
index 00000000000..2b83d26d234
--- /dev/null
+++ b/gnu/llvm/lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -0,0 +1,2520 @@
+"""
+LLDB module which provides the abstract base class of lldb test case.
+
+The concrete subclass can override lldbtest.TesBase in order to inherit the
+common behavior for unitest.TestCase.setUp/tearDown implemented in this file.
+
+The subclass should override the attribute mydir in order for the python runtime
+to locate the individual test cases when running as part of a large test suite
+or when running each test case as a separate python invocation.
+
+./dotest.py provides a test driver which sets up the environment to run the
+entire of part of the test suite . Example:
+
+# Exercises the test suite in the types directory....
+/Volumes/data/lldb/svn/ToT/test $ ./dotest.py -A x86_64 types
+...
+
+Session logs for test failures/errors/unexpected successes will go into directory '2012-05-16-13_35_42'
+Command invoked: python ./dotest.py -A x86_64 types
+compilers=['clang']
+
+Configuration: arch=x86_64 compiler=clang
+----------------------------------------------------------------------
+Collected 72 tests
+
+........................................................................
+----------------------------------------------------------------------
+Ran 72 tests in 135.468s
+
+OK
+$
+"""
+
+from __future__ import absolute_import
+from __future__ import print_function
+
+# System modules
+import abc
+from distutils.version import LooseVersion
+from functools import wraps
+import gc
+import glob
+import io
+import os.path
+import re
+import shutil
+import signal
+from subprocess import *
+import sys
+import time
+import traceback
+import distutils.spawn
+
+# Third-party modules
+import unittest2
+from six import add_metaclass
+from six import StringIO as SixStringIO
+import six
+
+# LLDB modules
+import lldb
+from . import configuration
+from . import decorators
+from . import lldbplatformutil
+from . import lldbtest_config
+from . import lldbutil
+from . import test_categories
+from lldbsuite.support import encoded_file
+from lldbsuite.support import funcutils
+
+# See also dotest.parseOptionsAndInitTestdirs(), where the environment variables
+# LLDB_COMMAND_TRACE is set from '-t' option.
+
+# By default, traceAlways is False.
+if "LLDB_COMMAND_TRACE" in os.environ and os.environ[
+ "LLDB_COMMAND_TRACE"] == "YES":
+ traceAlways = True
+else:
+ traceAlways = False
+
+# By default, doCleanup is True.
+if "LLDB_DO_CLEANUP" in os.environ and os.environ["LLDB_DO_CLEANUP"] == "NO":
+ doCleanup = False
+else:
+ doCleanup = True
+
+
+#
+# Some commonly used assert messages.
+#
+
+COMMAND_FAILED_AS_EXPECTED = "Command has failed as expected"
+
+CURRENT_EXECUTABLE_SET = "Current executable set successfully"
+
+PROCESS_IS_VALID = "Process is valid"
+
+PROCESS_KILLED = "Process is killed successfully"
+
+PROCESS_EXITED = "Process exited successfully"
+
+PROCESS_STOPPED = "Process status should be stopped"
+
+RUN_SUCCEEDED = "Process is launched successfully"
+
+RUN_COMPLETED = "Process exited successfully"
+
+BACKTRACE_DISPLAYED_CORRECTLY = "Backtrace displayed correctly"
+
+BREAKPOINT_CREATED = "Breakpoint created successfully"
+
+BREAKPOINT_STATE_CORRECT = "Breakpoint state is correct"
+
+BREAKPOINT_PENDING_CREATED = "Pending breakpoint created successfully"
+
+BREAKPOINT_HIT_ONCE = "Breakpoint resolved with hit count = 1"
+
+BREAKPOINT_HIT_TWICE = "Breakpoint resolved with hit count = 2"
+
+BREAKPOINT_HIT_THRICE = "Breakpoint resolved with hit count = 3"
+
+MISSING_EXPECTED_REGISTERS = "At least one expected register is unavailable."
+
+OBJECT_PRINTED_CORRECTLY = "Object printed correctly"
+
+SOURCE_DISPLAYED_CORRECTLY = "Source code displayed correctly"
+
+STEP_OUT_SUCCEEDED = "Thread step-out succeeded"
+
+STOPPED_DUE_TO_EXC_BAD_ACCESS = "Process should be stopped due to bad access exception"
+
+STOPPED_DUE_TO_ASSERT = "Process should be stopped due to an assertion"
+
+STOPPED_DUE_TO_BREAKPOINT = "Process should be stopped due to breakpoint"
+
+STOPPED_DUE_TO_BREAKPOINT_WITH_STOP_REASON_AS = "%s, %s" % (
+ STOPPED_DUE_TO_BREAKPOINT, "instead, the actual stop reason is: '%s'")
+
+STOPPED_DUE_TO_BREAKPOINT_CONDITION = "Stopped due to breakpoint condition"
+
+STOPPED_DUE_TO_BREAKPOINT_IGNORE_COUNT = "Stopped due to breakpoint and ignore count"
+
+STOPPED_DUE_TO_BREAKPOINT_JITTED_CONDITION = "Stopped due to breakpoint jitted condition"
+
+STOPPED_DUE_TO_SIGNAL = "Process state is stopped due to signal"
+
+STOPPED_DUE_TO_STEP_IN = "Process state is stopped due to step in"
+
+STOPPED_DUE_TO_WATCHPOINT = "Process should be stopped due to watchpoint"
+
+DATA_TYPES_DISPLAYED_CORRECTLY = "Data type(s) displayed correctly"
+
+VALID_BREAKPOINT = "Got a valid breakpoint"
+
+VALID_BREAKPOINT_LOCATION = "Got a valid breakpoint location"
+
+VALID_COMMAND_INTERPRETER = "Got a valid command interpreter"
+
+VALID_FILESPEC = "Got a valid filespec"
+
+VALID_MODULE = "Got a valid module"
+
+VALID_PROCESS = "Got a valid process"
+
+VALID_SYMBOL = "Got a valid symbol"
+
+VALID_TARGET = "Got a valid target"
+
+VALID_PLATFORM = "Got a valid platform"
+
+VALID_TYPE = "Got a valid type"
+
+VALID_VARIABLE = "Got a valid variable"
+
+VARIABLES_DISPLAYED_CORRECTLY = "Variable(s) displayed correctly"
+
+WATCHPOINT_CREATED = "Watchpoint created successfully"
+
+
+def CMD_MSG(str):
+ '''A generic "Command '%s' returns successfully" message generator.'''
+ return "Command '%s' returns successfully" % str
+
+
+def COMPLETION_MSG(str_before, str_after, completions):
+ '''A generic message generator for the completion mechanism.'''
+ return ("'%s' successfully completes to '%s', but completions were:\n%s"
+ % (str_before, str_after, "\n".join(completions)))
+
+
+def EXP_MSG(str, actual, exe):
+ '''A generic "'%s' returns expected result" message generator if exe.
+ Otherwise, it generates "'%s' matches expected result" message.'''
+
+ return "'%s' %s expected result, got '%s'" % (
+ str, 'returns' if exe else 'matches', actual.strip())
+
+
+def SETTING_MSG(setting):
+ '''A generic "Value of setting '%s' is correct" message generator.'''
+ return "Value of setting '%s' is correct" % setting
+
+
+def line_number(filename, string_to_match):
+ """Helper function to return the line number of the first matched string."""
+ with io.open(filename, mode='r', encoding="utf-8") as f:
+ for i, line in enumerate(f):
+ if line.find(string_to_match) != -1:
+ # Found our match.
+ return i + 1
+ raise Exception(
+ "Unable to find '%s' within file %s" %
+ (string_to_match, filename))
+
+def get_line(filename, line_number):
+ """Return the text of the line at the 1-based line number."""
+ with io.open(filename, mode='r', encoding="utf-8") as f:
+ return f.readlines()[line_number - 1]
+
+def pointer_size():
+ """Return the pointer size of the host system."""
+ import ctypes
+ a_pointer = ctypes.c_void_p(0xffff)
+ return 8 * ctypes.sizeof(a_pointer)
+
+
+def is_exe(fpath):
+ """Returns true if fpath is an executable."""
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+
+
+def which(program):
+ """Returns the full path to a program; None otherwise."""
+ fpath, fname = os.path.split(program)
+ if fpath:
+ if is_exe(program):
+ return program
+ else:
+ for path in os.environ["PATH"].split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file):
+ return exe_file
+ return None
+
+
+class recording(SixStringIO):
+ """
+ A nice little context manager for recording the debugger interactions into
+ our session object. If trace flag is ON, it also emits the interactions
+ into the stderr.
+ """
+
+ def __init__(self, test, trace):
+ """Create a SixStringIO instance; record the session obj and trace flag."""
+ SixStringIO.__init__(self)
+ # The test might not have undergone the 'setUp(self)' phase yet, so that
+ # the attribute 'session' might not even exist yet.
+ self.session = getattr(test, "session", None) if test else None
+ self.trace = trace
+
+ def __enter__(self):
+ """
+ Context management protocol on entry to the body of the with statement.
+ Just return the SixStringIO object.
+ """
+ return self
+
+ def __exit__(self, type, value, tb):
+ """
+ Context management protocol on exit from the body of the with statement.
+ If trace is ON, it emits the recordings into stderr. Always add the
+ recordings to our session object. And close the SixStringIO object, too.
+ """
+ if self.trace:
+ print(self.getvalue(), file=sys.stderr)
+ if self.session:
+ print(self.getvalue(), file=self.session)
+ self.close()
+
+
+@add_metaclass(abc.ABCMeta)
+class _BaseProcess(object):
+
+ @abc.abstractproperty
+ def pid(self):
+ """Returns process PID if has been launched already."""
+
+ @abc.abstractmethod
+ def launch(self, executable, args):
+ """Launches new process with given executable and args."""
+
+ @abc.abstractmethod
+ def terminate(self):
+ """Terminates previously launched process.."""
+
+
+class _LocalProcess(_BaseProcess):
+
+ def __init__(self, trace_on):
+ self._proc = None
+ self._trace_on = trace_on
+ self._delayafterterminate = 0.1
+
+ @property
+ def pid(self):
+ return self._proc.pid
+
+ def launch(self, executable, args):
+ self._proc = Popen(
+ [executable] + args,
+ stdout=open(
+ os.devnull) if not self._trace_on else None,
+ stdin=PIPE)
+
+ def terminate(self):
+ if self._proc.poll() is None:
+ # Terminate _proc like it does the pexpect
+ signals_to_try = [
+ sig for sig in [
+ 'SIGHUP',
+ 'SIGCONT',
+ 'SIGINT'] if sig in dir(signal)]
+ for sig in signals_to_try:
+ try:
+ self._proc.send_signal(getattr(signal, sig))
+ time.sleep(self._delayafterterminate)
+ if self._proc.poll() is not None:
+ return
+ except ValueError:
+ pass # Windows says SIGINT is not a valid signal to send
+ self._proc.terminate()
+ time.sleep(self._delayafterterminate)
+ if self._proc.poll() is not None:
+ return
+ self._proc.kill()
+ time.sleep(self._delayafterterminate)
+
+ def poll(self):
+ return self._proc.poll()
+
+
+class _RemoteProcess(_BaseProcess):
+
+ def __init__(self, install_remote):
+ self._pid = None
+ self._install_remote = install_remote
+
+ @property
+ def pid(self):
+ return self._pid
+
+ def launch(self, executable, args):
+ if self._install_remote:
+ src_path = executable
+ dst_path = lldbutil.join_remote_paths(
+ lldb.remote_platform.GetWorkingDirectory(), os.path.basename(executable))
+
+ dst_file_spec = lldb.SBFileSpec(dst_path, False)
+ err = lldb.remote_platform.Install(
+ lldb.SBFileSpec(src_path, True), dst_file_spec)
+ if err.Fail():
+ raise Exception(
+ "remote_platform.Install('%s', '%s') failed: %s" %
+ (src_path, dst_path, err))
+ else:
+ dst_path = executable
+ dst_file_spec = lldb.SBFileSpec(executable, False)
+
+ launch_info = lldb.SBLaunchInfo(args)
+ launch_info.SetExecutableFile(dst_file_spec, True)
+ launch_info.SetWorkingDirectory(
+ lldb.remote_platform.GetWorkingDirectory())
+
+ # Redirect stdout and stderr to /dev/null
+ launch_info.AddSuppressFileAction(1, False, True)
+ launch_info.AddSuppressFileAction(2, False, True)
+
+ err = lldb.remote_platform.Launch(launch_info)
+ if err.Fail():
+ raise Exception(
+ "remote_platform.Launch('%s', '%s') failed: %s" %
+ (dst_path, args, err))
+ self._pid = launch_info.GetProcessID()
+
+ def terminate(self):
+ lldb.remote_platform.Kill(self._pid)
+
+# From 2.7's subprocess.check_output() convenience function.
+# Return a tuple (stdoutdata, stderrdata).
+
+
+def system(commands, **kwargs):
+ r"""Run an os command with arguments and return its output as a byte string.
+
+ If the exit code was non-zero it raises a CalledProcessError. The
+ CalledProcessError object will have the return code in the returncode
+ attribute and output in the output attribute.
+
+ The arguments are the same as for the Popen constructor. Example:
+
+ >>> check_output(["ls", "-l", "/dev/null"])
+ 'crw-rw-rw- 1 root root 1, 3 Oct 18 2007 /dev/null\n'
+
+ The stdout argument is not allowed as it is used internally.
+ To capture standard error in the result, use stderr=STDOUT.
+
+ >>> check_output(["/bin/sh", "-c",
+ ... "ls -l non_existent_file ; exit 0"],
+ ... stderr=STDOUT)
+ 'ls: non_existent_file: No such file or directory\n'
+ """
+
+ # Assign the sender object to variable 'test' and remove it from kwargs.
+ test = kwargs.pop('sender', None)
+
+ # [['make', 'clean', 'foo'], ['make', 'foo']] -> ['make clean foo', 'make foo']
+ commandList = [' '.join(x) for x in commands]
+ output = ""
+ error = ""
+ for shellCommand in commandList:
+ if 'stdout' in kwargs:
+ raise ValueError(
+ 'stdout argument not allowed, it will be overridden.')
+ if 'shell' in kwargs and kwargs['shell'] == False:
+ raise ValueError('shell=False not allowed')
+ process = Popen(
+ shellCommand,
+ stdout=PIPE,
+ stderr=PIPE,
+ shell=True,
+ **kwargs)
+ pid = process.pid
+ this_output, this_error = process.communicate()
+ retcode = process.poll()
+
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = shellCommand
+ cpe = CalledProcessError(retcode, cmd)
+ # Ensure caller can access the stdout/stderr.
+ cpe.lldb_extensions = {
+ "stdout_content": this_output,
+ "stderr_content": this_error,
+ "command": shellCommand
+ }
+ raise cpe
+ output = output + this_output.decode("utf-8")
+ error = error + this_error.decode("utf-8")
+ return (output, error)
+
+
+def getsource_if_available(obj):
+ """
+ Return the text of the source code for an object if available. Otherwise,
+ a print representation is returned.
+ """
+ import inspect
+ try:
+ return inspect.getsource(obj)
+ except:
+ return repr(obj)
+
+
+def builder_module():
+ if sys.platform.startswith("freebsd"):
+ return __import__("builder_freebsd")
+ if sys.platform.startswith("openbsd"):
+ return __import__("builder_openbsd")
+ if sys.platform.startswith("netbsd"):
+ return __import__("builder_netbsd")
+ if sys.platform.startswith("linux"):
+ # sys.platform with Python-3.x returns 'linux', but with
+ # Python-2.x it returns 'linux2'.
+ return __import__("builder_linux")
+ return __import__("builder_" + sys.platform)
+
+
+class Base(unittest2.TestCase):
+ """
+ Abstract base for performing lldb (see TestBase) or other generic tests (see
+ BenchBase for one example). lldbtest.Base works with the test driver to
+ accomplish things.
+
+ """
+
+ # The concrete subclass should override this attribute.
+ mydir = None
+
+ # Keep track of the old current working directory.
+ oldcwd = None
+
+ @staticmethod
+ def compute_mydir(test_file):
+ '''Subclasses should call this function to correctly calculate the
+ required "mydir" attribute as follows:
+
+ mydir = TestBase.compute_mydir(__file__)
+ '''
+ # /abs/path/to/packages/group/subdir/mytest.py -> group/subdir
+ rel_prefix = test_file[len(os.environ["LLDB_TEST"]) + 1:]
+ return os.path.dirname(rel_prefix)
+
+ def TraceOn(self):
+ """Returns True if we are in trace mode (tracing detailed test execution)."""
+ return traceAlways
+
+ @classmethod
+ def setUpClass(cls):
+ """
+ Python unittest framework class setup fixture.
+ Do current directory manipulation.
+ """
+ # Fail fast if 'mydir' attribute is not overridden.
+ if not cls.mydir or len(cls.mydir) == 0:
+ raise Exception("Subclasses must override the 'mydir' attribute.")
+
+ # Save old working directory.
+ cls.oldcwd = os.getcwd()
+
+ # Change current working directory if ${LLDB_TEST} is defined.
+ # See also dotest.py which sets up ${LLDB_TEST}.
+ if ("LLDB_TEST" in os.environ):
+ full_dir = os.path.join(os.environ["LLDB_TEST"],
+ cls.mydir)
+ if traceAlways:
+ print("Change dir to:", full_dir, file=sys.stderr)
+ os.chdir(full_dir)
+
+ # Set platform context.
+ cls.platformContext = lldbplatformutil.createPlatformContext()
+
+ @classmethod
+ def tearDownClass(cls):
+ """
+ Python unittest framework class teardown fixture.
+ Do class-wide cleanup.
+ """
+
+ if doCleanup:
+ # First, let's do the platform-specific cleanup.
+ module = builder_module()
+ module.cleanup()
+
+ # Subclass might have specific cleanup function defined.
+ if getattr(cls, "classCleanup", None):
+ if traceAlways:
+ print(
+ "Call class-specific cleanup function for class:",
+ cls,
+ file=sys.stderr)
+ try:
+ cls.classCleanup()
+ except:
+ exc_type, exc_value, exc_tb = sys.exc_info()
+ traceback.print_exception(exc_type, exc_value, exc_tb)
+
+ # Restore old working directory.
+ if traceAlways:
+ print("Restore dir to:", cls.oldcwd, file=sys.stderr)
+ os.chdir(cls.oldcwd)
+
+ def enableLogChannelsForCurrentTest(self):
+ if len(lldbtest_config.channels) == 0:
+ return
+
+ # if debug channels are specified in lldbtest_config.channels,
+ # create a new set of log files for every test
+ log_basename = self.getLogBasenameForCurrentTest()
+
+ # confirm that the file is writeable
+ host_log_path = "{}-host.log".format(log_basename)
+ open(host_log_path, 'w').close()
+
+ log_enable = "log enable -Tpn -f {} ".format(host_log_path)
+ for channel_with_categories in lldbtest_config.channels:
+ channel_then_categories = channel_with_categories.split(' ', 1)
+ channel = channel_then_categories[0]
+ if len(channel_then_categories) > 1:
+ categories = channel_then_categories[1]
+ else:
+ categories = "default"
+
+ if channel == "gdb-remote" and lldb.remote_platform is None:
+ # communicate gdb-remote categories to debugserver
+ os.environ["LLDB_DEBUGSERVER_LOG_FLAGS"] = categories
+
+ self.ci.HandleCommand(
+ log_enable + channel_with_categories, self.res)
+ if not self.res.Succeeded():
+ raise Exception(
+ 'log enable failed (check LLDB_LOG_OPTION env variable)')
+
+ # Communicate log path name to debugserver & lldb-server
+ # For remote debugging, these variables need to be set when starting the platform
+ # instance.
+ if lldb.remote_platform is None:
+ server_log_path = "{}-server.log".format(log_basename)
+ open(server_log_path, 'w').close()
+ os.environ["LLDB_DEBUGSERVER_LOG_FILE"] = server_log_path
+
+ # Communicate channels to lldb-server
+ os.environ["LLDB_SERVER_LOG_CHANNELS"] = ":".join(
+ lldbtest_config.channels)
+
+ self.addTearDownHook(self.disableLogChannelsForCurrentTest)
+
+ def disableLogChannelsForCurrentTest(self):
+ # close all log files that we opened
+ for channel_and_categories in lldbtest_config.channels:
+ # channel format - <channel-name> [<category0> [<category1> ...]]
+ channel = channel_and_categories.split(' ', 1)[0]
+ self.ci.HandleCommand("log disable " + channel, self.res)
+ if not self.res.Succeeded():
+ raise Exception(
+ 'log disable failed (check LLDB_LOG_OPTION env variable)')
+
+ # Retrieve the server log (if any) from the remote system. It is assumed the server log
+ # is writing to the "server.log" file in the current test directory. This can be
+ # achieved by setting LLDB_DEBUGSERVER_LOG_FILE="server.log" when starting remote
+ # platform. If the remote logging is not enabled, then just let the Get() command silently
+ # fail.
+ if lldb.remote_platform:
+ lldb.remote_platform.Get(
+ lldb.SBFileSpec("server.log"), lldb.SBFileSpec(
+ self.getLogBasenameForCurrentTest() + "-server.log"))
+
+ def setPlatformWorkingDir(self):
+ if not lldb.remote_platform or not configuration.lldb_platform_working_dir:
+ return
+
+ components = self.mydir.split(os.path.sep) + [str(self.test_number), self.getBuildDirBasename()]
+ remote_test_dir = configuration.lldb_platform_working_dir
+ for c in components:
+ remote_test_dir = lldbutil.join_remote_paths(remote_test_dir, c)
+ error = lldb.remote_platform.MakeDirectory(
+ remote_test_dir, 448) # 448 = 0o700
+ if error.Fail():
+ raise Exception("making remote directory '%s': %s" % (
+ remote_test_dir, error))
+
+ lldb.remote_platform.SetWorkingDirectory(remote_test_dir)
+
+ # This function removes all files from the current working directory while leaving
+ # the directories in place. The cleaup is required to reduce the disk space required
+ # by the test suite while leaving the directories untouched is neccessary because
+ # sub-directories might belong to an other test
+ def clean_working_directory():
+ # TODO: Make it working on Windows when we need it for remote debugging support
+ # TODO: Replace the heuristic to remove the files with a logic what collects the
+ # list of files we have to remove during test runs.
+ shell_cmd = lldb.SBPlatformShellCommand(
+ "rm %s/*" % remote_test_dir)
+ lldb.remote_platform.Run(shell_cmd)
+ self.addTearDownHook(clean_working_directory)
+
+ def getSourceDir(self):
+ """Return the full path to the current test."""
+ return os.path.join(os.environ["LLDB_TEST"], self.mydir)
+
+ def getBuildDirBasename(self):
+ return self.__class__.__module__ + "." + self.testMethodName
+
+ def getBuildDir(self):
+ """Return the full path to the current test."""
+ return os.path.join(os.environ["LLDB_BUILD"], self.mydir,
+ self.getBuildDirBasename())
+
+
+ def makeBuildDir(self):
+ """Create the test-specific working directory, deleting any previous
+ contents."""
+ # See also dotest.py which sets up ${LLDB_BUILD}.
+ bdir = self.getBuildDir()
+ if os.path.isdir(bdir):
+ shutil.rmtree(bdir)
+ lldbutil.mkdir_p(bdir)
+
+ def getBuildArtifact(self, name="a.out"):
+ """Return absolute path to an artifact in the test's build directory."""
+ return os.path.join(self.getBuildDir(), name)
+
+ def getSourcePath(self, name):
+ """Return absolute path to a file in the test's source directory."""
+ return os.path.join(self.getSourceDir(), name)
+
+ @classmethod
+ def setUpCommands(cls):
+ commands = [
+ # Disable Spotlight lookup. The testsuite creates
+ # different binaries with the same UUID, because they only
+ # differ in the debug info, which is not being hashed.
+ "settings set symbols.enable-external-lookup false",
+
+ # Testsuite runs in parallel and the host can have also other load.
+ "settings set plugin.process.gdb-remote.packet-timeout 60",
+
+ 'settings set symbols.clang-modules-cache-path "{}"'.format(
+ configuration.lldb_module_cache_dir),
+ "settings set use-color false",
+ ]
+ # Make sure that a sanitizer LLDB's environment doesn't get passed on.
+ if cls.platformContext and cls.platformContext.shlib_environment_var in os.environ:
+ commands.append('settings set target.env-vars {}='.format(
+ cls.platformContext.shlib_environment_var))
+
+ # Set environment variables for the inferior.
+ if lldbtest_config.inferior_env:
+ commands.append('settings set target.env-vars {}'.format(
+ lldbtest_config.inferior_env))
+ return commands
+
+ def setUp(self):
+ """Fixture for unittest test case setup.
+
+ It works with the test driver to conditionally skip tests and does other
+ initializations."""
+ #import traceback
+ # traceback.print_stack()
+
+ if "LIBCXX_PATH" in os.environ:
+ self.libcxxPath = os.environ["LIBCXX_PATH"]
+ else:
+ self.libcxxPath = None
+
+ if "LLDBVSCODE_EXEC" in os.environ:
+ self.lldbVSCodeExec = os.environ["LLDBVSCODE_EXEC"]
+ else:
+ self.lldbVSCodeExec = None
+
+ self.lldbOption = " ".join(
+ "-o '" + s + "'" for s in self.setUpCommands())
+
+ # If we spawn an lldb process for test (via pexpect), do not load the
+ # init file unless told otherwise.
+ if os.environ.get("NO_LLDBINIT") != "NO":
+ self.lldbOption += " --no-lldbinit"
+
+ # Assign the test method name to self.testMethodName.
+ #
+ # For an example of the use of this attribute, look at test/types dir.
+ # There are a bunch of test cases under test/types and we don't want the
+ # module cacheing subsystem to be confused with executable name "a.out"
+ # used for all the test cases.
+ self.testMethodName = self._testMethodName
+
+ # This is for the case of directly spawning 'lldb'/'gdb' and interacting
+ # with it using pexpect.
+ self.child = None
+ self.child_prompt = "(lldb) "
+ # If the child is interacting with the embedded script interpreter,
+ # there are two exits required during tear down, first to quit the
+ # embedded script interpreter and second to quit the lldb command
+ # interpreter.
+ self.child_in_script_interpreter = False
+
+ # These are for customized teardown cleanup.
+ self.dict = None
+ self.doTearDownCleanup = False
+ # And in rare cases where there are multiple teardown cleanups.
+ self.dicts = []
+ self.doTearDownCleanups = False
+
+ # List of spawned subproces.Popen objects
+ self.subprocesses = []
+
+ # List of forked process PIDs
+ self.forkedProcessPids = []
+
+ # Create a string buffer to record the session info, to be dumped into a
+ # test case specific file if test failure is encountered.
+ self.log_basename = self.getLogBasenameForCurrentTest()
+
+ session_file = "{}.log".format(self.log_basename)
+ # Python 3 doesn't support unbuffered I/O in text mode. Open buffered.
+ self.session = encoded_file.open(session_file, "utf-8", mode="w")
+
+ # Optimistically set __errored__, __failed__, __expected__ to False
+ # initially. If the test errored/failed, the session info
+ # (self.session) is then dumped into a session specific file for
+ # diagnosis.
+ self.__cleanup_errored__ = False
+ self.__errored__ = False
+ self.__failed__ = False
+ self.__expected__ = False
+ # We are also interested in unexpected success.
+ self.__unexpected__ = False
+ # And skipped tests.
+ self.__skipped__ = False
+
+ # See addTearDownHook(self, hook) which allows the client to add a hook
+ # function to be run during tearDown() time.
+ self.hooks = []
+
+ # See HideStdout(self).
+ self.sys_stdout_hidden = False
+
+ if self.platformContext:
+ # set environment variable names for finding shared libraries
+ self.dylibPath = self.platformContext.shlib_environment_var
+
+ # Create the debugger instance if necessary.
+ try:
+ self.dbg = lldb.DBG
+ except AttributeError:
+ self.dbg = lldb.SBDebugger.Create()
+
+ if not self.dbg:
+ raise Exception('Invalid debugger instance')
+
+ # Retrieve the associated command interpreter instance.
+ self.ci = self.dbg.GetCommandInterpreter()
+ if not self.ci:
+ raise Exception('Could not get the command interpreter')
+
+ # And the result object.
+ self.res = lldb.SBCommandReturnObject()
+
+ self.setPlatformWorkingDir()
+ self.enableLogChannelsForCurrentTest()
+
+ lib_dir = os.environ["LLDB_LIB_DIR"]
+ self.dsym = None
+ self.framework_dir = None
+ self.darwinWithFramework = self.platformIsDarwin()
+ if sys.platform.startswith("darwin"):
+ # Handle the framework environment variable if it is set
+ if hasattr(lldbtest_config, 'lldb_framework_path'):
+ framework_path = lldbtest_config.lldb_framework_path
+ # Framework dir should be the directory containing the framework
+ self.framework_dir = framework_path[:framework_path.rfind('LLDB.framework')]
+ # If a framework dir was not specified assume the Xcode build
+ # directory layout where the framework is in LLDB_LIB_DIR.
+ else:
+ self.framework_dir = lib_dir
+ self.dsym = os.path.join(self.framework_dir, 'LLDB.framework', 'LLDB')
+ # If the framework binary doesn't exist, assume we didn't actually
+ # build a framework, and fallback to standard *nix behavior by
+ # setting framework_dir and dsym to None.
+ if not os.path.exists(self.dsym):
+ self.framework_dir = None
+ self.dsym = None
+ self.darwinWithFramework = False
+ self.makeBuildDir()
+
+ def setAsync(self, value):
+ """ Sets async mode to True/False and ensures it is reset after the testcase completes."""
+ old_async = self.dbg.GetAsync()
+ self.dbg.SetAsync(value)
+ self.addTearDownHook(lambda: self.dbg.SetAsync(old_async))
+
+ def cleanupSubprocesses(self):
+ # Ensure any subprocesses are cleaned up
+ for p in self.subprocesses:
+ p.terminate()
+ del p
+ del self.subprocesses[:]
+ # Ensure any forked processes are cleaned up
+ for pid in self.forkedProcessPids:
+ if os.path.exists("/proc/" + str(pid)):
+ os.kill(pid, signal.SIGTERM)
+
+ def spawnSubprocess(self, executable, args=[], install_remote=True):
+ """ Creates a subprocess.Popen object with the specified executable and arguments,
+ saves it in self.subprocesses, and returns the object.
+ NOTE: if using this function, ensure you also call:
+
+ self.addTearDownHook(self.cleanupSubprocesses)
+
+ otherwise the test suite will leak processes.
+ """
+ proc = _RemoteProcess(
+ install_remote) if lldb.remote_platform else _LocalProcess(self.TraceOn())
+ proc.launch(executable, args)
+ self.subprocesses.append(proc)
+ return proc
+
+ def forkSubprocess(self, executable, args=[]):
+ """ Fork a subprocess with its own group ID.
+ NOTE: if using this function, ensure you also call:
+
+ self.addTearDownHook(self.cleanupSubprocesses)
+
+ otherwise the test suite will leak processes.
+ """
+ child_pid = os.fork()
+ if child_pid == 0:
+ # If more I/O support is required, this can be beefed up.
+ fd = os.open(os.devnull, os.O_RDWR)
+ os.dup2(fd, 1)
+ os.dup2(fd, 2)
+ # This call causes the child to have its of group ID
+ os.setpgid(0, 0)
+ os.execvp(executable, [executable] + args)
+ # Give the child time to get through the execvp() call
+ time.sleep(0.1)
+ self.forkedProcessPids.append(child_pid)
+ return child_pid
+
+ def HideStdout(self):
+ """Hide output to stdout from the user.
+
+ During test execution, there might be cases where we don't want to show the
+ standard output to the user. For example,
+
+ self.runCmd(r'''sc print("\n\n\tHello!\n")''')
+
+ tests whether command abbreviation for 'script' works or not. There is no
+ need to show the 'Hello' output to the user as long as the 'script' command
+ succeeds and we are not in TraceOn() mode (see the '-t' option).
+
+ In this case, the test method calls self.HideStdout(self) to redirect the
+ sys.stdout to a null device, and restores the sys.stdout upon teardown.
+
+ Note that you should only call this method at most once during a test case
+ execution. Any subsequent call has no effect at all."""
+ if self.sys_stdout_hidden:
+ return
+
+ self.sys_stdout_hidden = True
+ old_stdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
+
+ def restore_stdout():
+ sys.stdout = old_stdout
+ self.addTearDownHook(restore_stdout)
+
+ # =======================================================================
+ # Methods for customized teardown cleanups as well as execution of hooks.
+ # =======================================================================
+
+ def setTearDownCleanup(self, dictionary=None):
+ """Register a cleanup action at tearDown() time with a dictinary"""
+ self.dict = dictionary
+ self.doTearDownCleanup = True
+
+ def addTearDownCleanup(self, dictionary):
+ """Add a cleanup action at tearDown() time with a dictinary"""
+ self.dicts.append(dictionary)
+ self.doTearDownCleanups = True
+
+ def addTearDownHook(self, hook):
+ """
+ Add a function to be run during tearDown() time.
+
+ Hooks are executed in a first come first serve manner.
+ """
+ if six.callable(hook):
+ with recording(self, traceAlways) as sbuf:
+ print(
+ "Adding tearDown hook:",
+ getsource_if_available(hook),
+ file=sbuf)
+ self.hooks.append(hook)
+
+ return self
+
+ def deletePexpectChild(self):
+ # This is for the case of directly spawning 'lldb' and interacting with it
+ # using pexpect.
+ if self.child and self.child.isalive():
+ import pexpect
+ with recording(self, traceAlways) as sbuf:
+ print("tearing down the child process....", file=sbuf)
+ try:
+ if self.child_in_script_interpreter:
+ self.child.sendline('quit()')
+ self.child.expect_exact(self.child_prompt)
+ self.child.sendline(
+ 'settings set interpreter.prompt-on-quit false')
+ self.child.sendline('quit')
+ self.child.expect(pexpect.EOF)
+ except (ValueError, pexpect.ExceptionPexpect):
+ # child is already terminated
+ pass
+ except OSError as exception:
+ import errno
+ if exception.errno != errno.EIO:
+ # unexpected error
+ raise
+ # child is already terminated
+ finally:
+ # Give it one final blow to make sure the child is terminated.
+ self.child.close()
+
+ def tearDown(self):
+ """Fixture for unittest test case teardown."""
+ #import traceback
+ # traceback.print_stack()
+
+ self.deletePexpectChild()
+
+ # Check and run any hook functions.
+ for hook in reversed(self.hooks):
+ with recording(self, traceAlways) as sbuf:
+ print(
+ "Executing tearDown hook:",
+ getsource_if_available(hook),
+ file=sbuf)
+ if funcutils.requires_self(hook):
+ hook(self)
+ else:
+ hook() # try the plain call and hope it works
+
+ del self.hooks
+
+ # Perform registered teardown cleanup.
+ if doCleanup and self.doTearDownCleanup:
+ self.cleanup(dictionary=self.dict)
+
+ # In rare cases where there are multiple teardown cleanups added.
+ if doCleanup and self.doTearDownCleanups:
+ if self.dicts:
+ for dict in reversed(self.dicts):
+ self.cleanup(dictionary=dict)
+
+ # =========================================================
+ # Various callbacks to allow introspection of test progress
+ # =========================================================
+
+ def markError(self):
+ """Callback invoked when an error (unexpected exception) errored."""
+ self.__errored__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "ERROR" to the stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ print("ERROR", file=sbuf)
+
+ def markCleanupError(self):
+ """Callback invoked when an error occurs while a test is cleaning up."""
+ self.__cleanup_errored__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "CLEANUP_ERROR" to the stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ print("CLEANUP_ERROR", file=sbuf)
+
+ def markFailure(self):
+ """Callback invoked when a failure (test assertion failure) occurred."""
+ self.__failed__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "FAIL" to the stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ print("FAIL", file=sbuf)
+
+ def markExpectedFailure(self, err, bugnumber):
+ """Callback invoked when an expected failure/error occurred."""
+ self.__expected__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "expected failure" to the
+ # stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ if bugnumber is None:
+ print("expected failure", file=sbuf)
+ else:
+ print(
+ "expected failure (problem id:" + str(bugnumber) + ")",
+ file=sbuf)
+
+ def markSkippedTest(self):
+ """Callback invoked when a test is skipped."""
+ self.__skipped__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "skipped test" to the
+ # stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ print("skipped test", file=sbuf)
+
+ def markUnexpectedSuccess(self, bugnumber):
+ """Callback invoked when an unexpected success occurred."""
+ self.__unexpected__ = True
+ with recording(self, False) as sbuf:
+ # False because there's no need to write "unexpected success" to the
+ # stderr twice.
+ # Once by the Python unittest framework, and a second time by us.
+ if bugnumber is None:
+ print("unexpected success", file=sbuf)
+ else:
+ print(
+ "unexpected success (problem id:" + str(bugnumber) + ")",
+ file=sbuf)
+
+ def getRerunArgs(self):
+ return " -f %s.%s" % (self.__class__.__name__, self._testMethodName)
+
+ def getLogBasenameForCurrentTest(self, prefix=None):
+ """
+ returns a partial path that can be used as the beginning of the name of multiple
+ log files pertaining to this test
+
+ <session-dir>/<arch>-<compiler>-<test-file>.<test-class>.<test-method>
+ """
+ dname = os.path.join(os.environ["LLDB_TEST"],
+ os.environ["LLDB_SESSION_DIRNAME"])
+ if not os.path.isdir(dname):
+ os.mkdir(dname)
+
+ components = []
+ if prefix is not None:
+ components.append(prefix)
+ for c in configuration.session_file_format:
+ if c == 'f':
+ components.append(self.__class__.__module__)
+ elif c == 'n':
+ components.append(self.__class__.__name__)
+ elif c == 'c':
+ compiler = self.getCompiler()
+
+ if compiler[1] == ':':
+ compiler = compiler[2:]
+ if os.path.altsep is not None:
+ compiler = compiler.replace(os.path.altsep, os.path.sep)
+ path_components = [x for x in compiler.split(os.path.sep) if x != ""]
+
+ # Add at most 4 path components to avoid generating very long
+ # filenames
+ components.extend(path_components[-4:])
+ elif c == 'a':
+ components.append(self.getArchitecture())
+ elif c == 'm':
+ components.append(self.testMethodName)
+ fname = "-".join(components)
+
+ return os.path.join(dname, fname)
+
+ def dumpSessionInfo(self):
+ """
+ Dump the debugger interactions leading to a test error/failure. This
+ allows for more convenient postmortem analysis.
+
+ See also LLDBTestResult (dotest.py) which is a singlton class derived
+ from TextTestResult and overwrites addError, addFailure, and
+ addExpectedFailure methods to allow us to to mark the test instance as
+ such.
+ """
+
+ # We are here because self.tearDown() detected that this test instance
+ # either errored or failed. The lldb.test_result singleton contains
+ # two lists (erros and failures) which get populated by the unittest
+ # framework. Look over there for stack trace information.
+ #
+ # The lists contain 2-tuples of TestCase instances and strings holding
+ # formatted tracebacks.
+ #
+ # See http://docs.python.org/library/unittest.html#unittest.TestResult.
+
+ # output tracebacks into session
+ pairs = []
+ if self.__errored__:
+ pairs = configuration.test_result.errors
+ prefix = 'Error'
+ elif self.__cleanup_errored__:
+ pairs = configuration.test_result.cleanup_errors
+ prefix = 'CleanupError'
+ elif self.__failed__:
+ pairs = configuration.test_result.failures
+ prefix = 'Failure'
+ elif self.__expected__:
+ pairs = configuration.test_result.expectedFailures
+ prefix = 'ExpectedFailure'
+ elif self.__skipped__:
+ prefix = 'SkippedTest'
+ elif self.__unexpected__:
+ prefix = 'UnexpectedSuccess'
+ else:
+ prefix = 'Success'
+
+ if not self.__unexpected__ and not self.__skipped__:
+ for test, traceback in pairs:
+ if test is self:
+ print(traceback, file=self.session)
+
+ import datetime
+ print(
+ "Session info generated @",
+ datetime.datetime.now().ctime(),
+ file=self.session)
+ self.session.close()
+ del self.session
+
+ # process the log files
+ log_files_for_this_test = glob.glob(self.log_basename + "*")
+
+ if prefix != 'Success' or lldbtest_config.log_success:
+ # keep all log files, rename them to include prefix
+ dst_log_basename = self.getLogBasenameForCurrentTest(prefix)
+ for src in log_files_for_this_test:
+ if os.path.isfile(src):
+ dst = src.replace(self.log_basename, dst_log_basename)
+ if os.name == "nt" and os.path.isfile(dst):
+ # On Windows, renaming a -> b will throw an exception if
+ # b exists. On non-Windows platforms it silently
+ # replaces the destination. Ultimately this means that
+ # atomic renames are not guaranteed to be possible on
+ # Windows, but we need this to work anyway, so just
+ # remove the destination first if it already exists.
+ remove_file(dst)
+
+ lldbutil.mkdir_p(os.path.dirname(dst))
+ os.rename(src, dst)
+ else:
+ # success! (and we don't want log files) delete log files
+ for log_file in log_files_for_this_test:
+ remove_file(log_file)
+
+ # ====================================================
+ # Config. methods supported through a plugin interface
+ # (enables reading of the current test configuration)
+ # ====================================================
+
+ def isMIPS(self):
+ """Returns true if the architecture is MIPS."""
+ arch = self.getArchitecture()
+ if re.match("mips", arch):
+ return True
+ return False
+
+ def isPPC64le(self):
+ """Returns true if the architecture is PPC64LE."""
+ arch = self.getArchitecture()
+ if re.match("powerpc64le", arch):
+ return True
+ return False
+
+ def getArchitecture(self):
+ """Returns the architecture in effect the test suite is running with."""
+ module = builder_module()
+ arch = module.getArchitecture()
+ if arch == 'amd64':
+ arch = 'x86_64'
+ return arch
+
+ def getLldbArchitecture(self):
+ """Returns the architecture of the lldb binary."""
+ if not hasattr(self, 'lldbArchitecture'):
+
+ # spawn local process
+ command = [
+ lldbtest_config.lldbExec,
+ "-o",
+ "file " + lldbtest_config.lldbExec,
+ "-o",
+ "quit"
+ ]
+
+ output = check_output(command)
+ str = output.decode("utf-8")
+
+ for line in str.splitlines():
+ m = re.search(
+ "Current executable set to '.*' \\((.*)\\)\\.", line)
+ if m:
+ self.lldbArchitecture = m.group(1)
+ break
+
+ return self.lldbArchitecture
+
+ def getCompiler(self):
+ """Returns the compiler in effect the test suite is running with."""
+ module = builder_module()
+ return module.getCompiler()
+
+ def getCompilerBinary(self):
+ """Returns the compiler binary the test suite is running with."""
+ return self.getCompiler().split()[0]
+
+ def getCompilerVersion(self):
+ """ Returns a string that represents the compiler version.
+ Supports: llvm, clang.
+ """
+ version = 'unknown'
+
+ compiler = self.getCompilerBinary()
+ version_output = system([[compiler, "-v"]])[1]
+ for line in version_output.split(os.linesep):
+ m = re.search('version ([0-9\.]+)', line)
+ if m:
+ version = m.group(1)
+ return version
+
+ def getDwarfVersion(self):
+ """ Returns the dwarf version generated by clang or '0'. """
+ if configuration.dwarf_version:
+ return str(configuration.dwarf_version)
+ if 'clang' in self.getCompiler():
+ try:
+ driver_output = check_output(
+ [self.getCompiler()] + '-g -c -x c - -o - -###'.split(),
+ stderr=STDOUT)
+ driver_output = driver_output.decode("utf-8")
+ for line in driver_output.split(os.linesep):
+ m = re.search('dwarf-version=([0-9])', line)
+ if m:
+ return m.group(1)
+ except: pass
+ return '0'
+
+ def platformIsDarwin(self):
+ """Returns true if the OS triple for the selected platform is any valid apple OS"""
+ return lldbplatformutil.platformIsDarwin()
+
+ def hasDarwinFramework(self):
+ return self.darwinWithFramework
+
+ def getPlatform(self):
+ """Returns the target platform the test suite is running on."""
+ return lldbplatformutil.getPlatform()
+
+ def isIntelCompiler(self):
+ """ Returns true if using an Intel (ICC) compiler, false otherwise. """
+ return any([x in self.getCompiler() for x in ["icc", "icpc", "icl"]])
+
+ def expectedCompilerVersion(self, compiler_version):
+ """Returns True iff compiler_version[1] matches the current compiler version.
+ Use compiler_version[0] to specify the operator used to determine if a match has occurred.
+ Any operator other than the following defaults to an equality test:
+ '>', '>=', "=>", '<', '<=', '=<', '!=', "!" or 'not'
+ """
+ if (compiler_version is None):
+ return True
+ operator = str(compiler_version[0])
+ version = compiler_version[1]
+
+ if (version is None):
+ return True
+ if (operator == '>'):
+ return LooseVersion(self.getCompilerVersion()) > LooseVersion(version)
+ if (operator == '>=' or operator == '=>'):
+ return LooseVersion(self.getCompilerVersion()) >= LooseVersion(version)
+ if (operator == '<'):
+ return LooseVersion(self.getCompilerVersion()) < LooseVersion(version)
+ if (operator == '<=' or operator == '=<'):
+ return LooseVersion(self.getCompilerVersion()) <= LooseVersion(version)
+ if (operator == '!=' or operator == '!' or operator == 'not'):
+ return str(version) not in str(self.getCompilerVersion())
+ return str(version) in str(self.getCompilerVersion())
+
+ def expectedCompiler(self, compilers):
+ """Returns True iff any element of compilers is a sub-string of the current compiler."""
+ if (compilers is None):
+ return True
+
+ for compiler in compilers:
+ if compiler in self.getCompiler():
+ return True
+
+ return False
+
+ def expectedArch(self, archs):
+ """Returns True iff any element of archs is a sub-string of the current architecture."""
+ if (archs is None):
+ return True
+
+ for arch in archs:
+ if arch in self.getArchitecture():
+ return True
+
+ return False
+
+ def getRunOptions(self):
+ """Command line option for -A and -C to run this test again, called from
+ self.dumpSessionInfo()."""
+ arch = self.getArchitecture()
+ comp = self.getCompiler()
+ option_str = ""
+ if arch:
+ option_str = "-A " + arch
+ if comp:
+ option_str += " -C " + comp
+ return option_str
+
+ def getDebugInfo(self):
+ method = getattr(self, self.testMethodName)
+ return getattr(method, "debug_info", None)
+
+ # ==================================================
+ # Build methods supported through a plugin interface
+ # ==================================================
+
+ def getstdlibFlag(self):
+ """ Returns the proper -stdlib flag, or empty if not required."""
+ if self.platformIsDarwin() or self.getPlatform() == "freebsd" or self.getPlatform() == "openbsd":
+ stdlibflag = "-stdlib=libc++"
+ else: # this includes NetBSD
+ stdlibflag = ""
+ return stdlibflag
+
+ def getstdFlag(self):
+ """ Returns the proper stdflag. """
+ if "gcc" in self.getCompiler() and "4.6" in self.getCompilerVersion():
+ stdflag = "-std=c++0x"
+ else:
+ stdflag = "-std=c++11"
+ return stdflag
+
+ def buildDriver(self, sources, exe_name):
+ """ Platform-specific way to build a program that links with LLDB (via the liblldb.so
+ or LLDB.framework).
+ """
+ stdflag = self.getstdFlag()
+ stdlibflag = self.getstdlibFlag()
+
+ lib_dir = os.environ["LLDB_LIB_DIR"]
+ if self.hasDarwinFramework():
+ d = {'CXX_SOURCES': sources,
+ 'EXE': exe_name,
+ 'CFLAGS_EXTRAS': "%s %s" % (stdflag, stdlibflag),
+ 'FRAMEWORK_INCLUDES': "-F%s" % self.framework_dir,
+ 'LD_EXTRAS': "%s -Wl,-rpath,%s" % (self.dsym, self.framework_dir),
+ }
+ elif sys.platform.startswith('win'):
+ d = {
+ 'CXX_SOURCES': sources,
+ 'EXE': exe_name,
+ 'CFLAGS_EXTRAS': "%s %s -I%s" % (stdflag,
+ stdlibflag,
+ os.path.join(
+ os.environ["LLDB_SRC"],
+ "include")),
+ 'LD_EXTRAS': "-L%s -lliblldb" % os.environ["LLDB_IMPLIB_DIR"]}
+ else:
+ d = {
+ 'CXX_SOURCES': sources,
+ 'EXE': exe_name,
+ 'CFLAGS_EXTRAS': "%s %s -I%s" % (stdflag,
+ stdlibflag,
+ os.path.join(
+ os.environ["LLDB_SRC"],
+ "include")),
+ 'LD_EXTRAS': "-L%s/../lib -llldb -Wl,-rpath,%s/../lib" % (lib_dir, lib_dir)}
+ if self.TraceOn():
+ print(
+ "Building LLDB Driver (%s) from sources %s" %
+ (exe_name, sources))
+
+ self.buildDefault(dictionary=d)
+
+ def buildLibrary(self, sources, lib_name):
+ """Platform specific way to build a default library. """
+
+ stdflag = self.getstdFlag()
+
+ lib_dir = os.environ["LLDB_LIB_DIR"]
+ if self.hasDarwinFramework():
+ d = {'DYLIB_CXX_SOURCES': sources,
+ 'DYLIB_NAME': lib_name,
+ 'CFLAGS_EXTRAS': "%s -stdlib=libc++" % stdflag,
+ 'FRAMEWORK_INCLUDES': "-F%s" % self.framework_dir,
+ 'LD_EXTRAS': "%s -Wl,-rpath,%s -dynamiclib" % (self.dsym, self.framework_dir),
+ }
+ elif self.getPlatform() == 'windows':
+ d = {
+ 'DYLIB_CXX_SOURCES': sources,
+ 'DYLIB_NAME': lib_name,
+ 'CFLAGS_EXTRAS': "%s -I%s " % (stdflag,
+ os.path.join(
+ os.environ["LLDB_SRC"],
+ "include")),
+ 'LD_EXTRAS': "-shared -l%s\liblldb.lib" % self.os.environ["LLDB_IMPLIB_DIR"]}
+ else:
+ d = {
+ 'DYLIB_CXX_SOURCES': sources,
+ 'DYLIB_NAME': lib_name,
+ 'CFLAGS_EXTRAS': "%s -I%s -fPIC" % (stdflag,
+ os.path.join(
+ os.environ["LLDB_SRC"],
+ "include")),
+ 'LD_EXTRAS': "-shared -L%s/../lib -llldb -Wl,-rpath,%s/../lib" % (lib_dir, lib_dir)}
+ if self.TraceOn():
+ print(
+ "Building LLDB Library (%s) from sources %s" %
+ (lib_name, sources))
+
+ self.buildDefault(dictionary=d)
+
+ def buildProgram(self, sources, exe_name):
+ """ Platform specific way to build an executable from C/C++ sources. """
+ d = {'CXX_SOURCES': sources,
+ 'EXE': exe_name}
+ self.buildDefault(dictionary=d)
+
+ def buildDefault(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build the default binaries."""
+ testdir = self.mydir
+ testname = self.getBuildDirBasename()
+ if self.getDebugInfo():
+ raise Exception("buildDefault tests must set NO_DEBUG_INFO_TESTCASE")
+ module = builder_module()
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if not module.buildDefault(self, architecture, compiler,
+ dictionary, testdir, testname):
+ raise Exception("Don't know how to build default binary")
+
+ def buildDsym(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build binaries with dsym info."""
+ testdir = self.mydir
+ testname = self.getBuildDirBasename()
+ if self.getDebugInfo() != "dsym":
+ raise Exception("NO_DEBUG_INFO_TESTCASE must build with buildDefault")
+
+ module = builder_module()
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if not module.buildDsym(self, architecture, compiler,
+ dictionary, testdir, testname):
+ raise Exception("Don't know how to build binary with dsym")
+
+ def buildDwarf(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build binaries with dwarf maps."""
+ testdir = self.mydir
+ testname = self.getBuildDirBasename()
+ if self.getDebugInfo() != "dwarf":
+ raise Exception("NO_DEBUG_INFO_TESTCASE must build with buildDefault")
+
+ module = builder_module()
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if not module.buildDwarf(self, architecture, compiler,
+ dictionary, testdir, testname):
+ raise Exception("Don't know how to build binary with dwarf")
+
+ def buildDwo(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build binaries with dwarf maps."""
+ testdir = self.mydir
+ testname = self.getBuildDirBasename()
+ if self.getDebugInfo() != "dwo":
+ raise Exception("NO_DEBUG_INFO_TESTCASE must build with buildDefault")
+
+ module = builder_module()
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if not module.buildDwo(self, architecture, compiler,
+ dictionary, testdir, testname):
+ raise Exception("Don't know how to build binary with dwo")
+
+ def buildGModules(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build binaries with gmodules info."""
+ testdir = self.mydir
+ testname = self.getBuildDirBasename()
+ if self.getDebugInfo() != "gmodules":
+ raise Exception("NO_DEBUG_INFO_TESTCASE must build with buildDefault")
+
+ module = builder_module()
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if not module.buildGModules(self, architecture, compiler,
+ dictionary, testdir, testname):
+ raise Exception("Don't know how to build binary with gmodules")
+
+ def signBinary(self, binary_path):
+ if sys.platform.startswith("darwin"):
+ codesign_cmd = "codesign --force --sign \"%s\" %s" % (
+ lldbtest_config.codesign_identity, binary_path)
+ call(codesign_cmd, shell=True)
+
+ def findBuiltClang(self):
+ """Tries to find and use Clang from the build directory as the compiler (instead of the system compiler)."""
+ paths_to_try = [
+ "llvm-build/Release+Asserts/x86_64/bin/clang",
+ "llvm-build/Debug+Asserts/x86_64/bin/clang",
+ "llvm-build/Release/x86_64/bin/clang",
+ "llvm-build/Debug/x86_64/bin/clang",
+ ]
+ lldb_root_path = os.path.join(
+ os.path.dirname(__file__), "..", "..", "..", "..")
+ for p in paths_to_try:
+ path = os.path.join(lldb_root_path, p)
+ if os.path.exists(path):
+ return path
+
+ # Tries to find clang at the same folder as the lldb
+ lldb_dir = os.path.dirname(lldbtest_config.lldbExec)
+ path = distutils.spawn.find_executable("clang", lldb_dir)
+ if path is not None:
+ return path
+
+ return os.environ["CC"]
+
+ def findYaml2obj(self):
+ """
+ Get the path to the yaml2obj executable, which can be used to create
+ test object files from easy to write yaml instructions.
+
+ Throws an Exception if the executable cannot be found.
+ """
+ # Tries to find yaml2obj at the same folder as clang
+ clang_dir = os.path.dirname(self.findBuiltClang())
+ path = distutils.spawn.find_executable("yaml2obj", clang_dir)
+ if path is not None:
+ return path
+ raise Exception("yaml2obj executable not found")
+
+
+ def yaml2obj(self, yaml_path, obj_path):
+ """
+ Create an object file at the given path from a yaml file.
+
+ Throws subprocess.CalledProcessError if the object could not be created.
+ """
+ yaml2obj = self.findYaml2obj()
+ command = [yaml2obj, "-o=%s" % obj_path, yaml_path]
+ system([command])
+
+ def getBuildFlags(
+ self,
+ use_cpp11=True,
+ use_libcxx=False,
+ use_libstdcxx=False):
+ """ Returns a dictionary (which can be provided to build* functions above) which
+ contains OS-specific build flags.
+ """
+ cflags = ""
+ ldflags = ""
+
+ # On Mac OS X, unless specifically requested to use libstdc++, use
+ # libc++
+ if not use_libstdcxx and self.platformIsDarwin():
+ use_libcxx = True
+
+ if use_libcxx and self.libcxxPath:
+ cflags += "-stdlib=libc++ "
+ if self.libcxxPath:
+ libcxxInclude = os.path.join(self.libcxxPath, "include")
+ libcxxLib = os.path.join(self.libcxxPath, "lib")
+ if os.path.isdir(libcxxInclude) and os.path.isdir(libcxxLib):
+ cflags += "-nostdinc++ -I%s -L%s -Wl,-rpath,%s " % (
+ libcxxInclude, libcxxLib, libcxxLib)
+
+ if use_cpp11:
+ cflags += "-std="
+ if "gcc" in self.getCompiler() and "4.6" in self.getCompilerVersion():
+ cflags += "c++0x"
+ else:
+ cflags += "c++11"
+ if self.platformIsDarwin() or self.getPlatform() == "freebsd":
+ cflags += " -stdlib=libc++"
+ elif self.getPlatform() == "openbsd":
+ cflags += " -stdlib=libc++"
+ elif self.getPlatform() == "netbsd":
+ # NetBSD defaults to libc++
+ pass
+ elif "clang" in self.getCompiler():
+ cflags += " -stdlib=libstdc++"
+
+ return {'CFLAGS_EXTRAS': cflags,
+ 'LD_EXTRAS': ldflags,
+ }
+
+ def cleanup(self, dictionary=None):
+ """Platform specific way to do cleanup after build."""
+ module = builder_module()
+ if not module.cleanup(self, dictionary):
+ raise Exception(
+ "Don't know how to do cleanup with dictionary: " +
+ dictionary)
+
+ def getLLDBLibraryEnvVal(self):
+ """ Returns the path that the OS-specific library search environment variable
+ (self.dylibPath) should be set to in order for a program to find the LLDB
+ library. If an environment variable named self.dylibPath is already set,
+ the new path is appended to it and returned.
+ """
+ existing_library_path = os.environ[
+ self.dylibPath] if self.dylibPath in os.environ else None
+ lib_dir = os.environ["LLDB_LIB_DIR"]
+ if existing_library_path:
+ return "%s:%s" % (existing_library_path, lib_dir)
+ elif sys.platform.startswith("darwin"):
+ return os.path.join(lib_dir, 'LLDB.framework')
+ else:
+ return lib_dir
+
+ def getLibcPlusPlusLibs(self):
+ if self.getPlatform() in ('freebsd', 'linux', 'netbsd', 'openbsd'):
+ return ['libc++.so.1']
+ else:
+ return ['libc++.1.dylib', 'libc++abi.']
+
+# Metaclass for TestBase to change the list of test metods when a new TestCase is loaded.
+# We change the test methods to create a new test method for each test for each debug info we are
+# testing. The name of the new test method will be '<original-name>_<debug-info>' and with adding
+# the new test method we remove the old method at the same time. This functionality can be
+# supressed by at test case level setting the class attribute NO_DEBUG_INFO_TESTCASE or at test
+# level by using the decorator @no_debug_info_test.
+
+
+class LLDBTestCaseFactory(type):
+
+ def __new__(cls, name, bases, attrs):
+ original_testcase = super(
+ LLDBTestCaseFactory, cls).__new__(
+ cls, name, bases, attrs)
+ if original_testcase.NO_DEBUG_INFO_TESTCASE:
+ return original_testcase
+
+ newattrs = {}
+ for attrname, attrvalue in attrs.items():
+ if attrname.startswith("test") and not getattr(
+ attrvalue, "__no_debug_info_test__", False):
+
+ # If any debug info categories were explicitly tagged, assume that list to be
+ # authoritative. If none were specified, try with all debug
+ # info formats.
+ all_dbginfo_categories = set(test_categories.debug_info_categories)
+ categories = set(
+ getattr(
+ attrvalue,
+ "categories",
+ [])) & all_dbginfo_categories
+ if not categories:
+ categories = all_dbginfo_categories
+
+ for cat in categories:
+ @decorators.add_test_categories([cat])
+ @wraps(attrvalue)
+ def test_method(self, attrvalue=attrvalue):
+ return attrvalue(self)
+
+ method_name = attrname + "_" + cat
+ test_method.__name__ = method_name
+ test_method.debug_info = cat
+ newattrs[method_name] = test_method
+
+ else:
+ newattrs[attrname] = attrvalue
+ return super(
+ LLDBTestCaseFactory,
+ cls).__new__(
+ cls,
+ name,
+ bases,
+ newattrs)
+
+# Setup the metaclass for this class to change the list of the test
+# methods when a new class is loaded
+
+
+@add_metaclass(LLDBTestCaseFactory)
+class TestBase(Base):
+ """
+ This abstract base class is meant to be subclassed. It provides default
+ implementations for setUpClass(), tearDownClass(), setUp(), and tearDown(),
+ among other things.
+
+ Important things for test class writers:
+
+ - Overwrite the mydir class attribute, otherwise your test class won't
+ run. It specifies the relative directory to the top level 'test' so
+ the test harness can change to the correct working directory before
+ running your test.
+
+ - The setUp method sets up things to facilitate subsequent interactions
+ with the debugger as part of the test. These include:
+ - populate the test method name
+ - create/get a debugger set with synchronous mode (self.dbg)
+ - get the command interpreter from with the debugger (self.ci)
+ - create a result object for use with the command interpreter
+ (self.res)
+ - plus other stuffs
+
+ - The tearDown method tries to perform some necessary cleanup on behalf
+ of the test to return the debugger to a good state for the next test.
+ These include:
+ - execute any tearDown hooks registered by the test method with
+ TestBase.addTearDownHook(); examples can be found in
+ settings/TestSettings.py
+ - kill the inferior process associated with each target, if any,
+ and, then delete the target from the debugger's target list
+ - perform build cleanup before running the next test method in the
+ same test class; examples of registering for this service can be
+ found in types/TestIntegerTypes.py with the call:
+ - self.setTearDownCleanup(dictionary=d)
+
+ - Similarly setUpClass and tearDownClass perform classwise setup and
+ teardown fixtures. The tearDownClass method invokes a default build
+ cleanup for the entire test class; also, subclasses can implement the
+ classmethod classCleanup(cls) to perform special class cleanup action.
+
+ - The instance methods runCmd and expect are used heavily by existing
+ test cases to send a command to the command interpreter and to perform
+ string/pattern matching on the output of such command execution. The
+ expect method also provides a mode to peform string/pattern matching
+ without running a command.
+
+ - The build methods buildDefault, buildDsym, and buildDwarf are used to
+ build the binaries used during a particular test scenario. A plugin
+ should be provided for the sys.platform running the test suite. The
+ Mac OS X implementation is located in plugins/darwin.py.
+ """
+
+ # Subclasses can set this to true (if they don't depend on debug info) to avoid running the
+ # test multiple times with various debug info types.
+ NO_DEBUG_INFO_TESTCASE = False
+
+ # Maximum allowed attempts when launching the inferior process.
+ # Can be overridden by the LLDB_MAX_LAUNCH_COUNT environment variable.
+ maxLaunchCount = 1
+
+ # Time to wait before the next launching attempt in second(s).
+ # Can be overridden by the LLDB_TIME_WAIT_NEXT_LAUNCH environment variable.
+ timeWaitNextLaunch = 1.0
+
+ def generateSource(self, source):
+ template = source + '.template'
+ temp = os.path.join(self.getSourceDir(), template)
+ with open(temp, 'r') as f:
+ content = f.read()
+
+ public_api_dir = os.path.join(
+ os.environ["LLDB_SRC"], "include", "lldb", "API")
+
+ # Look under the include/lldb/API directory and add #include statements
+ # for all the SB API headers.
+ public_headers = os.listdir(public_api_dir)
+ # For different platforms, the include statement can vary.
+ if self.hasDarwinFramework():
+ include_stmt = "'#include <%s>' % os.path.join('LLDB', header)"
+ else:
+ include_stmt = "'#include <%s>' % os.path.join('" + public_api_dir + "', header)"
+ list = [eval(include_stmt) for header in public_headers if (
+ header.startswith("SB") and header.endswith(".h"))]
+ includes = '\n'.join(list)
+ new_content = content.replace('%include_SB_APIs%', includes)
+ src = os.path.join(self.getBuildDir(), source)
+ with open(src, 'w') as f:
+ f.write(new_content)
+
+ self.addTearDownHook(lambda: os.remove(src))
+
+ def setUp(self):
+ #import traceback
+ # traceback.print_stack()
+
+ # Works with the test driver to conditionally skip tests via
+ # decorators.
+ Base.setUp(self)
+
+ for s in self.setUpCommands():
+ self.runCmd(s)
+
+ if "LLDB_MAX_LAUNCH_COUNT" in os.environ:
+ self.maxLaunchCount = int(os.environ["LLDB_MAX_LAUNCH_COUNT"])
+
+ if "LLDB_TIME_WAIT_NEXT_LAUNCH" in os.environ:
+ self.timeWaitNextLaunch = float(
+ os.environ["LLDB_TIME_WAIT_NEXT_LAUNCH"])
+
+ # We want our debugger to be synchronous.
+ self.dbg.SetAsync(False)
+
+ # Retrieve the associated command interpreter instance.
+ self.ci = self.dbg.GetCommandInterpreter()
+ if not self.ci:
+ raise Exception('Could not get the command interpreter')
+
+ # And the result object.
+ self.res = lldb.SBCommandReturnObject()
+
+ def registerSharedLibrariesWithTarget(self, target, shlibs):
+ '''If we are remotely running the test suite, register the shared libraries with the target so they get uploaded, otherwise do nothing
+
+ Any modules in the target that have their remote install file specification set will
+ get uploaded to the remote host. This function registers the local copies of the
+ shared libraries with the target and sets their remote install locations so they will
+ be uploaded when the target is run.
+ '''
+ if not shlibs or not self.platformContext:
+ return None
+
+ shlib_environment_var = self.platformContext.shlib_environment_var
+ shlib_prefix = self.platformContext.shlib_prefix
+ shlib_extension = '.' + self.platformContext.shlib_extension
+
+ working_dir = self.get_process_working_directory()
+ environment = ['%s=%s' % (shlib_environment_var, working_dir)]
+ # Add any shared libraries to our target if remote so they get
+ # uploaded into the working directory on the remote side
+ for name in shlibs:
+ # The path can be a full path to a shared library, or a make file name like "Foo" for
+ # "libFoo.dylib" or "libFoo.so", or "Foo.so" for "Foo.so" or "libFoo.so", or just a
+ # basename like "libFoo.so". So figure out which one it is and resolve the local copy
+ # of the shared library accordingly
+ if os.path.isfile(name):
+ local_shlib_path = name # name is the full path to the local shared library
+ else:
+ # Check relative names
+ local_shlib_path = os.path.join(
+ self.getBuildDir(), shlib_prefix + name + shlib_extension)
+ if not os.path.exists(local_shlib_path):
+ local_shlib_path = os.path.join(
+ self.getBuildDir(), name + shlib_extension)
+ if not os.path.exists(local_shlib_path):
+ local_shlib_path = os.path.join(self.getBuildDir(), name)
+
+ # Make sure we found the local shared library in the above code
+ self.assertTrue(os.path.exists(local_shlib_path))
+
+ # Add the shared library to our target
+ shlib_module = target.AddModule(local_shlib_path, None, None, None)
+ if lldb.remote_platform:
+ # We must set the remote install location if we want the shared library
+ # to get uploaded to the remote target
+ remote_shlib_path = lldbutil.append_to_process_working_directory(self,
+ os.path.basename(local_shlib_path))
+ shlib_module.SetRemoteInstallFileSpec(
+ lldb.SBFileSpec(remote_shlib_path, False))
+
+ return environment
+
+ def registerSanitizerLibrariesWithTarget(self, target):
+ runtimes = []
+ for m in target.module_iter():
+ libspec = m.GetFileSpec()
+ if "clang_rt" in libspec.GetFilename():
+ runtimes.append(os.path.join(libspec.GetDirectory(),
+ libspec.GetFilename()))
+ return self.registerSharedLibrariesWithTarget(target, runtimes)
+
+ # utility methods that tests can use to access the current objects
+ def target(self):
+ if not self.dbg:
+ raise Exception('Invalid debugger instance')
+ return self.dbg.GetSelectedTarget()
+
+ def process(self):
+ if not self.dbg:
+ raise Exception('Invalid debugger instance')
+ return self.dbg.GetSelectedTarget().GetProcess()
+
+ def thread(self):
+ if not self.dbg:
+ raise Exception('Invalid debugger instance')
+ return self.dbg.GetSelectedTarget().GetProcess().GetSelectedThread()
+
+ def frame(self):
+ if not self.dbg:
+ raise Exception('Invalid debugger instance')
+ return self.dbg.GetSelectedTarget().GetProcess(
+ ).GetSelectedThread().GetSelectedFrame()
+
+ def get_process_working_directory(self):
+ '''Get the working directory that should be used when launching processes for local or remote processes.'''
+ if lldb.remote_platform:
+ # Remote tests set the platform working directory up in
+ # TestBase.setUp()
+ return lldb.remote_platform.GetWorkingDirectory()
+ else:
+ # local tests change directory into each test subdirectory
+ return self.getBuildDir()
+
+ def tearDown(self):
+ #import traceback
+ # traceback.print_stack()
+
+ # Ensure all the references to SB objects have gone away so that we can
+ # be sure that all test-specific resources have been freed before we
+ # attempt to delete the targets.
+ gc.collect()
+
+ # Delete the target(s) from the debugger as a general cleanup step.
+ # This includes terminating the process for each target, if any.
+ # We'd like to reuse the debugger for our next test without incurring
+ # the initialization overhead.
+ targets = []
+ for target in self.dbg:
+ if target:
+ targets.append(target)
+ process = target.GetProcess()
+ if process:
+ rc = self.invoke(process, "Kill")
+ self.assertTrue(rc.Success(), PROCESS_KILLED)
+ for target in targets:
+ self.dbg.DeleteTarget(target)
+
+ # Do this last, to make sure it's in reverse order from how we setup.
+ Base.tearDown(self)
+
+ # This must be the last statement, otherwise teardown hooks or other
+ # lines might depend on this still being active.
+ del self.dbg
+
+ def switch_to_thread_with_stop_reason(self, stop_reason):
+ """
+ Run the 'thread list' command, and select the thread with stop reason as
+ 'stop_reason'. If no such thread exists, no select action is done.
+ """
+ from .lldbutil import stop_reason_to_str
+ self.runCmd('thread list')
+ output = self.res.GetOutput()
+ thread_line_pattern = re.compile(
+ "^[ *] thread #([0-9]+):.*stop reason = %s" %
+ stop_reason_to_str(stop_reason))
+ for line in output.splitlines():
+ matched = thread_line_pattern.match(line)
+ if matched:
+ self.runCmd('thread select %s' % matched.group(1))
+
+ def runCmd(self, cmd, msg=None, check=True, trace=False, inHistory=False):
+ """
+ Ask the command interpreter to handle the command and then check its
+ return status.
+ """
+ # Fail fast if 'cmd' is not meaningful.
+ if not cmd or len(cmd) == 0:
+ raise Exception("Bad 'cmd' parameter encountered")
+
+ trace = (True if traceAlways else trace)
+
+ if cmd.startswith("target create "):
+ cmd = cmd.replace("target create ", "file ")
+
+ running = (cmd.startswith("run") or cmd.startswith("process launch"))
+
+ for i in range(self.maxLaunchCount if running else 1):
+ self.ci.HandleCommand(cmd, self.res, inHistory)
+
+ with recording(self, trace) as sbuf:
+ print("runCmd:", cmd, file=sbuf)
+ if not check:
+ print("check of return status not required", file=sbuf)
+ if self.res.Succeeded():
+ print("output:", self.res.GetOutput(), file=sbuf)
+ else:
+ print("runCmd failed!", file=sbuf)
+ print(self.res.GetError(), file=sbuf)
+
+ if self.res.Succeeded():
+ break
+ elif running:
+ # For process launch, wait some time before possible next try.
+ time.sleep(self.timeWaitNextLaunch)
+ with recording(self, trace) as sbuf:
+ print("Command '" + cmd + "' failed!", file=sbuf)
+
+ if check:
+ output = ""
+ if self.res.GetOutput():
+ output += "\nCommand output:\n" + self.res.GetOutput()
+ if self.res.GetError():
+ output += "\nError output:\n" + self.res.GetError()
+ if msg:
+ msg += output
+ if cmd:
+ cmd += output
+ self.assertTrue(self.res.Succeeded(),
+ msg if (msg) else CMD_MSG(cmd))
+
+ def match(
+ self,
+ str,
+ patterns,
+ msg=None,
+ trace=False,
+ error=False,
+ matching=True,
+ exe=True):
+ """run command in str, and match the result against regexp in patterns returning the match object for the first matching pattern
+
+ Otherwise, all the arguments have the same meanings as for the expect function"""
+
+ trace = (True if traceAlways else trace)
+
+ if exe:
+ # First run the command. If we are expecting error, set check=False.
+ # Pass the assert message along since it provides more semantic
+ # info.
+ self.runCmd(
+ str,
+ msg=msg,
+ trace=(
+ True if trace else False),
+ check=not error)
+
+ # Then compare the output against expected strings.
+ output = self.res.GetError() if error else self.res.GetOutput()
+
+ # If error is True, the API client expects the command to fail!
+ if error:
+ self.assertFalse(self.res.Succeeded(),
+ "Command '" + str + "' is expected to fail!")
+ else:
+ # No execution required, just compare str against the golden input.
+ output = str
+ with recording(self, trace) as sbuf:
+ print("looking at:", output, file=sbuf)
+
+ # The heading says either "Expecting" or "Not expecting".
+ heading = "Expecting" if matching else "Not expecting"
+
+ for pattern in patterns:
+ # Match Objects always have a boolean value of True.
+ match_object = re.search(pattern, output)
+ matched = bool(match_object)
+ with recording(self, trace) as sbuf:
+ print("%s pattern: %s" % (heading, pattern), file=sbuf)
+ print("Matched" if matched else "Not matched", file=sbuf)
+ if matched:
+ break
+
+ self.assertTrue(matched if matching else not matched,
+ msg if msg else EXP_MSG(str, output, exe))
+
+ return match_object
+
+ def check_completion_with_desc(self, str_input, match_desc_pairs):
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ description_strings = lldb.SBStringList()
+ num_matches = interp.HandleCompletionWithDescriptions(str_input, len(str_input), 0, -1, match_strings, description_strings)
+ self.assertEqual(len(description_strings), len(match_strings))
+
+ missing_pairs = []
+ for pair in match_desc_pairs:
+ found_pair = False
+ for i in range(num_matches + 1):
+ match_candidate = match_strings.GetStringAtIndex(i)
+ description_candidate = description_strings.GetStringAtIndex(i)
+ if match_candidate == pair[0] and description_candidate == pair[1]:
+ found_pair = True
+ break
+ if not found_pair:
+ missing_pairs.append(pair)
+
+ if len(missing_pairs):
+ error_msg = "Missing pairs:\n"
+ for pair in missing_pairs:
+ error_msg += " [" + pair[0] + ":" + pair[1] + "]\n"
+ error_msg += "Got the following " + str(num_matches) + " completions back:\n"
+ for i in range(num_matches + 1):
+ match_candidate = match_strings.GetStringAtIndex(i)
+ description_candidate = description_strings.GetStringAtIndex(i)
+ error_msg += "[" + match_candidate + ":" + description_candidate + "]\n"
+ self.assertEqual(0, len(missing_pairs), error_msg)
+
+ def complete_exactly(self, str_input, patterns):
+ self.complete_from_to(str_input, patterns, True)
+
+ def complete_from_to(self, str_input, patterns, turn_off_re_match=False):
+ """Test that the completion mechanism completes str_input to patterns,
+ where patterns could be a pattern-string or a list of pattern-strings"""
+ # Patterns should not be None in order to proceed.
+ self.assertFalse(patterns is None)
+ # And should be either a string or list of strings. Check for list type
+ # below, if not, make a list out of the singleton string. If patterns
+ # is not a string or not a list of strings, there'll be runtime errors
+ # later on.
+ if not isinstance(patterns, list):
+ patterns = [patterns]
+
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings)
+ common_match = match_strings.GetStringAtIndex(0)
+ if num_matches == 0:
+ compare_string = str_input
+ else:
+ if common_match != None and len(common_match) > 0:
+ compare_string = str_input + common_match
+ else:
+ compare_string = ""
+ for idx in range(1, num_matches+1):
+ compare_string += match_strings.GetStringAtIndex(idx) + "\n"
+
+ for p in patterns:
+ if turn_off_re_match:
+ self.expect(
+ compare_string, msg=COMPLETION_MSG(
+ str_input, p, match_strings), exe=False, substrs=[p])
+ else:
+ self.expect(
+ compare_string, msg=COMPLETION_MSG(
+ str_input, p, match_strings), exe=False, patterns=[p])
+
+ def completions_match(self, command, completions):
+ """Checks that the completions for the given command are equal to the
+ given list of completions"""
+ interp = self.dbg.GetCommandInterpreter()
+ match_strings = lldb.SBStringList()
+ interp.HandleCompletion(command, len(command), 0, -1, match_strings)
+ # match_strings is a 1-indexed list, so we have to slice...
+ self.assertItemsEqual(completions, list(match_strings)[1:],
+ "List of returned completion is wrong")
+
+ def filecheck(
+ self,
+ command,
+ check_file,
+ filecheck_options = '',
+ expect_cmd_failure = False):
+ # Run the command.
+ self.runCmd(
+ command,
+ check=(not expect_cmd_failure),
+ msg="FileCheck'ing result of `{0}`".format(command))
+
+ self.assertTrue((not expect_cmd_failure) == self.res.Succeeded())
+
+ # Get the error text if there was an error, and the regular text if not.
+ output = self.res.GetOutput() if self.res.Succeeded() \
+ else self.res.GetError()
+
+ # Assemble the absolute path to the check file. As a convenience for
+ # LLDB inline tests, assume that the check file is a relative path to
+ # a file within the inline test directory.
+ if check_file.endswith('.pyc'):
+ check_file = check_file[:-1]
+ check_file_abs = os.path.abspath(check_file)
+
+ # Run FileCheck.
+ filecheck_bin = configuration.get_filecheck_path()
+ if not filecheck_bin:
+ self.assertTrue(False, "No valid FileCheck executable specified")
+ filecheck_args = [filecheck_bin, check_file_abs]
+ if filecheck_options:
+ filecheck_args.append(filecheck_options)
+ subproc = Popen(filecheck_args, stdin=PIPE, stdout=PIPE, stderr=PIPE, universal_newlines = True)
+ cmd_stdout, cmd_stderr = subproc.communicate(input=output)
+ cmd_status = subproc.returncode
+
+ filecheck_cmd = " ".join(filecheck_args)
+ filecheck_trace = """
+--- FileCheck trace (code={0}) ---
+{1}
+
+FileCheck input:
+{2}
+
+FileCheck output:
+{3}
+{4}
+""".format(cmd_status, filecheck_cmd, output, cmd_stdout, cmd_stderr)
+
+ trace = cmd_status != 0 or traceAlways
+ with recording(self, trace) as sbuf:
+ print(filecheck_trace, file=sbuf)
+
+ self.assertTrue(cmd_status == 0)
+
+ def expect(
+ self,
+ str,
+ msg=None,
+ patterns=None,
+ startstr=None,
+ endstr=None,
+ substrs=None,
+ trace=False,
+ error=False,
+ matching=True,
+ exe=True,
+ inHistory=False):
+ """
+ Similar to runCmd; with additional expect style output matching ability.
+
+ Ask the command interpreter to handle the command and then check its
+ return status. The 'msg' parameter specifies an informational assert
+ message. We expect the output from running the command to start with
+ 'startstr', matches the substrings contained in 'substrs', and regexp
+ matches the patterns contained in 'patterns'.
+
+ If the keyword argument error is set to True, it signifies that the API
+ client is expecting the command to fail. In this case, the error stream
+ from running the command is retrieved and compared against the golden
+ input, instead.
+
+ If the keyword argument matching is set to False, it signifies that the API
+ client is expecting the output of the command not to match the golden
+ input.
+
+ Finally, the required argument 'str' represents the lldb command to be
+ sent to the command interpreter. In case the keyword argument 'exe' is
+ set to False, the 'str' is treated as a string to be matched/not-matched
+ against the golden input.
+ """
+ trace = (True if traceAlways else trace)
+
+ if exe:
+ # First run the command. If we are expecting error, set check=False.
+ # Pass the assert message along since it provides more semantic
+ # info.
+ self.runCmd(
+ str,
+ msg=msg,
+ trace=(
+ True if trace else False),
+ check=not error,
+ inHistory=inHistory)
+
+ # Then compare the output against expected strings.
+ output = self.res.GetError() if error else self.res.GetOutput()
+
+ # If error is True, the API client expects the command to fail!
+ if error:
+ self.assertFalse(self.res.Succeeded(),
+ "Command '" + str + "' is expected to fail!")
+ else:
+ # No execution required, just compare str against the golden input.
+ if isinstance(str, lldb.SBCommandReturnObject):
+ output = str.GetOutput()
+ else:
+ output = str
+ with recording(self, trace) as sbuf:
+ print("looking at:", output, file=sbuf)
+
+ # The heading says either "Expecting" or "Not expecting".
+ heading = "Expecting" if matching else "Not expecting"
+
+ # Start from the startstr, if specified.
+ # If there's no startstr, set the initial state appropriately.
+ matched = output.startswith(startstr) if startstr else (
+ True if matching else False)
+
+ if startstr:
+ with recording(self, trace) as sbuf:
+ print("%s start string: %s" % (heading, startstr), file=sbuf)
+ print("Matched" if matched else "Not matched", file=sbuf)
+
+ # Look for endstr, if specified.
+ keepgoing = matched if matching else not matched
+ if endstr:
+ matched = output.endswith(endstr)
+ with recording(self, trace) as sbuf:
+ print("%s end string: %s" % (heading, endstr), file=sbuf)
+ print("Matched" if matched else "Not matched", file=sbuf)
+
+ # Look for sub strings, if specified.
+ keepgoing = matched if matching else not matched
+ if substrs and keepgoing:
+ for substr in substrs:
+ matched = output.find(substr) != -1
+ with recording(self, trace) as sbuf:
+ print("%s sub string: %s" % (heading, substr), file=sbuf)
+ print("Matched" if matched else "Not matched", file=sbuf)
+ keepgoing = matched if matching else not matched
+ if not keepgoing:
+ break
+
+ # Search for regular expression patterns, if specified.
+ keepgoing = matched if matching else not matched
+ if patterns and keepgoing:
+ for pattern in patterns:
+ # Match Objects always have a boolean value of True.
+ matched = bool(re.search(pattern, output))
+ with recording(self, trace) as sbuf:
+ print("%s pattern: %s" % (heading, pattern), file=sbuf)
+ print("Matched" if matched else "Not matched", file=sbuf)
+ keepgoing = matched if matching else not matched
+ if not keepgoing:
+ break
+
+ self.assertTrue(matched if matching else not matched,
+ msg if msg else EXP_MSG(str, output, exe))
+
+ def expect_expr(
+ self,
+ expr,
+ result_summary=None,
+ result_value=None,
+ result_type=None,
+ error_msg=None,
+ ):
+ """
+ Evaluates the given expression and verifies the result.
+ :param expr: The expression as a string.
+ :param result_summary: The summary that the expression should have. None if the summary should not be checked.
+ :param result_value: The value that the expression should have. None if the value should not be checked.
+ :param result_type: The type that the expression result should have. None if the type should not be checked.
+ :param error_msg: The error message the expression should return. None if the error output should not be checked.
+ """
+ self.assertTrue(expr.strip() == expr, "Expression contains trailing/leading whitespace: '" + expr + "'")
+
+ frame = self.frame()
+ eval_result = frame.EvaluateExpression(expr)
+
+ if error_msg:
+ self.assertFalse(eval_result.IsValid())
+ self.assertEqual(error_msg, eval_result.GetError().GetCString())
+ return
+
+ if not eval_result.GetError().Success():
+ self.assertTrue(eval_result.GetError().Success(),
+ "Unexpected failure with msg: " + eval_result.GetError().GetCString())
+
+ if result_type:
+ self.assertEqual(result_type, eval_result.GetTypeName())
+
+ if result_value:
+ self.assertEqual(result_value, eval_result.GetValue())
+
+ if result_summary:
+ self.assertEqual(result_summary, eval_result.GetSummary())
+
+ def invoke(self, obj, name, trace=False):
+ """Use reflection to call a method dynamically with no argument."""
+ trace = (True if traceAlways else trace)
+
+ method = getattr(obj, name)
+ import inspect
+ self.assertTrue(inspect.ismethod(method),
+ name + "is a method name of object: " + str(obj))
+ result = method()
+ with recording(self, trace) as sbuf:
+ print(str(method) + ":", result, file=sbuf)
+ return result
+
+ def build(
+ self,
+ architecture=None,
+ compiler=None,
+ dictionary=None):
+ """Platform specific way to build the default binaries."""
+ module = builder_module()
+
+ dictionary = lldbplatformutil.finalize_build_dictionary(dictionary)
+ if self.getDebugInfo() is None:
+ return self.buildDefault(architecture, compiler, dictionary)
+ elif self.getDebugInfo() == "dsym":
+ return self.buildDsym(architecture, compiler, dictionary)
+ elif self.getDebugInfo() == "dwarf":
+ return self.buildDwarf(architecture, compiler, dictionary)
+ elif self.getDebugInfo() == "dwo":
+ return self.buildDwo(architecture, compiler, dictionary)
+ elif self.getDebugInfo() == "gmodules":
+ return self.buildGModules(architecture, compiler, dictionary)
+ else:
+ self.fail("Can't build for debug info: %s" % self.getDebugInfo())
+
+ def run_platform_command(self, cmd):
+ platform = self.dbg.GetSelectedPlatform()
+ shell_command = lldb.SBPlatformShellCommand(cmd)
+ err = platform.Run(shell_command)
+ return (err, shell_command.GetStatus(), shell_command.GetOutput())
+
+ # =================================================
+ # Misc. helper methods for debugging test execution
+ # =================================================
+
+ def DebugSBValue(self, val):
+ """Debug print a SBValue object, if traceAlways is True."""
+ from .lldbutil import value_type_to_str
+
+ if not traceAlways:
+ return
+
+ err = sys.stderr
+ err.write(val.GetName() + ":\n")
+ err.write('\t' + "TypeName -> " + val.GetTypeName() + '\n')
+ err.write('\t' + "ByteSize -> " +
+ str(val.GetByteSize()) + '\n')
+ err.write('\t' + "NumChildren -> " +
+ str(val.GetNumChildren()) + '\n')
+ err.write('\t' + "Value -> " + str(val.GetValue()) + '\n')
+ err.write('\t' + "ValueAsUnsigned -> " +
+ str(val.GetValueAsUnsigned()) + '\n')
+ err.write(
+ '\t' +
+ "ValueType -> " +
+ value_type_to_str(
+ val.GetValueType()) +
+ '\n')
+ err.write('\t' + "Summary -> " + str(val.GetSummary()) + '\n')
+ err.write('\t' + "IsPointerType -> " +
+ str(val.TypeIsPointerType()) + '\n')
+ err.write('\t' + "Location -> " + val.GetLocation() + '\n')
+
+ def DebugSBType(self, type):
+ """Debug print a SBType object, if traceAlways is True."""
+ if not traceAlways:
+ return
+
+ err = sys.stderr
+ err.write(type.GetName() + ":\n")
+ err.write('\t' + "ByteSize -> " +
+ str(type.GetByteSize()) + '\n')
+ err.write('\t' + "IsPointerType -> " +
+ str(type.IsPointerType()) + '\n')
+ err.write('\t' + "IsReferenceType -> " +
+ str(type.IsReferenceType()) + '\n')
+
+ def DebugPExpect(self, child):
+ """Debug the spwaned pexpect object."""
+ if not traceAlways:
+ return
+
+ print(child)
+
+ @classmethod
+ def RemoveTempFile(cls, file):
+ if os.path.exists(file):
+ remove_file(file)
+
+# On Windows, the first attempt to delete a recently-touched file can fail
+# because of a race with antimalware scanners. This function will detect a
+# failure and retry.
+
+
+def remove_file(file, num_retries=1, sleep_duration=0.5):
+ for i in range(num_retries + 1):
+ try:
+ os.remove(file)
+ return True
+ except:
+ time.sleep(sleep_duration)
+ continue
+ return False