| # System modules | |
| # allow the use of the `list[str]` type hint in Python 3.8 | |
| from __future__ import annotations | |
| from collections.abc import Callable | |
| from functools import lru_cache, wraps | |
| from typing import Optional | |
| from packaging import version | |
| import contextlib | |
| import ctypes | |
| import locale | |
| import os | |
| import platform | |
| import re | |
| import socket | |
| import sys | |
| import tempfile | |
| import subprocess | |
| import json | |
| # Third-party modules | |
| import unittest | |
| # LLDB modules | |
| import lldb | |
| from . import configuration | |
| from . import test_categories | |
| from . import lldbtest_config | |
| from lldbsuite.support import funcutils | |
| from lldbsuite.support import temp_file | |
| from lldbsuite.test import lldbplatform | |
| from lldbsuite.test import lldbplatformutil | |
| from lldbsuite.test.cpu_feature import CPUFeature | |
| from lldbsuite.test.skip_reason import UnsupportedReason | |
| class DecorateMode: | |
| Skip, Xfail = range(2) | |
| # You can use no_match to reverse the test of the conditional that is used to match keyword | |
| # arguments in the skip / xfail decorators. If oslist=["windows", "linux"] skips windows | |
| # and linux, oslist=no_match(["windows", "linux"]) skips *unless* windows | |
| # or linux. | |
| class no_match: | |
| def __init__(self, item): | |
| self.item = item | |
| def _check_expected_version(comparison, expected, actual): | |
| def fn_leq(x, y): | |
| return x <= y | |
| def fn_less(x, y): | |
| return x < y | |
| def fn_geq(x, y): | |
| return x >= y | |
| def fn_greater(x, y): | |
| return x > y | |
| def fn_eq(x, y): | |
| return x == y | |
| def fn_neq(x, y): | |
| return x != y | |
| op_lookup = { | |
| "==": fn_eq, | |
| "=": fn_eq, | |
| "!=": fn_neq, | |
| "<>": fn_neq, | |
| ">": fn_greater, | |
| "<": fn_less, | |
| ">=": fn_geq, | |
| "<=": fn_leq, | |
| } | |
| return op_lookup[comparison](version.parse(actual), version.parse(expected)) | |
| def _get_macos_sdk_version() -> str | None: | |
| """ Returns the current macOS SDK version or None if this system doesn't | |
| use a macOS SDK.""" | |
| if platform.mac_ver()[0] == "": | |
| return None | |
| try: | |
| return ( | |
| subprocess.check_output(["xcrun", "--sdk", "macosx", "--show-sdk-version"]) | |
| .rstrip() | |
| .decode("utf-8") | |
| ) | |
| except Exception: | |
| return None | |
| def _match_decorator_property(expected, actual): | |
| if expected is None: | |
| return True | |
| if actual is None: | |
| return False | |
| if isinstance(expected, no_match): | |
| return not _match_decorator_property(expected.item, actual) | |
| if isinstance(expected, (re.Pattern, str)): | |
| return re.search(expected, actual) is not None | |
| if hasattr(expected, "__iter__"): | |
| return any( | |
| [x is not None and _match_decorator_property(x, actual) for x in expected] | |
| ) | |
| return expected == actual | |
| def _compiler_supports(compiler, flag, source="int main() {}", output_file=None): | |
| """Test whether the compiler supports the given flag.""" | |
| if output_file: | |
| context = contextlib.nullcontext(output_file) | |
| else: | |
| context = temp_file.OnDiskTempFile() | |
| with context as ctx: | |
| if platform.system() == "Darwin": | |
| compiler = "xcrun " + compiler | |
| try: | |
| cmd = "echo '%s' | %s %s -x c -o %s -" % ( | |
| source, | |
| compiler, | |
| flag, | |
| ctx.path, | |
| ) | |
| subprocess.check_call(cmd, shell=True) | |
| except subprocess.CalledProcessError: | |
| return False | |
| return True | |
| def expectedFailureIf(condition, bugnumber=None): | |
| def expectedFailure_impl(func): | |
| if isinstance(func, type) and issubclass(func, unittest.TestCase): | |
| raise Exception("Decorator can only be used to decorate a test method") | |
| if condition: | |
| return unittest.expectedFailure(func) | |
| return func | |
| if callable(bugnumber): | |
| return expectedFailure_impl(bugnumber) | |
| else: | |
| return expectedFailure_impl | |
| def requiresSwiftPlugin(min_apple_os_version=None): | |
| """Mark a test that can only pass when LLDB has the Swift language plugin. | |
| More and more of Foundation is implemented in Swift, so its values are | |
| increasingly backed by native Swift types that only the Swift language | |
| plugin knows how to format. A test exercising such a value passes when LLDB | |
| is built with Swift support and is expected to fail otherwise. | |
| Starting with macOS/iOS/... 26.0 all Apple OS share a common | |
| version number, so ``min_apple_os_version`` describes the release | |
| in which a value moved to Swift. Older OS versions, non-Apple | |
| targets, and any build that has the Swift plugin are expected to | |
| pass. When ``min_apple_os_version`` is omitted the plugin is | |
| required on every Apple OS. | |
| """ | |
| def decorate(func): | |
| # An LLDB built with the Swift plugin satisfies the requirement. | |
| if _get_bool_config("swift", fail_value=False): | |
| return func | |
| # Otherwise the test is expected to fail, but only on Apple targets | |
| # that are new enough to have moved the value to Swift. | |
| apple_oslist = lldbplatformutil.getDarwinOSTriples() + ["xros", "xrsimulator"] | |
| expected = _match_decorator_property( | |
| apple_oslist, lldbplatformutil.getPlatform() | |
| ) | |
| if expected and min_apple_os_version is not None: | |
| target = lldb.selected_platform | |
| actual = "%d.%d" % ( | |
| target.GetOSMajorVersion(), | |
| target.GetOSMinorVersion(), | |
| ) | |
| expected = _check_expected_version(">=", str(min_apple_os_version), actual) | |
| return expectedFailureIf(expected)(func) | |
| # Support bare usage (@requiresSwiftPlugin) in addition to the parenthesized | |
| # form (@requiresSwiftPlugin(min_apple_os_version=...)). | |
| if callable(min_apple_os_version): | |
| func, min_apple_os_version = min_apple_os_version, None | |
| return decorate(func) | |
| return decorate | |
| def skipTestIfFn(expected_fn, bugnumber=None): | |
| def skipTestIfFn_impl(func): | |
| if isinstance(func, type) and issubclass(func, unittest.TestCase): | |
| reason = expected_fn() | |
| # The return value is the reason (or None if we don't skip), so | |
| # reason is used for both args. | |
| return unittest.skipIf(condition=reason, reason=reason)(func) | |
| @wraps(func) | |
| def wrapper(*args, **kwargs): | |
| self = args[0] | |
| if funcutils.requires_self(expected_fn): | |
| reason = expected_fn(self) | |
| else: | |
| reason = expected_fn() | |
| if reason is not None: | |
| self.skipTest(reason) | |
| else: | |
| return func(*args, **kwargs) | |
| return wrapper | |
| # Some decorators can be called both with no arguments (e.g. @expectedFailureWindows) | |
| # or with arguments (e.g. @expectedFailureWindows(compilers=['gcc'])). When called | |
| # the first way, the first argument will be the actual function because decorators are | |
| # weird like that. So this is basically a check that says "how was the | |
| # decorator used" | |
| if callable(bugnumber): | |
| return skipTestIfFn_impl(bugnumber) | |
| else: | |
| return skipTestIfFn_impl | |
| def _xfailForVariant(variant_name, expected_fn, bugnumber=None): | |
| """Mark a test method as expected-failure for a specific variant dimension. | |
| Adds *expected_fn* to the decorated function's `__variant_xfail__` | |
| dictionary under the key *variant_name*. When the `LLDBTestCaseFactory` | |
| metaclass expands the test method, it looks up *variant_name* in this | |
| dictionary and calls the corresponding function with the concrete variant | |
| value. If the function returns a reason string, the expanded test method | |
| is marked as expected failure via `unittest.expectedFailure`. | |
| Args: | |
| variant_name: The variant dimension name (e.g. `"debug_info"`). | |
| expected_fn: Callable `(**{variant_name: value}) -> reason | None`. | |
| bugnumber: Optional bug reference or the decorated function itself | |
| (when the decorator is used without parentheses). | |
| """ | |
| def expectedFailure_impl(func): | |
| if isinstance(func, type) and issubclass(func, unittest.TestCase): | |
| raise Exception("Decorator can only be used to decorate a test method") | |
| xfail_dict = getattr(func, "__variant_xfail__", {}) | |
| xfail_dict[variant_name] = expected_fn | |
| func.__variant_xfail__ = xfail_dict | |
| return func | |
| if callable(bugnumber): | |
| return expectedFailure_impl(bugnumber) | |
| else: | |
| return expectedFailure_impl | |
| def FreshTestFunction(src): | |
| """Return a private copy of *src* for one generated test class to own. | |
| Several decorators record their state on the function object they are | |
| handed instead of on a wrapper. | |
| """ | |
| @wraps(src) | |
| def copy(self): | |
| return src(self) | |
| return copy | |
| def _skipForVariant(variant_name, expected_fn, bugnumber=None): | |
| """Mark a test method as skipped for a specific variant dimension. | |
| Adds *expected_fn* to the decorated function's `__variant_skip__` | |
| dictionary under the key *variant_name*. When the `LLDBTestCaseFactory` | |
| metaclass expands the test method, it looks up *variant_name* in this | |
| dictionary and calls the corresponding function with the concrete variant | |
| value. If the function returns a reason string, the expanded test method | |
| is skipped via `unittest.skip(reason)`. | |
| Args: | |
| variant_name: The variant dimension name (e.g. `"debug_info"`). | |
| expected_fn: Callable `(**{variant_name: value}) -> reason | None`. | |
| bugnumber: Optional bug reference or the decorated function itself | |
| (when the decorator is used without parentheses). | |
| """ | |
| def skipImpl(func): | |
| if isinstance(func, type) and issubclass(func, unittest.TestCase): | |
| raise Exception("Decorator can only be used to decorate a test method") | |
| skip_dict = getattr(func, "__variant_skip__", {}) | |
| existing_fn = skip_dict.get(variant_name) | |
| if existing_fn: | |
| # Chain the decorator with the existing one. | |
| def chained_fn(**kwargs): | |
| reason = expected_fn(**kwargs) | |
| if reason: | |
| return reason | |
| return existing_fn(**kwargs) | |
| skip_dict[variant_name] = chained_fn | |
| else: | |
| skip_dict[variant_name] = expected_fn | |
| func.__variant_skip__ = skip_dict | |
| return func | |
| if callable(bugnumber): | |
| return skipImpl(bugnumber) | |
| else: | |
| return skipImpl | |
| def _decorateTest( | |
| mode, | |
| bugnumber=None, | |
| oslist=None, | |
| hostoslist=None, | |
| compiler=None, | |
| compiler_version=None, | |
| archs=None, | |
| triple=None, | |
| debug_info=None, | |
| swig_version=None, | |
| py_version=None, | |
| macos_version=None, | |
| macos_sdk_version=None, | |
| remote=None, | |
| dwarf_version=None, | |
| setting=None, | |
| asan=None, | |
| ): | |
| def fn(**actual_variants): | |
| skip_for_os = _match_decorator_property( | |
| lldbplatform.translate(oslist), lldbplatformutil.getPlatform() | |
| ) | |
| skip_for_hostos = _match_decorator_property( | |
| lldbplatform.translate(hostoslist), lldbplatformutil.getHostPlatform() | |
| ) | |
| skip_for_compiler = _match_decorator_property( | |
| compiler, lldbplatformutil.getCompiler() | |
| ) and lldbplatformutil.expectedCompilerVersion(compiler_version) | |
| skip_for_arch = _match_decorator_property( | |
| archs, lldbplatformutil.getArchitecture() | |
| ) | |
| skip_for_debug_info = _match_decorator_property( | |
| debug_info, actual_variants.get("debug_info") | |
| ) | |
| skip_for_triple = _match_decorator_property( | |
| triple, lldb.selected_platform.GetTriple() | |
| ) | |
| skip_for_remote = _match_decorator_property( | |
| remote, lldb.remote_platform is not None | |
| ) | |
| skip_for_swig_version = ( | |
| (swig_version is None) | |
| or (not hasattr(lldb, "swig_version")) | |
| or ( | |
| _check_expected_version( | |
| swig_version[0], swig_version[1], lldb.swig_version | |
| ) | |
| ) | |
| ) | |
| skip_for_py_version = (py_version is None) or _check_expected_version( | |
| py_version[0], | |
| py_version[1], | |
| "{}.{}".format(sys.version_info.major, sys.version_info.minor), | |
| ) | |
| skip_for_macos_version = (macos_version is None) or ( | |
| (platform.mac_ver()[0] != "") | |
| and ( | |
| _check_expected_version( | |
| macos_version[0], macos_version[1], platform.mac_ver()[0] | |
| ) | |
| ) | |
| ) | |
| _sdk_ver = _get_macos_sdk_version() | |
| skip_for_macos_sdk_version = macos_sdk_version is None | |
| if macos_sdk_version is not None and _sdk_ver is not None: | |
| skip_for_macos_sdk_version = _check_expected_version( | |
| macos_sdk_version[0], macos_sdk_version[1], _sdk_ver | |
| ) | |
| skip_for_dwarf_version = (dwarf_version is None) or ( | |
| _check_expected_version( | |
| dwarf_version[0], dwarf_version[1], lldbplatformutil.getDwarfVersion() | |
| ) | |
| ) | |
| skip_for_setting = (setting is None) or (setting in configuration.settings) | |
| skip_for_asan = (asan is None) or is_running_under_asan() | |
| # For the test to be skipped, all specified (e.g. not None) parameters must be True. | |
| # An unspecified parameter means "any", so those are marked skip by default. And we skip | |
| # the final test if all conditions are True. | |
| conditions = [ | |
| (oslist, skip_for_os, "target o/s"), | |
| (hostoslist, skip_for_hostos, "host o/s"), | |
| (compiler, skip_for_compiler, "compiler or version"), | |
| (archs, skip_for_arch, "architecture"), | |
| (debug_info, skip_for_debug_info, "debug info format"), | |
| (triple, skip_for_triple, "target triple"), | |
| (swig_version, skip_for_swig_version, "swig version"), | |
| (py_version, skip_for_py_version, "python version"), | |
| (macos_version, skip_for_macos_version, "macOS version"), | |
| (macos_sdk_version, skip_for_macos_sdk_version, "macOS SDK version"), | |
| (remote, skip_for_remote, "platform locality (remote/local)"), | |
| (dwarf_version, skip_for_dwarf_version, "dwarf version"), | |
| (setting, skip_for_setting, "setting"), | |
| (asan, skip_for_asan, "running under asan"), | |
| ] | |
| reasons = [] | |
| final_skip_result = True | |
| for this_condition in conditions: | |
| final_skip_result = final_skip_result and this_condition[1] | |
| if this_condition[0] is not None and this_condition[1]: | |
| reasons.append(this_condition[2]) | |
| reason_str = None | |
| if final_skip_result: | |
| mode_str = {DecorateMode.Skip: "skipping", DecorateMode.Xfail: "xfailing"}[ | |
| mode | |
| ] | |
| if len(reasons) > 0: | |
| reason_str = ",".join(reasons) | |
| reason_str = "{} due to the following parameter(s): {}".format( | |
| mode_str, reason_str | |
| ) | |
| else: | |
| reason_str = "{} unconditionally".format(mode_str) | |
| if bugnumber is not None and not callable(bugnumber): | |
| reason_str = reason_str + " [" + str(bugnumber) + "]" | |
| return reason_str | |
| if mode == DecorateMode.Skip: | |
| if debug_info: | |
| return _skipForVariant("debug_info", fn, bugnumber) | |
| return skipTestIfFn(fn, bugnumber) | |
| elif mode == DecorateMode.Xfail: | |
| if debug_info: | |
| return _xfailForVariant("debug_info", fn, bugnumber) | |
| return expectedFailureIf(fn(), bugnumber) | |
| else: | |
| return None | |
| # provide a function to xfail on defined oslist, compiler version, and archs | |
| # if none is specified for any argument, that argument won't be checked and thus means for all | |
| # for example, | |
| # @expectedFailureAll, xfail for all platform/compiler/arch, | |
| # @expectedFailureAll(compiler='gcc'), xfail for gcc on all platform/architecture | |
| # @expectedFailureAll(bugnumber, ["linux"], "gcc", ['>=', '4.9'], ['i386']), xfail for gcc>=4.9 on linux with i386 | |
| def expectedFailureAll( | |
| bugnumber=None, | |
| oslist=None, | |
| hostoslist=None, | |
| compiler=None, | |
| compiler_version=None, | |
| archs=None, | |
| triple=None, | |
| debug_info=None, | |
| swig_version=None, | |
| py_version=None, | |
| macos_version=None, | |
| macos_sdk_version=None, | |
| remote=None, | |
| dwarf_version=None, | |
| setting=None, | |
| asan=None, | |
| ): | |
| return _decorateTest( | |
| DecorateMode.Xfail, | |
| bugnumber=bugnumber, | |
| oslist=oslist, | |
| hostoslist=hostoslist, | |
| compiler=compiler, | |
| compiler_version=compiler_version, | |
| archs=archs, | |
| triple=triple, | |
| debug_info=debug_info, | |
| swig_version=swig_version, | |
| py_version=py_version, | |
| macos_version=macos_version, | |
| macos_sdk_version=macos_sdk_version, | |
| remote=remote, | |
| dwarf_version=dwarf_version, | |
| setting=setting, | |
| asan=asan, | |
| ) | |
| # provide a function to skip on defined oslist, compiler version, and archs | |
| # if none is specified for any argument, that argument won't be checked and thus means for all | |
| # for example, | |
| # @skipIf, skip for all platform/compiler/arch, | |
| # @skipIf(compiler='gcc'), skip for gcc on all platform/architecture | |
| # @skipIf(bugnumber, ["linux"], "gcc", ['>=', '4.9'], ['i386']), skip for gcc>=4.9 on linux with i386 (all conditions must be true) | |
| def skipIf( | |
| bugnumber=None, | |
| oslist=None, | |
| hostoslist=None, | |
| compiler=None, | |
| compiler_version=None, | |
| archs=None, | |
| triple=None, | |
| debug_info=None, | |
| swig_version=None, | |
| py_version=None, | |
| macos_version=None, | |
| macos_sdk_version=None, | |
| remote=None, | |
| dwarf_version=None, | |
| setting=None, | |
| asan=None, | |
| ): | |
| return _decorateTest( | |
| DecorateMode.Skip, | |
| bugnumber=bugnumber, | |
| oslist=oslist, | |
| hostoslist=hostoslist, | |
| compiler=compiler, | |
| compiler_version=compiler_version, | |
| archs=archs, | |
| triple=triple, | |
| debug_info=debug_info, | |
| swig_version=swig_version, | |
| py_version=py_version, | |
| macos_version=macos_version, | |
| macos_sdk_version=macos_sdk_version, | |
| remote=remote, | |
| dwarf_version=dwarf_version, | |
| setting=setting, | |
| asan=asan, | |
| ) | |
| def skip(bugnumber=None): | |
| return _decorateTest(DecorateMode.Skip, bugnumber=bugnumber) | |
| def _skip_fn_for_android(reason, api_levels, archs): | |
| def impl(): | |
| result = lldbplatformutil.match_android_device( | |
| lldbplatformutil.getArchitecture(), | |
| valid_archs=archs, | |
| valid_api_levels=api_levels, | |
| ) | |
| return reason if result else None | |
| return impl | |
| def add_test_categories(cat): | |
| """Add test categories to a TestCase method""" | |
| cat = test_categories.validate(cat, True) | |
| def impl(func): | |
| try: | |
| if hasattr(func, "categories"): | |
| cat.extend(func.categories) | |
| setattr(func, "categories", cat) | |
| except AttributeError: | |
| raise Exception("Cannot assign categories to inline tests.") | |
| return func | |
| return impl | |
| def unicode_test(func): | |
| """Decorate the item as a test which requires Unicode to be enabled. | |
| lldb checks the value of the `LANG` environment variable for the substring "utf-8" | |
| to determine if the terminal supports Unicode (except on Windows, where stdout | |
| being connected to an interactive console is used as the signal instead). | |
| This decorator sets LANG to `utf-8` before running the test and resets it to its | |
| previous value afterwards. | |
| """ | |
| if sys.platform == "win32": | |
| import ctypes | |
| STD_OUTPUT_HANDLE = -11 | |
| FILE_TYPE_CHAR = 0x0002 | |
| handle = ctypes.windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) | |
| file_type = ctypes.windll.kernel32.GetFileType(handle) | |
| # Mirror Terminal::SupportsUnicode(): Unicode is supported only when | |
| # stdout is connected to a real console. | |
| if file_type != FILE_TYPE_CHAR: | |
| return unittest.skip( | |
| "Unicode test requires an interactive console (stderr is redirected)" | |
| ) | |
| return func | |
| def unicode_wrapped(*args, **kwargs): | |
| import os | |
| previous_lang = os.environ.get("LANG", None) | |
| os.environ["LANG"] = "en_US.UTF-8" | |
| try: | |
| func(*args, **kwargs) | |
| except Exception as err: | |
| raise err | |
| finally: | |
| # Reset the value, whether the test failed or not. | |
| if previous_lang is not None: | |
| os.environ["LANG"] = previous_lang | |
| else: | |
| del os.environ["LANG"] | |
| return unicode_wrapped | |
| def no_debug_info_test(func): | |
| """Decorate the item as a test what don't use any debug info. If this annotation is specified | |
| then the test runner won't generate a separate test for each debug info format.""" | |
| if isinstance(func, type) and issubclass(func, unittest.TestCase): | |
| raise Exception( | |
| "@no_debug_info_test can only be used to decorate a test method" | |
| ) | |
| @wraps(func) | |
| def wrapper(self, *args, **kwargs): | |
| return func(self, *args, **kwargs) | |
| # Mark this function as such to separate them from the regular tests. | |
| wrapper.__no_debug_info_test__ = True | |
| return wrapper | |
| def apple_simulator_test(platform): | |
| """ | |
| Decorate the test as a test requiring a simulator for a specific platform. | |
| Consider that a simulator is available if you have the corresponding SDK | |
| and runtime installed. | |
| The SDK identifiers for simulators are iphonesimulator, appletvsimulator, | |
| watchsimulator | |
| """ | |
| def should_skip_simulator_test(): | |
| if lldbplatformutil.getHostPlatform() not in ["darwin", "macosx"]: | |
| return "simulator tests are run only on darwin hosts." | |
| if lldbplatformutil.getArchitecture() == "arm64e": | |
| return "simulators do not support arm64e." | |
| # Make sure we recognize the platform. | |
| mapping = { | |
| "iphone": "ios", | |
| "appletv": "tvos", | |
| "watch": "watchos", | |
| } | |
| if platform not in mapping: | |
| return "unknown simulator platform: {}".format(platform) | |
| # Make sure we have an SDK. | |
| try: | |
| output = subprocess.check_output( | |
| ["xcodebuild", "-showsdks"], stderr=subprocess.DEVNULL | |
| ).decode("utf-8") | |
| if not re.search("%ssimulator" % platform, output): | |
| return "%s simulator is not supported on this system." % platform | |
| except subprocess.CalledProcessError: | |
| return "Simulators are unsupported on this system (xcodebuild failed)" | |
| # Make sure we a simulator runtime. | |
| try: | |
| sim_devices_str = subprocess.check_output( | |
| ["xcrun", "simctl", "list", "-j", "devices"] | |
| ).decode("utf-8") | |
| sim_devices = json.loads(sim_devices_str)["devices"] | |
| for simulator in sim_devices: | |
| if isinstance(simulator, dict): | |
| runtime = simulator["name"] | |
| devices = simulator["devices"] | |
| else: | |
| runtime = simulator | |
| devices = sim_devices[simulator] | |
| if not mapping[platform] in runtime.lower(): | |
| continue | |
| for device in devices: | |
| if ( | |
| "availability" in device | |
| and device["availability"] == "(available)" | |
| ): | |
| return None | |
| if "isAvailable" in device and device["isAvailable"]: | |
| return None | |
| return "{} simulator is not supported on this system.".format(platform) | |
| except (subprocess.CalledProcessError, json.decoder.JSONDecodeError): | |
| return "Simulators are unsupported on this system (simctl failed)" | |
| return skipTestIfFn(should_skip_simulator_test) | |
| def debugserver_test(func): | |
| """Decorate the item as a debugserver test.""" | |
| return add_test_categories(["debugserver"])(func) | |
| def llgs_test(func): | |
| """Decorate the item as a lldb-server test.""" | |
| return add_test_categories(["llgs"])(func) | |
| def expectedFailureOS( | |
| oslist, bugnumber=None, compilers=None, debug_info=None, archs=None | |
| ): | |
| return expectedFailureAll( | |
| oslist=oslist, | |
| bugnumber=bugnumber, | |
| compiler=compilers, | |
| archs=archs, | |
| debug_info=debug_info, | |
| ) | |
| def expectedFailureDarwin(bugnumber=None, compilers=None, debug_info=None, archs=None): | |
| # For legacy reasons, we support both "darwin" and "macosx" as OS X | |
| # triples. | |
| return expectedFailureOS( | |
| lldbplatform.darwin_all, | |
| bugnumber, | |
| compilers, | |
| debug_info=debug_info, | |
| archs=archs, | |
| ) | |
| def expectedFailureAndroid(bugnumber=None, api_levels=None, archs=None): | |
| """Mark a test as xfail for Android. | |
| Arguments: | |
| bugnumber - The LLVM pr associated with the problem. | |
| api_levels - A sequence of numbers specifying the Android API levels | |
| for which a test is expected to fail. None means all API level. | |
| arch - A sequence of architecture names specifying the architectures | |
| for which a test is expected to fail. None means all architectures. | |
| """ | |
| return expectedFailureIf( | |
| _skip_fn_for_android("xfailing on android", api_levels, archs)(), bugnumber | |
| ) | |
| def expectedFailureNetBSD(bugnumber=None): | |
| return expectedFailureOS(["netbsd"], bugnumber) | |
| def expectedFailureWindows(bugnumber=None): | |
| return expectedFailureOS(["windows"], bugnumber) | |
| def _usingLLDBServerOnWindows(): | |
| """Return True if Windows tests should drive lldb-server instead of the | |
| in-process Win32 ``windows`` process plugin. | |
| The choice is controlled by the ``LLDB_USE_LLDB_SERVER`` environment | |
| variable: unset/off selects the default in-process plugin, on selects | |
| the gdb-remote path through ``lldb-server``. | |
| """ | |
| return os.environ.get("LLDB_USE_LLDB_SERVER", "").lower() in ( | |
| "on", | |
| "yes", | |
| "1", | |
| "true", | |
| ) | |
| def expectedFailureWindowsAndLLDBServer(bugnumber=None): | |
| """Mark a test as xfail on Windows when driving lldb-server.""" | |
| if not _usingLLDBServerOnWindows(): | |
| return lambda func: func | |
| return expectedFailureOS(["windows"], bugnumber) | |
| def expectedFailureWindowsAndNoLLDBServer(bugnumber=None): | |
| """Mark a test as xfail on Windows when using the in-process plugin.""" | |
| if _usingLLDBServerOnWindows(): | |
| return lambda func: func | |
| return expectedFailureOS(["windows"], bugnumber) | |
| # TODO: This decorator does not do anything. Remove it. | |
| def expectedFlakey(expected_fn, bugnumber=None): | |
| def expectedFailure_impl(func): | |
| @wraps(func) | |
| def wrapper(*args, **kwargs): | |
| func(*args, **kwargs) | |
| return wrapper | |
| # Some decorators can be called both with no arguments (e.g. @expectedFailureWindows) | |
| # or with arguments (e.g. @expectedFailureWindows(compilers=['gcc'])). When called | |
| # the first way, the first argument will be the actual function because decorators are | |
| # weird like that. So this is basically a check that says "which syntax was the original | |
| # function decorated with?" | |
| if callable(bugnumber): | |
| return expectedFailure_impl(bugnumber) | |
| else: | |
| return expectedFailure_impl | |
| def expectedFlakeyOS(oslist, bugnumber=None, compilers=None): | |
| def fn(self): | |
| return ( | |
| lldbplatformutil.getPlatform() in oslist | |
| and lldbplatformutil.expectedCompiler(compilers) | |
| ) | |
| return expectedFlakey(fn, bugnumber) | |
| def expectedFlakeyDarwin(bugnumber=None, compilers=None): | |
| # For legacy reasons, we support both "darwin" and "macosx" as OS X | |
| # triples. | |
| return expectedFlakeyOS(lldbplatformutil.getDarwinOSTriples(), bugnumber, compilers) | |
| def expectedFlakeyFreeBSD(bugnumber=None, compilers=None): | |
| return expectedFlakeyOS(["freebsd"], bugnumber, compilers) | |
| def expectedFlakeyLinux(bugnumber=None, compilers=None): | |
| return expectedFlakeyOS(["linux"], bugnumber, compilers) | |
| def expectedFlakeyNetBSD(bugnumber=None, compilers=None): | |
| return expectedFlakeyOS(["netbsd"], bugnumber, compilers) | |
| def expectedFlakeyAndroid(bugnumber=None, api_levels=None, archs=None): | |
| return expectedFlakey( | |
| _skip_fn_for_android("flakey on android", api_levels, archs), bugnumber | |
| ) | |
| def skipIfOutOfTreeDebugserver(func): | |
| """Decorate the item to skip tests if using an out-of-tree debugserver.""" | |
| def is_out_of_tree_debugserver(): | |
| return ( | |
| "out-of-tree debugserver" | |
| if lldbtest_config.out_of_tree_debugserver | |
| else None | |
| ) | |
| return skipTestIfFn(is_out_of_tree_debugserver)(func) | |
| def skipIfOutOfTreeLibunwind(func): | |
| """Decorate the item to skip tests if libunwind was not built in-tree.""" | |
| def is_out_of_tree_libunwind(): | |
| if not configuration.llvm_tools_dir: | |
| return "out-of-tree libunwind" | |
| # llvm_tools_dir is typically <build>/bin, so lib is a sibling. | |
| llvm_lib_dir = os.path.join( | |
| os.path.dirname(configuration.llvm_tools_dir), "lib" | |
| ) | |
| if not os.path.isdir(llvm_lib_dir): | |
| return "out-of-tree libunwind" | |
| # Check for libunwind library (any extension). | |
| for filename in os.listdir(llvm_lib_dir): | |
| if filename.startswith("libunwind.") or filename.startswith("unwind."): | |
| return None | |
| return "out-of-tree libunwind" | |
| return skipTestIfFn(is_out_of_tree_libunwind)(func) | |
| def skipIfRemote(func): | |
| """Decorate the item to skip tests if testing remotely.""" | |
| return unittest.skipIf(lldb.remote_platform, "skip on remote platform")(func) | |
| def skipIfNoSBHeaders(func): | |
| """Decorate the item to mark tests that should be skipped when LLDB is built with no SB API headers.""" | |
| def are_sb_headers_missing(): | |
| if lldb.remote_platform: | |
| return "skip because SBHeaders tests make no sense remotely" | |
| if ( | |
| lldbplatformutil.getHostPlatform() == "darwin" | |
| and configuration.lldb_framework_path | |
| ): | |
| header = os.path.join( | |
| configuration.lldb_framework_path, | |
| "Versions", | |
| "Current", | |
| "Headers", | |
| "LLDB.h", | |
| ) | |
| if os.path.exists(header): | |
| return None | |
| header = os.path.join( | |
| os.environ["LLDB_SRC"], "include", "lldb", "API", "LLDB.h" | |
| ) | |
| if not os.path.exists(header): | |
| return "skip because LLDB.h header not found" | |
| return None | |
| return skipTestIfFn(are_sb_headers_missing)(func) | |
| def skipIfRosetta(bugnumber): | |
| """Skip a test when running the testsuite on macOS under the Rosetta translation layer.""" | |
| def is_running_rosetta(): | |
| if lldbplatformutil.getPlatform() in ["darwin", "macosx"]: | |
| if (platform.uname()[5] == "arm") and ( | |
| lldbplatformutil.getArchitecture() == "x86_64" | |
| ): | |
| return "skipped under Rosetta" | |
| return None | |
| return skipTestIfFn(is_running_rosetta) | |
| def skipIfiOSSimulator(func): | |
| """Decorate the item to skip tests that should be skipped on the iOS Simulator.""" | |
| def is_ios_simulator(): | |
| return ( | |
| "skip on the iOS Simulator" | |
| if configuration.lldb_platform_name == "ios-simulator" | |
| else None | |
| ) | |
| return skipTestIfFn(is_ios_simulator)(func) | |
| def skipIfiOS(func): | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.ios))(func) | |
| def skipIftvOS(func): | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.tvos))(func) | |
| def skipIfwatchOS(func): | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.watchos))(func) | |
| def skipIfbridgeOS(func): | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.bridgeos))(func) | |
| def skipIfDarwinEmbedded(func): | |
| """Decorate the item to skip tests that should be skipped on Darwin armv7/arm64 targets.""" | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.darwin_embedded))(func) | |
| def skipIfDarwinSimulator(func): | |
| """Decorate the item to skip tests that should be skipped on Darwin simulator targets.""" | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.darwin_simulator))(func) | |
| def skipIfFreeBSD(func): | |
| """Decorate the item to skip tests that should be skipped on FreeBSD.""" | |
| return skipIfPlatform(["freebsd"])(func) | |
| def skipIfNetBSD(func): | |
| """Decorate the item to skip tests that should be skipped on NetBSD.""" | |
| return skipIfPlatform(["netbsd"])(func) | |
| def skipIfDarwin(func): | |
| """Decorate the item to skip tests that should be skipped on Darwin.""" | |
| return skipIfPlatform(lldbplatform.translate(lldbplatform.darwin_all))(func) | |
| def skipIfLinux(func): | |
| """Decorate the item to skip tests that should be skipped on Linux.""" | |
| return skipIfPlatform(["linux"])(func) | |
| def skipIfWasm(func): | |
| """Decorate the item to skip tests that should be skipped on WebAssembly.""" | |
| return skipIfPlatform(["wasip1", "wasi"])(func) | |
| def skipIfNoSignals(func): | |
| """Decorate the item to skip tests on platforms without signal support.""" | |
| return skipIfPlatform(["windows", "wasip1", "wasi"])(func) | |
| def skipIfWindows(func=None, windows_version=None): | |
| """Decorate the item to skip tests that should be skipped on Windows.""" | |
| def decorator(func): | |
| if windows_version is None: | |
| return skipIfPlatform(["windows"])(func) | |
| else: | |
| actual_win_version = lldbplatformutil.getWindowsVersion() | |
| def version_check(): | |
| if actual_win_version == "unknown": | |
| return False | |
| operator, required_windows_version = windows_version | |
| return lldbplatformutil.isExpectedVersion( | |
| actual_version=actual_win_version, | |
| required_version=required_windows_version, | |
| operator=operator, | |
| ) | |
| return unittest.skipIf( | |
| version_check(), | |
| f"Test is skipped on Windows '{actual_win_version}'", | |
| )(func) | |
| if func is not None: | |
| return decorator(func) | |
| return decorator | |
| def skipIfWindowsAndLLDBServer(func): | |
| """Skip tests on Windows when driving lldb-server.""" | |
| if not _usingLLDBServerOnWindows(): | |
| return func | |
| return skipIfPlatform(["windows"])(func) | |
| def skipIfWindowsAndNoLLDBServer(func): | |
| """Skip tests on Windows when using the in-process plugin.""" | |
| if _usingLLDBServerOnWindows(): | |
| return func | |
| return skipIfPlatform(["windows"])(func) | |
| def skipIfWindowsAndNonEnglish(func): | |
| """Decorate the item to skip tests that should be skipped on non-English locales on Windows.""" | |
| def is_Windows_NonEnglish(): | |
| if sys.platform != "win32": | |
| return None | |
| kernel = ctypes.windll.kernel32 | |
| if locale.windows_locale[kernel.GetUserDefaultUILanguage()] == "en_US": | |
| return None | |
| return "skipping non-English Windows locale" | |
| return skipTestIfFn(is_Windows_NonEnglish)(func) | |
| def skipUnlessWindows(func): | |
| """Decorate the item to skip tests that should be skipped on any non-Windows platform.""" | |
| return skipUnlessPlatform(["windows"])(func) | |
| def skipUnlessWindowsConPTY(func): | |
| """Skip if on Windows older than 10.0.17763 (the first build to ship ConPTY).""" | |
| return skipIfWindows(windows_version=["<", "10.0.17763"])(func) | |
| def skipUnlessWindowsConPTY2022(func): | |
| """Skip on Windows older than 10.0.20348 (Server 2022). | |
| Windows Server 2019 (10.0.17763) emits additional VT initialisation | |
| sequences that LLDB does not handle. | |
| """ | |
| return skipIfWindows(windows_version=["<", "10.0.20348"])(func) | |
| def skipUnlessDarwin(func): | |
| """Decorate the item to skip tests that should be skipped on any non Darwin platform.""" | |
| return skipUnlessPlatform(lldbplatformutil.getDarwinOSTriples())(func) | |
| def skipUnlessTargetAndroid(func): | |
| return unittest.skipUnless( | |
| lldbplatformutil.target_is_android(), "requires target to be Android" | |
| )(func) | |
| def skipIfHostIncompatibleWithTarget(func): | |
| """Decorate the item to skip tests when the host and target are incompatible.""" | |
| def is_host_incompatible_with_remote(): | |
| host_arch = lldbplatformutil.getLLDBArchitecture() | |
| host_platform = lldbplatformutil.getHostPlatform() | |
| target_arch = lldbplatformutil.getArchitecture() | |
| target_platform = lldbplatformutil.getPlatform() | |
| if ( | |
| not (target_arch == "x86_64" and host_arch == "i386") | |
| and host_arch != target_arch | |
| ): | |
| return ( | |
| "skipping because target %s is not compatible with host architecture %s" | |
| % (target_arch, host_arch) | |
| ) | |
| if target_platform != host_platform: | |
| return "skipping because target is %s but host is %s" % ( | |
| target_platform, | |
| host_platform, | |
| ) | |
| if lldbplatformutil.match_android_device(target_arch): | |
| return "skipping because target is android" | |
| return None | |
| return skipTestIfFn(is_host_incompatible_with_remote)(func) | |
| def skipIfPlatform(oslist): | |
| """Decorate the item to skip tests if running on one of the listed platforms.""" | |
| # This decorator cannot be ported to `skipIf` yet because it is used on entire | |
| # classes, which `skipIf` explicitly forbids. | |
| return unittest.skipIf( | |
| lldbplatformutil.getPlatform() in oslist, "skip on %s" % (", ".join(oslist)) | |
| ) | |
| def skipUnlessPlatform(oslist): | |
| """Decorate the item to skip tests unless running on one of the listed platforms.""" | |
| # This decorator cannot be ported to `skipIf` yet because it is used on entire | |
| # classes, which `skipIf` explicitly forbids. | |
| return unittest.skipUnless( | |
| lldbplatformutil.getPlatform() in oslist, | |
| "requires one of %s" % (", ".join(oslist)), | |
| ) | |
| ############################################################################## | |
| # Platform *requirement* decorators. | |
| # | |
| # These express "this test can only ever run here", as opposed to the skipIf / | |
| # skipUnless family which means "this test ought to run here but is broken". | |
| # Tests turned off by a `require*` decorator are reported as UNSUPPORTED; tests | |
| # turned off by a `skip*` decorator are reported as SKIPPED. See | |
| # `lldbsuite.test.skip_reason` for how the distinction is carried. | |
| # | |
| # Reach for these when the test is inherently tied to a platform: it debugs a | |
| # platform-specific file format, drives a platform-specific API, or exercises | |
| # an OS feature that simply doesn't exist elsewhere. If the test is merely | |
| # untested or broken somewhere, keep using skipIf / skipUnless so it stays | |
| # visible as work to be done. | |
| ############################################################################## | |
| def requirePlatform(oslist): | |
| """Mark the item as runnable only on the listed target platforms. | |
| Unlike `skipUnlessPlatform`, other platforms are reported as UNSUPPORTED | |
| rather than SKIPPED. | |
| """ | |
| return unittest.skipUnless( | |
| lldbplatformutil.getPlatform() in oslist, | |
| UnsupportedReason("requires one of %s" % (", ".join(oslist))), | |
| ) | |
| def requireNotPlatform(oslist: list, reason: Optional[str] = None): | |
| """Mark the item as inherently inapplicable to the listed target platforms. | |
| Unlike `skipIfPlatform`, the listed platforms are reported as UNSUPPORTED | |
| rather than SKIPPED. | |
| """ | |
| assert isinstance( | |
| reason, (str, type(None)) | |
| ), f"expects 'str' or 'None' got {type(reason).__name__!r}" | |
| skip_reason = f"unsupported on {', '.join(oslist)}" | |
| if reason: | |
| skip_reason += f": {reason}" | |
| return unittest.skipIf( | |
| lldbplatformutil.getPlatform() in oslist, UnsupportedReason(skip_reason) | |
| ) | |
| def requireDarwin(func): | |
| """Mark the item as inherently Darwin-only (Mach-O, debug maps, Darwin | |
| kernel/runtime APIs, ...). Non-Darwin targets report UNSUPPORTED.""" | |
| return requirePlatform(lldbplatform.translate(lldbplatform.darwin_all))(func) | |
| def requireNotDarwin(reason: str): | |
| """Mark the item as inherently inapplicable to Darwin targets.""" | |
| return requireNotPlatform( | |
| lldbplatform.translate(lldbplatform.darwin_all), reason=reason | |
| ) | |
| def requireLinux(func): | |
| """Mark the item as inherently Linux-only (procfs, Linux-specific syscalls, | |
| ...). Other targets report UNSUPPORTED.""" | |
| return requirePlatform(["linux"])(func) | |
| def requireNotLinux(reason: str): | |
| """Mark the item as inherently inapplicable to Linux targets.""" | |
| return requireNotPlatform(["linux"], reason=reason) | |
| def requireWindows(func): | |
| """Mark the item as inherently Windows-only (PE/COFF, Win32 APIs, ...). | |
| Other targets report UNSUPPORTED.""" | |
| return requirePlatform(["windows"])(func) | |
| def requireNotWindows(reason: str): | |
| """Mark the item as inherently inapplicable to Windows targets. | |
| Use this for tests built on POSIX-only concepts: fork/exec semantics, | |
| POSIX signals, ptrace, ELF/Mach-O specifics, shell pipelines, and so on. | |
| """ | |
| return requireNotPlatform(["windows"], reason=reason) | |
| def requirePOSIX(func): | |
| """Mark the item as requiring a POSIX target. | |
| A shorthand for `requireNotWindows` that reads better on tests whose | |
| dependency is POSIX semantics generally rather than anything about | |
| Windows specifically. | |
| """ | |
| return requireNotPlatform(["windows"], reason="uses the posix API.")(func) | |
| def requireMacOS(func): | |
| """Mark the item as inherently macOS-only, as opposed to other Darwin | |
| platforms (iOS, tvOS, watchOS, ...). | |
| """ | |
| return requirePlatform(["macosx"])(func) | |
| def requireSignals(func): | |
| """Mark the item as requiring POSIX signal support on the target.""" | |
| return requireNotPlatform(["windows", "wasip1", "wasi"])(func) | |
| def requireExpressionEvaluation(func): | |
| """Mark the item as requiring expression evaluation.""" | |
| return requireNotWasm(reason="needs expression evaluation support")(func) | |
| def requireNotWasm(reason: str): | |
| """Mark the item as inherently inapplicable to WebAssembly targets. | |
| WebAssembly has no processes, no signals, no shared libraries and no | |
| ptrace-style debugging, so a large amount of the test suite can never | |
| apply to it. | |
| """ | |
| return requireNotPlatform(["wasip1", "wasi"], reason=reason) | |
| def requireHostPlatform(oslist): | |
| """Mark the item as runnable only on the listed *host* platforms.""" | |
| return unittest.skipUnless( | |
| lldbplatformutil.getHostPlatform() in oslist, | |
| UnsupportedReason("requires one of %s as host" % (", ".join(oslist))), | |
| ) | |
| def requireDarwinHost(func): | |
| """Mark the item as requiring a Darwin host, regardless of target. | |
| Use for tests that drive host-side Darwin facilities: `xcrun`, the | |
| simulator runtimes, dsymutil, the LLDB.framework layout, and so on. | |
| """ | |
| return requireHostPlatform(lldbplatform.translate(lldbplatform.darwin_all))(func) | |
| def requireThreadSupport(func): | |
| """Mark the item as requiring thread support (e.g. pthreads) on the target.""" | |
| platform = lldbplatformutil.getPlatform() | |
| return unittest.skipIf( | |
| # WASI targets ending in "-threads" (e.g. wasip1-threads) support threads; | |
| # other WASI targets (e.g. wasip1, wasip2) do not. | |
| platform.startswith("wasi") and not platform.endswith("threads"), | |
| UnsupportedReason(f"threads are not supported on {platform}"), | |
| )(func) | |
| @lru_cache(maxsize=None) | |
| def _socketPermissionError() -> Optional[str]: | |
| """Probe whether the host lets us open a listening socket. | |
| Returns None if it does, otherwise a description of why it doesn't. | |
| """ | |
| family = socket.AF_INET | |
| addr = ("localhost", 0) | |
| try: | |
| with socket.socket(family, socket.SOCK_STREAM) as sock: | |
| sock.bind(addr) | |
| sock.listen(1) | |
| except OSError as e: | |
| return f"host does not permit opening a listening socket: {e}" | |
| return None | |
| def requireSocketPermission(func): | |
| """Mark the item as requiring permission to open a listening socket.""" | |
| error = _socketPermissionError() | |
| return unittest.skipIf(error is not None, UnsupportedReason(error or ""))(func) | |
| def requireClang(func): | |
| """Mark the item as inherently Clang-only (Clang-specific debug info, | |
| diagnostics, or command-line flags).""" | |
| compiler = os.path.basename(lldbplatformutil.getCompiler()) | |
| return unittest.skipUnless( | |
| compiler.startswith("clang"), UnsupportedReason("requires clang") | |
| )(func) | |
| def requireMinidumpSave(func): | |
| """Mark the item as requiring the ability to save a Minidump file. Which | |
| is not implemented for all architectures.""" | |
| arch = lldbplatformutil.getArchitecture() | |
| return unittest.skipUnless( | |
| arch in ["aarch64", "x86_64", "arm64", "arm64e"], | |
| UnsupportedReason(f"Minidump saving is not implemented for {arch}"), | |
| )(func) | |
| def skipIfTargetDoesNotSupportSharedLibraries(): | |
| """Skip tests that require shared library (dylib/so) support.""" | |
| platform = lldbplatformutil.getPlatform() | |
| return unittest.skipIf( | |
| platform.startswith("wasi"), | |
| "shared libraries are not supported on %s" % platform, | |
| ) | |
| def skipUnlessArch(arch): | |
| """Decorate the item to skip tests unless running on the specified architecture.""" | |
| def arch_doesnt_match(): | |
| target_arch = lldbplatformutil.getArchitecture() | |
| if arch != target_arch: | |
| return "Test only runs on " + arch + ", but target arch is " + target_arch | |
| return None | |
| return skipTestIfFn(arch_doesnt_match) | |
| def skipIfTargetAndroid(bugnumber=None, api_levels=None, archs=None): | |
| """Decorator to skip tests when the target is Android. | |
| Arguments: | |
| api_levels - The API levels for which the test should be skipped. If | |
| it is None, then the test will be skipped for all API levels. | |
| arch - A sequence of architecture names specifying the architectures | |
| for which a test is skipped. None means all architectures. | |
| """ | |
| return skipTestIfFn( | |
| _skip_fn_for_android("skipping for android", api_levels, archs), bugnumber | |
| ) | |
| def skipUnlessAppleSilicon(func): | |
| """Decorate the item to skip tests unless running on Apple Silicon.""" | |
| def not_apple_silicon(): | |
| if platform.system() != "Darwin" or lldbplatformutil.getArchitecture() not in [ | |
| "arm64", | |
| "arm64e", | |
| ]: | |
| return "Test only runs on Apple Silicon" | |
| return None | |
| return skipTestIfFn(not_apple_silicon)(func) | |
| def skipUnlessSupportedTypeAttribute(attr): | |
| """Decorate the item to skip test unless Clang supports type __attribute__(attr).""" | |
| def compiler_doesnt_support_struct_attribute(): | |
| compiler_path = lldbplatformutil.getCompiler() | |
| with temp_file.OnDiskTempFile() as f: | |
| cmd = [lldbplatformutil.getCompiler(), "-x", "c++", "-c", "-o", f.path, "-"] | |
| p = subprocess.Popen( | |
| cmd, | |
| stdin=subprocess.PIPE, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.PIPE, | |
| universal_newlines=True, | |
| ) | |
| stdout, stderr = p.communicate("struct __attribute__((%s)) Test {};" % attr) | |
| if attr in stderr: | |
| return "Compiler does not support attribute %s" % (attr) | |
| return None | |
| return skipTestIfFn(compiler_doesnt_support_struct_attribute) | |
| def skipUnlessHasCallSiteInfo(func): | |
| """Decorate the function to skip testing unless call site info from clang is available.""" | |
| def is_compiler_clang_with_call_site_info(): | |
| compiler_path = lldbplatformutil.getCompiler() | |
| compiler = os.path.basename(compiler_path) | |
| if not compiler.startswith("clang"): | |
| return "Test requires clang as compiler" | |
| with temp_file.OnDiskTempFile() as f: | |
| cmd = ( | |
| "echo 'int main() {}' | " | |
| "%s -g -glldb -O1 -S -emit-llvm -x c -o %s -" % (compiler_path, f.path) | |
| ) | |
| if os.popen(cmd).close() is not None: | |
| return "Compiler can't compile with call site info enabled" | |
| with open(f.path, "r") as ir_output_file: | |
| buf = ir_output_file.read() | |
| if "DIFlagAllCallsDescribed" not in buf: | |
| return "Compiler did not introduce DIFlagAllCallsDescribed IR flag" | |
| return None | |
| return skipTestIfFn(is_compiler_clang_with_call_site_info)(func) | |
| def skipUnlessMSVC(func): | |
| """Decorate the item to skip test unless msvc is available.""" | |
| def is_msvc_in_path(): | |
| try: | |
| result = subprocess.run( | |
| ["cl.exe"], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| except FileNotFoundError: | |
| return f"Test requires MSVC to be in the Path." | |
| if result.returncode != 0: | |
| return f"Test requires MSVC to be in the Path." | |
| return None | |
| return skipTestIfFn(is_msvc_in_path)(func) | |
| def skipUnlessThreadSanitizer(func): | |
| """Decorate the item to skip test unless Clang -fsanitize=thread is supported.""" | |
| def is_compiler_clang_with_thread_sanitizer(): | |
| if is_running_under_asan(): | |
| return "Thread sanitizer tests are disabled when runing under ASAN" | |
| compiler_path = lldbplatformutil.getCompiler() | |
| compiler = os.path.basename(compiler_path) | |
| if not compiler.startswith("clang"): | |
| return "Test requires clang as compiler" | |
| if lldbplatformutil.getPlatform() == "windows": | |
| return "TSAN tests not compatible with 'windows'" | |
| # rdar://28659145 - TSAN tests don't look like they're supported on i386 | |
| if ( | |
| lldbplatformutil.getArchitecture() == "i386" | |
| and platform.system() == "Darwin" | |
| ): | |
| return "TSAN tests not compatible with i386 targets" | |
| if not _compiler_supports(compiler_path, "-fsanitize=thread"): | |
| return "Compiler cannot compile with -fsanitize=thread" | |
| return None | |
| return skipTestIfFn(is_compiler_clang_with_thread_sanitizer)(func) | |
| def skipUnlessUndefinedBehaviorSanitizer(func): | |
| """Decorate the item to skip test unless -fsanitize=undefined is supported.""" | |
| def is_compiler_clang_with_ubsan(): | |
| if is_running_under_asan(): | |
| return ( | |
| "Undefined behavior sanitizer tests are disabled when runing under ASAN" | |
| ) | |
| # We need to write out the object into a named temp file for inspection. | |
| with temp_file.OnDiskTempFile() as outputf: | |
| # Try to compile with ubsan turned on. | |
| if not _compiler_supports( | |
| lldbplatformutil.getCompiler(), | |
| "-fsanitize=undefined", | |
| "int main() { int x = 0; return x / x; }", | |
| outputf, | |
| ): | |
| return "Compiler cannot compile with -fsanitize=undefined" | |
| if not outputf.path: | |
| return "Cannot create Temp file path." | |
| nm_bin = configuration.get_nm_path() | |
| if not nm_bin: | |
| return "No llvm-nm or nm binary." | |
| # Check that we actually see ubsan instrumentation in the binary. | |
| nm_output = subprocess.check_output([nm_bin, outputf.path], text=True) | |
| if "__ubsan_handle_divrem_overflow" not in nm_output: | |
| return "Division by zero instrumentation is missing" | |
| return None | |
| return skipTestIfFn(is_compiler_clang_with_ubsan)(func) | |
| def is_running_under_asan(): | |
| if "ASAN_OPTIONS" in os.environ: | |
| return "ASAN unsupported" | |
| return None | |
| def is_running_under_mte(): | |
| if configuration.mte_enabled: | |
| return "MTE unsupported" | |
| return None | |
| def skipIfMTE(func): | |
| """Skip this test when running with MTE (Memory Tagging Extension) enabled.""" | |
| return skipTestIfFn(is_running_under_mte)(func) | |
| def skipUnlessAddressSanitizer(func): | |
| """Decorate the item to skip test unless Clang -fsanitize=thread is supported.""" | |
| def is_compiler_with_address_sanitizer(): | |
| # Also don't run tests that use address sanitizer inside an | |
| # address-sanitized LLDB. The tests don't support that | |
| # configuration. | |
| if is_running_under_asan(): | |
| return "Address sanitizer tests are disabled when runing under ASAN" | |
| if lldbplatformutil.getPlatform() == "windows": | |
| return "ASAN tests not compatible with 'windows'" | |
| if not _compiler_supports(lldbplatformutil.getCompiler(), "-fsanitize=address"): | |
| return "Compiler cannot compile with -fsanitize=address" | |
| return None | |
| return skipTestIfFn(is_compiler_with_address_sanitizer)(func) | |
| def skipUnlessBoundsSafety(func): | |
| """Decorate the item to skip test unless Clang -fbounds-safety is supported.""" | |
| def is_compiler_with_bounds_safety(): | |
| if not _compiler_supports(lldbplatformutil.getCompiler(), "-fbounds-safety"): | |
| return "Compiler cannot compile with -fbounds-safety" | |
| return None | |
| return skipTestIfFn(is_compiler_with_bounds_safety)(func) | |
| def skipUnlessCompilerSupports(flag): | |
| """Decorate the item to skip the test unless the compiler supports this flag.""" | |
| def does_compiler_support_flag(): | |
| if not _compiler_supports(lldbplatformutil.getCompiler(), flag): | |
| return f"Compiler does not support flag {flag}" | |
| return None | |
| return skipTestIfFn(does_compiler_support_flag) | |
| def skipIfAsan(func): | |
| """Skip this test if the environment is set up to run LLDB *itself* under ASAN.""" | |
| return skipTestIfFn(is_running_under_asan)(func) | |
| def skipUnlessAArch64MTELinuxCompiler(func): | |
| """Decorate the item to skip test unless MTE is supported by the test compiler.""" | |
| def is_toolchain_with_mte(): | |
| compiler_path = lldbplatformutil.getCompiler() | |
| with temp_file.OnDiskTempFile() as f: | |
| if lldbplatformutil.getPlatform() == "windows": | |
| return "MTE tests are not compatible with 'windows'" | |
| cmd = f"{compiler_path} -x c -o {f.path} -" | |
| if ( | |
| subprocess.run( | |
| cmd, shell=True, input="int main() {}".encode() | |
| ).returncode | |
| != 0 | |
| ): | |
| # Cannot compile at all, don't skip the test | |
| # so that we report the broken compiler normally. | |
| return None | |
| # We need the Linux headers and ACLE MTE intrinsics | |
| test_src = """ | |
| #include <asm/hwcap.h> | |
| #include <arm_acle.h> | |
| #ifndef HWCAP2_MTE | |
| #error | |
| #endif | |
| int main() { | |
| void* ptr = __arm_mte_create_random_tag((void*)(0), 0); | |
| }""" | |
| cmd = f"{compiler_path} -march=armv8.5-a+memtag -x c -o {f.path} -" | |
| res = subprocess.run(cmd, shell=True, input=test_src.encode()) | |
| if res.returncode != 0: | |
| return "Toolchain does not support MTE" | |
| return None | |
| return skipTestIfFn(is_toolchain_with_mte)(func) | |
| def _get_bool_config(key, fail_value=True): | |
| """ | |
| Returns the current LLDB's build config value. | |
| :param key The key to lookup in LLDB's build configuration. | |
| :param fail_value The error value to return when the key can't be found. | |
| Defaults to true so that if an unknown key is lookup up we rather | |
| enable more tests (that then fail) than silently skipping them. | |
| """ | |
| config = lldb.SBDebugger.GetBuildConfiguration() | |
| value_node = config.GetValueForKey(key) | |
| return value_node.GetValueForKey("value").GetBooleanValue(fail_value) | |
| def _get_bool_config_skip_if_decorator(key): | |
| have = _get_bool_config(key) | |
| return unittest.skipIf(not have, "requires " + key) | |
| def skipIfCurlSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("curl")(func) | |
| def skipIfCursesSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("curses")(func) | |
| def skipIfXmlSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("xml")(func) | |
| def skipIfEditlineSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("editline")(func) | |
| def skipIfEditlineWideCharSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("editline_wchar")(func) | |
| def skipIfZLIBSupportMissing(func): | |
| return _get_bool_config_skip_if_decorator("zlib")(func) | |
| def skipIfLLVMTargetMissing(target): | |
| config = lldb.SBDebugger.GetBuildConfiguration() | |
| targets = config.GetValueForKey("targets").GetValueForKey("value") | |
| found = False | |
| for i in range(targets.GetSize()): | |
| if targets.GetItemAtIndex(i).GetStringValue(99) == target: | |
| found = True | |
| break | |
| return unittest.skipIf(not found, "requires " + target) | |
| def skipUnlessFeature(cpu_feature: CPUFeature): | |
| def hasFeature(test_case): | |
| if not test_case.isSupported(cpu_feature): | |
| return f"Unsupported CPU feature: {cpu_feature}" | |
| return None | |
| return skipTestIfFn(hasFeature) | |
| def skipIfBuildType(types: list[str]): | |
| """Skip tests if built in a specific CMAKE_BUILD_TYPE. | |
| Supported types include 'Release', 'RelWithDebInfo', 'Debug', 'MinSizeRel'. | |
| """ | |
| types = [name.lower() for name in types] | |
| return unittest.skipIf( | |
| configuration.cmake_build_type is not None | |
| and configuration.cmake_build_type.lower() in types, | |
| "skip on {} build type(s)".format(", ".join(types)), | |
| ) | |
| def skipUnlessArm64eSupported(func): | |
| """Decorate the item to skip test unless Clang can target arm64e.""" | |
| def can_build_and_run_arm64e(): | |
| arch = lldbplatformutil.getArchitecture() | |
| # We need to be running the test suite for arm64 or arm64e. If we're | |
| # running the whole test suite as arm64e, we don't need any additional | |
| # checks. | |
| if arch == "arm64e": | |
| return None | |
| elif arch != "arm64": | |
| return "Not targeting arm64" | |
| # Need at least macOS Tahoe (26) to run arm64e binaries. | |
| if platform.mac_ver()[0] == "" or _check_expected_version( | |
| "<", "26.0", platform.mac_ver()[0] | |
| ): | |
| return "Host cannot run arm64e binaries" | |
| # Need a compiler that can target arm64e. | |
| compiler_path = lldbplatformutil.getCompiler() | |
| if not _compiler_supports(compiler_path, "-arch arm64e"): | |
| return "Compiler cannot target arm64e" | |
| # Need debugserver built with arm64e support. | |
| if not configuration.arm64e_debugserver: | |
| return "debugserver not built with arm64e support" | |
| # Technically ASan is supported, but we need an arm64e sanitizer | |
| # runtime and we assume that's not the case unless we run the whole | |
| # test suite as arm64e. | |
| if is_running_under_asan(): | |
| return "Sanitizer runtime may not support arm64e" | |
| return None | |
| return skipTestIfFn(can_build_and_run_arm64e)(func) | |
| def skipUnlessPackageAvailable(name): | |
| """Skip the test case if the named package is not available on the system.""" | |
| available = True | |
| try: | |
| __import__(name) | |
| except ImportError: | |
| available = False | |
| return unittest.skipUnless(available, f"requires the '{name}' package") | |
| def skipUnlessTargetIsHost(func): | |
| """Skip the test case if the test binary architecture does not match LLDB.framework.""" | |
| def check_arch_match(): | |
| # The lldb executable is built the same as the framework. | |
| lldb_arch = lldbplatformutil.getLLDBArchitecture() | |
| test_arch = lldbplatformutil.getArchitecture() | |
| if lldb_arch != test_arch: | |
| return "Test binary architecture differs from host architecture" | |
| return None | |
| return skipTestIfFn(check_arch_match)(func) |