| # -*- Python -*- | |
| # Configuration file for the 'lit' test runner. | |
| import os | |
| import platform | |
| import shlex | |
| import shutil | |
| import subprocess | |
| import sys | |
| import lit.formats | |
| # name: The name of this test suite. | |
| config.name = "lldb-api" | |
| # suffixes: A list of file extensions to treat as test files. | |
| config.suffixes = [".py"] | |
| # test_source_root: The root path where tests are located. | |
| config.test_source_root = os.path.dirname(__file__) | |
| # test_exec_root: The root path where tests should be run. | |
| config.test_exec_root = os.path.join(config.lldb_obj_root, "test", "API") | |
| def mkdir_p(path): | |
| import errno | |
| try: | |
| os.makedirs(path) | |
| except OSError as e: | |
| if e.errno != errno.EEXIST: | |
| raise | |
| if not os.path.isdir(path): | |
| raise OSError(errno.ENOTDIR, "%s is not a directory" % path) | |
| def find_sanitizer_runtime(name): | |
| resource_dir = ( | |
| subprocess.check_output([config.cmake_cxx_compiler, "-print-resource-dir"]) | |
| .decode("utf-8") | |
| .strip() | |
| ) | |
| return os.path.join(resource_dir, "lib", "darwin", name) | |
| def find_shlibpath_var(): | |
| if platform.system() in ["Linux", "FreeBSD", "NetBSD", "OpenBSD", "SunOS"]: | |
| yield "LD_LIBRARY_PATH" | |
| elif platform.system() == "Darwin": | |
| yield "DYLD_LIBRARY_PATH" | |
| elif platform.system() == "Windows": | |
| yield "PATH" | |
| # On macOS, we can't do the DYLD_INSERT_LIBRARIES trick with a shim python | |
| # binary as the ASan interceptors get loaded too late. Also, when SIP is | |
| # enabled, we can't inject libraries into system binaries at all, so we need a | |
| # copy of the "real" python to work with. | |
| def find_python_interpreter(): | |
| # This is only necessary when using DYLD_INSERT_LIBRARIES. | |
| if "DYLD_INSERT_LIBRARIES" not in config.environment: | |
| return None | |
| # If we're running in a virtual environment, we have to copy Python into | |
| # the virtual environment for it to work. | |
| if sys.prefix != sys.base_prefix: | |
| copied_python = os.path.join(sys.prefix, "bin", "copied-python") | |
| else: | |
| copied_python = os.path.join(config.lldb_build_directory, "copied-python") | |
| # Avoid doing any work if we already copied the binary. | |
| if os.path.isfile(copied_python): | |
| return copied_python | |
| # Find the "real" python binary. | |
| real_python = ( | |
| subprocess.check_output( | |
| [ | |
| config.python_executable, | |
| os.path.join( | |
| os.path.dirname(os.path.realpath(__file__)), | |
| "get_darwin_real_python.py", | |
| ), | |
| ] | |
| ) | |
| .decode("utf-8") | |
| .strip() | |
| ) | |
| shutil.copy(real_python, copied_python) | |
| # macOS 15+ restricts injecting the ASAN runtime to only user-compiled code. | |
| subprocess.check_call(["/usr/bin/codesign", "--remove-signature", copied_python]) | |
| # Now make sure the copied Python works. The Python in Xcode has a relative | |
| # RPATH and cannot be copied. | |
| try: | |
| # We don't care about the output, just make sure it runs. | |
| subprocess.check_call([copied_python, "-V"]) | |
| except subprocess.CalledProcessError: | |
| # The copied Python didn't work. Assume we're dealing with the Python | |
| # interpreter in Xcode. Given that this is not a system binary SIP | |
| # won't prevent us form injecting the interceptors, but when running in | |
| # a virtual environment, we can't use it directly. Create a symlink | |
| # instead. | |
| os.remove(copied_python) | |
| os.symlink(real_python, copied_python) | |
| # The copied Python works. | |
| return copied_python | |
| def is_configured(attr): | |
| """Return the configuration attribute if it exists and None otherwise. | |
| This allows us to check if the attribute exists before trying to access it.""" | |
| return getattr(config, attr, None) | |
| def delete_module_cache(path): | |
| """Clean the module caches in the test build directory. | |
| This is necessary in an incremental build whenever clang changes underneath, | |
| so doing it once per lit.py invocation is close enough.""" | |
| if os.path.isdir(path): | |
| lit_config.note("Deleting module cache at %s." % path) | |
| shutil.rmtree(path) | |
| if is_configured("llvm_use_sanitizer"): | |
| config.environment["MallocNanoZone"] = "0" | |
| if "Address" in config.llvm_use_sanitizer: | |
| config.environment["ASAN_OPTIONS"] = "detect_stack_use_after_return=1" | |
| if "Darwin" in config.target_os: | |
| config.environment["DYLD_INSERT_LIBRARIES"] = find_sanitizer_runtime( | |
| "libclang_rt.asan_osx_dynamic.dylib" | |
| ) | |
| if "Thread" in config.llvm_use_sanitizer: | |
| tsan_suppressions = os.path.join( | |
| os.path.dirname(config.test_source_root), "tsan-suppressions.txt" | |
| ) | |
| config.environment["TSAN_OPTIONS"] = ( | |
| "halt_on_error=1:suppressions=" + tsan_suppressions | |
| ) | |
| if "Darwin" in config.target_os: | |
| config.environment["DYLD_INSERT_LIBRARIES"] = find_sanitizer_runtime( | |
| "libclang_rt.tsan_osx_dynamic.dylib" | |
| ) | |
| if platform.system() == "Darwin": | |
| python_executable = find_python_interpreter() | |
| if python_executable: | |
| lit_config.note( | |
| "Using {} instead of {}".format(python_executable, config.python_executable) | |
| ) | |
| config.python_executable = python_executable | |
| # Shared library build of LLVM may require LD_LIBRARY_PATH or equivalent. | |
| if is_configured("shared_libs"): | |
| for shlibpath_var in find_shlibpath_var(): | |
| # In stand-alone build llvm_shlib_dir specifies LLDB's lib directory while | |
| # llvm_libs_dir specifies LLVM's lib directory. | |
| shlibpath = os.path.pathsep.join( | |
| ( | |
| config.llvm_shlib_dir, | |
| config.llvm_libs_dir, | |
| config.environment.get(shlibpath_var, ""), | |
| ) | |
| ) | |
| config.environment[shlibpath_var] = shlibpath | |
| else: | |
| lit_config.warning( | |
| "unable to inject shared library path on '{}'".format(platform.system()) | |
| ) | |
| # Windows has no rpath: drivers built by API tests link against | |
| # liblldb.dll and need its directory on PATH at launch. | |
| if platform.system() == "Windows": | |
| config.environment["PATH"] = os.path.pathsep.join( | |
| (config.llvm_shlib_dir, config.environment.get("PATH", "")) | |
| ) | |
| lldb_use_simulator = lit_config.params.get("lldb-run-with-simulator", None) | |
| if lldb_use_simulator: | |
| if lldb_use_simulator == "ios": | |
| lit_config.note("Running API tests on iOS simulator") | |
| config.available_features.add("lldb-simulator-ios") | |
| elif lldb_use_simulator == "watchos": | |
| lit_config.note("Running API tests on watchOS simulator") | |
| config.available_features.add("lldb-simulator-watchos") | |
| elif lldb_use_simulator == "tvos": | |
| lit_config.note("Running API tests on tvOS simulator") | |
| config.available_features.add("lldb-simulator-tvos") | |
| elif lldb_use_simulator == "qemu-user": | |
| lit_config.note("Running API tests on qemu-user simulator") | |
| config.available_features.add("lldb-simulator-qemu-user") | |
| else: | |
| lit_config.error("Unknown simulator id '{}'".format(lldb_use_simulator)) | |
| # Simulator tests can interfer with each other when they access the same device | |
| # kind, so prevent them from running at the same time. | |
| lit_config.parallelism_groups["apple-simulator"] = 1 | |
| # Set a default per-test timeout of 10 minutes. Setting a timeout per test | |
| # requires that killProcessAndChildren() is supported on the platform and | |
| # lit complains if the value is set but it is not supported. | |
| supported, errormsg = lit_config.maxIndividualTestTimeIsSupported | |
| if supported: | |
| config.maxIndividualTestTime = 600 | |
| else: | |
| lit_config.warning("Could not set a default per-test timeout. " + errormsg) | |
| # Build dotest command. | |
| dotest_cmd = [os.path.join(config.lldb_src_root, "test", "API", "dotest.py")] | |
| if is_configured("dotest_common_args_str"): | |
| dotest_cmd.extend(config.dotest_common_args_str.split(";")) | |
| # Library path may be needed to locate just-built clang and libcxx. | |
| if is_configured("llvm_libs_dir"): | |
| dotest_cmd += ["--env", "LLVM_LIBS_DIR=" + config.llvm_libs_dir] | |
| # Include path may be needed to locate just-built libcxx. | |
| if is_configured("llvm_include_dir"): | |
| dotest_cmd += ["--env", "LLVM_INCLUDE_DIR=" + config.llvm_include_dir] | |
| # This path may be needed to locate required llvm tools | |
| if is_configured("llvm_tools_dir"): | |
| dotest_cmd += ["--env", "LLVM_TOOLS_DIR=" + config.llvm_tools_dir] | |
| # Prevent tests from accidentally invoking the real dsymForUUID, which can | |
| # make slow network requests. Tests that need a working dsymForUUID mock | |
| # should override this with their own script. | |
| if platform.system() == "Darwin": | |
| dotest_cmd += [ | |
| "--env", | |
| "LLDB_APPLE_DSYMFORUUID_EXECUTABLE=" | |
| + os.path.join(config.lldb_src_root, "test", "Utils", "fake-dsymForUUID.sh"), | |
| ] | |
| # If we have a just-built libcxx, prefer it over the system one. | |
| if is_configured("has_libcxx") and config.has_libcxx: | |
| if platform.system() != "Windows": | |
| if is_configured("libcxx_include_dir") and is_configured("libcxx_libs_dir"): | |
| dotest_cmd += ["--libcxx-include-dir", config.libcxx_include_dir] | |
| if is_configured("libcxx_include_target_dir"): | |
| dotest_cmd += [ | |
| "--libcxx-include-target-dir", | |
| config.libcxx_include_target_dir, | |
| ] | |
| dotest_cmd += ["--libcxx-library-dir", config.libcxx_libs_dir] | |
| # Forward ASan-specific environment variables to tests, as a test may load an | |
| # ASan-ified dylib. | |
| for env_var in ("ASAN_OPTIONS", "DYLD_INSERT_LIBRARIES"): | |
| if env_var in config.environment: | |
| dotest_cmd += ["--inferior-env", env_var + "=" + config.environment[env_var]] | |
| if is_configured("test_triple"): | |
| dotest_cmd += ["--triple", config.test_triple] | |
| if is_configured("lldb_build_directory"): | |
| dotest_cmd += ["--build-dir", config.lldb_build_directory] | |
| if is_configured("lldb_module_cache"): | |
| delete_module_cache(config.lldb_module_cache) | |
| dotest_cmd += ["--lldb-module-cache-dir", config.lldb_module_cache] | |
| if is_configured("clang_module_cache"): | |
| dotest_cmd += ["--clang-module-cache-dir", config.clang_module_cache] | |
| if is_configured("lldb_executable"): | |
| dotest_cmd += ["--executable", config.lldb_executable] | |
| try: | |
| version_output = subprocess.check_output( | |
| [config.lldb_executable, "--version"], | |
| stderr=subprocess.STDOUT, | |
| text=True, | |
| ).strip() | |
| for line in version_output.splitlines(): | |
| lit_config.note(line.strip()) | |
| except (subprocess.CalledProcessError, OSError) as e: | |
| lit_config.warning( | |
| "Could not get lldb version from {}: {}".format(config.lldb_executable, e) | |
| ) | |
| # Discover the directory that contains the 'lldb' Python module once here, | |
| # so each dotest invocation doesn't have to spawn '<lldb> -P' itself. | |
| try: | |
| lldb_dash_p_output = subprocess.check_output( | |
| [config.lldb_executable, "-P"], | |
| stderr=subprocess.STDOUT, | |
| text=True, | |
| ) | |
| for line in lldb_dash_p_output.splitlines(): | |
| line = line.strip() | |
| if os.path.isdir(line) and os.path.exists( | |
| os.path.join(line, "lldb", "__init__.py") | |
| ): | |
| dotest_cmd += ["--lldb-python-dir", line] | |
| break | |
| except (subprocess.CalledProcessError, OSError) as e: | |
| lit_config.warning( | |
| "Could not discover lldb python path from {}: {}".format( | |
| config.lldb_executable, e | |
| ) | |
| ) | |
| if is_configured("test_compiler"): | |
| dotest_cmd += ["--compiler", config.test_compiler] | |
| if is_configured("dsymutil"): | |
| dotest_cmd += ["--dsymutil", config.dsymutil] | |
| if is_configured("make"): | |
| dotest_cmd += ["--make", config.make] | |
| if is_configured("llvm_tools_dir"): | |
| dotest_cmd += ["--llvm-tools-dir", config.llvm_tools_dir] | |
| if is_configured("server"): | |
| dotest_cmd += ["--server", config.server] | |
| if is_configured("lldb_obj_root"): | |
| dotest_cmd += ["--lldb-obj-root", config.lldb_obj_root] | |
| if is_configured("lldb_libs_dir"): | |
| dotest_cmd += ["--lldb-libs-dir", config.lldb_libs_dir] | |
| if is_configured("lldb_framework_dir"): | |
| dotest_cmd += ["--framework", config.lldb_framework_dir] | |
| if is_configured("cmake_build_type"): | |
| dotest_cmd += ["--cmake-build-type", config.cmake_build_type] | |
| if "lldb-simulator-ios" in config.available_features: | |
| dotest_cmd += ["--apple-sdk", "iphonesimulator", "--platform-name", "ios-simulator"] | |
| elif "lldb-simulator-watchos" in config.available_features: | |
| dotest_cmd += [ | |
| "--apple-sdk", | |
| "watchsimulator", | |
| "--platform-name", | |
| "watchos-simulator", | |
| ] | |
| elif "lldb-simulator-tvos" in config.available_features: | |
| dotest_cmd += [ | |
| "--apple-sdk", | |
| "appletvsimulator", | |
| "--platform-name", | |
| "tvos-simulator", | |
| ] | |
| if "lldb-simulator-qemu-user" in config.available_features: | |
| dotest_cmd += ["--platform-name", "qemu-user"] | |
| if is_configured("enabled_plugins"): | |
| for plugin in config.enabled_plugins: | |
| dotest_cmd += ["--enable-plugin", plugin] | |
| if getattr(config, "lldb_enable_mte", False): | |
| dotest_cmd += ["--enable-mte"] | |
| if getattr(config, "lldb_enable_arm64e_debugserver", False): | |
| dotest_cmd += ["--arm64e-debugserver"] | |
| # `dotest` args come from three different sources: | |
| # 1. Derived by CMake based on its configs (LLDB_TEST_COMMON_ARGS), which end | |
| # up in `dotest_common_args_str`. | |
| # 2. CMake user parameters (LLDB_TEST_USER_ARGS), which end up in | |
| # `dotest_user_args_str`. | |
| # 3. With `llvm-lit "--param=dotest-args=..."`, which end up in | |
| # `dotest_lit_args_str`. | |
| # Check them in this order, so that more specific overrides are visited last. | |
| # In particular, (1) is visited at the top of the file, since the script | |
| # derives other information from it. | |
| if is_configured("lldb_platform_url"): | |
| dotest_cmd += ["--platform-url", config.lldb_platform_url] | |
| if is_configured("lldb_platform_working_dir"): | |
| dotest_cmd += ["--platform-working-dir", config.lldb_platform_working_dir] | |
| if is_configured("cmake_sysroot"): | |
| dotest_cmd += ["--sysroot", config.cmake_sysroot] | |
| if is_configured("test_resource_dir"): | |
| dotest_cmd += ["--resource-dir", config.test_resource_dir] | |
| if is_configured("dotest_user_args_str"): | |
| dotest_cmd.extend(config.dotest_user_args_str.split(";")) | |
| if is_configured("dotest_lit_args_str"): | |
| # We don't want to force users passing arguments to lit to use `;` as a | |
| # separator. We use Python's simple lexical analyzer to turn the args into a | |
| # list. Pass there arguments last so they can override anything that was | |
| # already configured. | |
| dotest_cmd.extend(shlex.split(config.dotest_lit_args_str)) | |
| # Load LLDB test format. | |
| sys.path.append(os.path.join(config.lldb_src_root, "test")) | |
| sys.path.append(os.path.join(config.lldb_src_root, "test", "API")) | |
| import lldbtest | |
| # testFormat: The test format to use to interpret tests. | |
| config.test_format = lldbtest.LLDBTest(dotest_cmd) | |
| # Propagate TERM or default to vt100. | |
| config.environment["TERM"] = os.getenv("TERM", default="vt100") | |
| # Propagate FREEBSD_LEGACY_PLUGIN | |
| if "FREEBSD_LEGACY_PLUGIN" in os.environ: | |
| config.environment["FREEBSD_LEGACY_PLUGIN"] = os.environ["FREEBSD_LEGACY_PLUGIN"] | |
| # Propagate XDG_CACHE_HOME | |
| if "XDG_CACHE_HOME" in os.environ: | |
| config.environment["XDG_CACHE_HOME"] = os.environ["XDG_CACHE_HOME"] | |
| # Transfer some environment variables into the tests on Windows build host. | |
| if platform.system() == "Windows": | |
| for v in ["SystemDrive"]: | |
| if v in os.environ: | |
| config.environment[v] = os.environ[v] | |
| config.environment["LLDB_USE_LLDB_SERVER"] = ( | |
| "1" if getattr(config, "lldb_use_lldb_server", False) else "0" | |
| ) | |
| # Use anonymous pipes instead of ConPTY for all tests. ConPTY injects VT | |
| # escape sequences into the output stream, which breaks tests that check | |
| # for specific stdout/stderr content. | |
| dotest_cmd += ["--env", "LLDB_LAUNCH_FLAG_USE_PIPES=1"] | |
| # Some steps required to initialize the tests dynamically link with python.dll | |
| # and need to know the location of the Python libraries. This ensures that we | |
| # use the same version of Python that was used to build lldb to run our tests. | |
| config.environment["PYTHONHOME"] = config.python_root_dir | |
| config.environment["PATH"] = os.path.pathsep.join( | |
| (config.python_root_dir, config.environment.get("PATH", "")) | |
| ) |