Skip to content

[lldb][test] Apply @expectedFailureAll/@skipIf early for debug_info tests #73067

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jan 19, 2024
53 changes: 50 additions & 3 deletions lldb/packages/Python/lldbsuite/test/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ def _compiler_supports(
return True


def expectedFailureIf(condition, bugnumber=None):
def expectedFailure_impl(func):
if isinstance(func, type) and issubclass(func, unittest2.TestCase):
raise Exception("Decorator can only be used to decorate a test method")

if condition:
return unittest2.expectedFailure(func)
return func

if callable(bugnumber):
return expectedFailure_impl(bugnumber)
else:
return expectedFailure_impl


def expectedFailureIfFn(expected_fn, bugnumber=None):
def expectedFailure_impl(func):
if isinstance(func, type) and issubclass(func, unittest2.TestCase):
Expand Down Expand Up @@ -174,6 +189,34 @@ def wrapper(*args, **kwargs):
return skipTestIfFn_impl


def _xfailForDebugInfo(expected_fn, bugnumber=None):
def expectedFailure_impl(func):
if isinstance(func, type) and issubclass(func, unittest2.TestCase):
raise Exception("Decorator can only be used to decorate a test method")

func.__xfail_for_debug_info_cat_fn__ = expected_fn
return func

if callable(bugnumber):
return expectedFailure_impl(bugnumber)
else:
return expectedFailure_impl


def _skipForDebugInfo(expected_fn, bugnumber=None):
def skipImpl(func):
if isinstance(func, type) and issubclass(func, unittest2.TestCase):
raise Exception("Decorator can only be used to decorate a test method")

func.__skip_for_debug_info_cat_fn__ = expected_fn
return func

if callable(bugnumber):
return skipImpl(bugnumber)
else:
return skipImpl


def _decorateTest(
mode,
bugnumber=None,
Expand All @@ -191,7 +234,7 @@ def _decorateTest(
dwarf_version=None,
setting=None,
):
def fn(self):
def fn(actual_debug_info=None):
skip_for_os = _match_decorator_property(
lldbplatform.translate(oslist), lldbplatformutil.getPlatform()
)
Expand All @@ -204,7 +247,7 @@ def fn(self):
skip_for_arch = _match_decorator_property(
archs, lldbplatformutil.getArchitecture()
)
skip_for_debug_info = _match_decorator_property(debug_info, self.getDebugInfo())
skip_for_debug_info = _match_decorator_property(debug_info, actual_debug_info)
skip_for_triple = _match_decorator_property(
triple, lldb.selected_platform.GetTriple()
)
Expand Down Expand Up @@ -279,9 +322,13 @@ def fn(self):
return reason_str

if mode == DecorateMode.Skip:
if debug_info:
return _skipForDebugInfo(fn, bugnumber)
return skipTestIfFn(fn, bugnumber)
elif mode == DecorateMode.Xfail:
return expectedFailureIfFn(fn, bugnumber)
if debug_info:
return _xfailForDebugInfo(fn, bugnumber)
return expectedFailureIf(fn(), bugnumber)
else:
return None

Expand Down
22 changes: 22 additions & 0 deletions lldb/packages/Python/lldbsuite/test/lldbtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1667,6 +1667,11 @@ def __new__(cls, name, bases, attrs):
if original_testcase.NO_DEBUG_INFO_TESTCASE:
return original_testcase

# Default implementation for skip/xfail reason based on the debug category,
# where "None" means to run the test as usual.
def no_reason(_):
return None

newattrs = {}
for attrname, attrvalue in attrs.items():
if attrname.startswith("test") and not getattr(
Expand All @@ -1688,6 +1693,12 @@ def __new__(cls, name, bases, attrs):
if can_replicate
]

xfail_for_debug_info_cat_fn = getattr(
attrvalue, "__xfail_for_debug_info_cat_fn__", no_reason
)
skip_for_debug_info_cat_fn = getattr(
attrvalue, "__skip_for_debug_info_cat_fn__", no_reason
)
for cat in categories:

@decorators.add_test_categories([cat])
Expand All @@ -1698,6 +1709,17 @@ def test_method(self, attrvalue=attrvalue):
method_name = attrname + "_" + cat
test_method.__name__ = method_name
test_method.debug_info = cat

xfail_reason = xfail_for_debug_info_cat_fn(cat)
if xfail_reason:
test_method = unittest2.expectedFailure(xfail_reason)(
test_method
)

skip_reason = skip_for_debug_info_cat_fn(cat)
if skip_reason:
test_method = unittest2.skip(skip_reason)(test_method)

newattrs[method_name] = test_method

else:
Expand Down
110 changes: 107 additions & 3 deletions lldb/test/API/test_utils/TestDecorators.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,115 @@
from lldbsuite.test.lldbtest import Base
import re

from lldbsuite.test.lldbtest import TestBase
from lldbsuite.test.decorators import *


class TestDecorators(Base):
def expectedFailureDwarf(bugnumber=None):
return expectedFailureAll(bugnumber, debug_info="dwarf")


class TestDecoratorsNoDebugInfoClass(TestBase):
NO_DEBUG_INFO_TESTCASE = True

@expectedFailureAll(debug_info="dwarf")
def test_decorator_skip_no_debug_info(self):
def test_decorator_xfail(self):
"""Test that specifying a debug info category works for a NO_DEBUG_INFO_TESTCASE"""

@expectedFailureDwarf
def test_decorator_xfail_bare_decorator(self):
"""Same as test_decorator_xfail, but with a custom decorator w/ a bare syntax"""

@expectedFailureDwarf()
def test_decorator_xfail_decorator_empty_args(self):
"""Same as test_decorator_xfail, but with a custom decorator w/ no args"""

@add_test_categories(["dwarf"])
def test_add_test_categories(self):
# Note: the "dwarf" test category is ignored, because we don't generate any debug info test variants
self.assertIsNone(self.getDebugInfo())

@expectedFailureAll
def test_xfail_regexp(self):
"""Test that expectedFailureAll can be empty (but please just use expectedFailure)"""
self.fail()

@expectedFailureAll(compiler=re.compile(".*"))
def test_xfail_regexp(self):
"""Test that xfail can take a regex as a matcher"""
self.fail()

@expectedFailureAll(compiler=no_match(re.compile(".*")))
def test_xfail_no_match(self):
"""Test that xfail can take a no_match matcher"""
pass

@expectedFailureIf(condition=True)
def test_xfail_condition_true(self):
self.fail()

@expectedFailureIf(condition=False)
def test_xfail_condition_false(self):
pass


class TestDecorators(TestBase):
@expectedFailureAll(debug_info="dwarf")
def test_decorator_xfail(self):
"""Test that expectedFailureAll fails for the debug_info variant"""
if self.getDebugInfo() == "dwarf":
self.fail()

@skipIf(debug_info="dwarf")
def test_decorator_skip(self):
"""Test that skipIf skips the debug_info variant"""
self.assertNotEqual(self.getDebugInfo(), "dwarf")

@expectedFailureDwarf
def test_decorator_xfail2(self):
"""Same as test_decorator_xfail, but with a custom decorator w/ a bare syntax"""
if self.getDebugInfo() == "dwarf":
self.fail()

@expectedFailureDwarf()
def test_decorator_xfail3(self):
"""Same as test_decorator_xfail, but with a custom decorator w/ no args"""
if self.getDebugInfo() == "dwarf":
self.fail()

@add_test_categories(["dwarf"])
def test_add_test_categories(self):
"""Test that add_test_categories limits the kinds of debug info test variants"""
self.assertEqual(self.getDebugInfo(), "dwarf")

@expectedFailureAll(compiler="fake", debug_info="dwarf")
def test_decorator_xfail_all(self):
"""Test that expectedFailureAll requires all conditions to match to be xfail"""

@skipIf(compiler="fake", debug_info="dwarf")
def test_decorator_skip2(self):
"""Test that expectedFailureAll fails for the debug_info variant"""
# Note: the following assertion would fail, if this were not skipped:
# self.assertNotEqual(self.getDebugInfo(), "dwarf")

@expectedFailureAll
def test_xfail_regexp(self):
"""Test that xfail can be empty"""
self.fail()

@expectedFailureAll(compiler=re.compile(".*"))
def test_xfail_regexp(self):
"""Test that xfail can take a regex as a matcher"""
self.fail()

@expectedFailureAll(compiler=no_match(re.compile(".*")))
def test_xfail_no_match(self):
"""Test that xfail can take a no_match matcher"""
pass

@expectedFailureIf(condition=True)
def test_xfail_condition_true(self):
self.fail()

@expectedFailureIf(condition=False)
def test_xfail_condition_false(self):
pass