mirror-llvm-project/lldb/test/API/macosx/thread_suspend/TestInternalThreadSuspension.py
Charles Zablit 67a20745d6
[lldb] Introduce the require decorator (#212753)
Currently, it is unclear if a test that is skipped is either:
- Unsupported because it will never run (macosx tests should not run on
Windows).
- Skipped because it's supposed to work but does not yet.
`@expectedFailure` is not always an option because a test might be
timing out and waiting 600s for it to fail is not acceptable.

This patch introduces 2 new decorators to differentiate those tests:
- `@requireX` meaning the tests require X to run (e.g `@requireDarwin`).
- `@requireNotX` meaning the tests requires anything but `X` to run (e.g
`@requireNotDarwin`).

In the tests results summary, this introduces a new category: `skipped`
tests:
- `@requireX` -> `UNSUPPORTED`
- `@skipIfX` -> `SKIPPED`

Here is how they can and cannot be stacked:

This will never run:
```py3
@requireDarwin
@requireWindows
def test():
    ...
```

This will not run on Linux, because `@requireWindows` is not satisfied.
```py3
@requireNotDarwin
@requireWindows
def test():
    ...
```

For "require Darwin or Windows", you need:
```py3
@requirePlatform(["darwin", "windows"])
def test():
    ...
```

For "require Darwin on ARM64 or Windows on ARM64", you need:
```py3
@requireARM64
@requirePlatform(["darwin", "windows"])
def test():
    ...
```

This patch also converts the tests in
`lldb/tests/API/{macosx,linux,windows}` to use these decorators. Patches
to convert the rest of the tests will follow.
2026-08-01 18:03:29 +02:00

118 lines
4.9 KiB
Python

"""
Make sure that if threads are suspended outside of lldb, debugserver
won't make them run, even if we call an expression on the thread.
"""
import lldb
from lldbsuite.test.decorators import *
import lldbsuite.test.lldbutil as lldbutil
from lldbsuite.test.lldbtest import *
class TestSuspendedThreadHandling(TestBase):
NO_DEBUG_INFO_TESTCASE = True
@requireDarwin
def test_suspended_threads(self):
"""Test that debugserver doesn't disturb the suspend count of a thread
that has been suspended from within a program, when navigating breakpoints
on other threads, or calling functions both on the suspended thread and
on other threads."""
self.build()
self.main_source_file = lldb.SBFileSpec("main.c")
self.suspended_thread_test()
def setUp(self):
# Call super's setUp().
TestBase.setUp(self)
# Set up your test case here. If your test doesn't need any set up then
# remove this method from your TestCase class.
def try_an_expression(self, thread, correct_value, test_bp):
frame = thread.frames[0]
value = frame.EvaluateExpression("function_to_call()")
self.assertSuccess(value.GetError(), "Successfully called the function")
self.assertEqual(
value.GetValueAsSigned(), correct_value, "Got expected value for expression"
)
# Again, make sure we didn't let the suspend thread breakpoint run:
self.assertEqual(
test_bp.GetHitCount(),
0,
"First expression allowed the suspend thread to run",
)
def make_bkpt(self, pattern):
bp = self.target.BreakpointCreateBySourceRegex(pattern, self.main_source_file)
self.assertEqual(bp.GetNumLocations(), 1, "Locations for %s" % (pattern))
return bp
def suspended_thread_test(self):
(self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "Stop here to get things going", self.main_source_file
)
# Make in the running thread, so the we will have to stop a number of times
# while handling breakpoints. The first couple of times we hit it we will
# run expressions as well. Make sure we don't let the suspended thread run
# during those operations.
rt_bp = self.make_bkpt("Break here to show we can handle breakpoints")
# Make a breakpoint that we will hit when the running thread exits:
rt_exit_bp = self.make_bkpt("Break here after thread_join")
# Make a breakpoint in the suspended thread. We should not hit this till we
# resume it after joining the running thread.
st_bp = self.make_bkpt("We allowed the suspend thread to run")
# Make a breakpoint after pthread_join of the suspend thread to ensure
# that we didn't keep the thread from exiting normally
st_exit_bp = self.make_bkpt(
" Break here to make sure the thread exited normally"
)
threads = lldbutil.continue_to_breakpoint(process, rt_bp)
self.assertEqual(len(threads), 1, "Hit the running_func breakpoint")
# Make sure we didn't hit the suspend thread breakpoint:
self.assertEqual(
st_bp.GetHitCount(), 0, "Continue allowed the suspend thread to run"
)
# Now try an expression on the running thread:
self.try_an_expression(threads[0], 0, st_bp)
# Continue, and check the same things:
threads = lldbutil.continue_to_breakpoint(process, rt_bp)
self.assertEqual(len(threads), 1, "We didn't hit running breakpoint")
# Try an expression on the suspended thread:
thread = lldb.SBThread()
for thread in process.threads:
th_name = thread.GetName()
if th_name is None:
continue
if "Look for me" in th_name:
break
self.assertTrue(thread.IsValid(), "We found the suspend thread.")
self.try_an_expression(thread, 1, st_bp)
# Now set the running thread breakpoint to auto-continue and let it
# run a bit to make sure we still don't let the suspend thread run.
rt_bp.SetAutoContinue(True)
threads = lldbutil.continue_to_breakpoint(process, rt_exit_bp)
self.assertEqual(len(threads), 1)
self.assertEqual(
st_bp.GetHitCount(), 0, "Continue again let suspended thread run"
)
# Now continue and we SHOULD hit the suspend_func breakpoint:
threads = lldbutil.continue_to_breakpoint(process, st_bp)
self.assertEqual(len(threads), 1, "The thread resumed successfully")
# Finally, continue again and we should get out of the last pthread_join
# and the process should be about to exit
threads = lldbutil.continue_to_breakpoint(process, st_exit_bp)
self.assertEqual(len(threads), 1, "pthread_join exited successfully")