mgorny created this revision. mgorny added reviewers: labath, emaste, krytarowski, jingham. Herald added a subscriber: arichardson. Herald added a project: All. mgorny requested review of this revision.
Perform a major refactoring of vCont-threads tests in order to attempt to improve their stability and performance. Split test_vCont_run_subset_of_threads() into smaller test cases, and split the whole suite into two files: one for signal-related tests, the running-subset-of tests. Eliminate output_match checks entirely, as they are fragile to fragmentation of output. Instead, for the initial thread list capture raise an explicit SIGSTOP from inside the test program, and for the remaining output let the test program run until exit, and check all the captured output afterwards. For resume tests, capture the LLDB's thread view before and after starting new threads in order to determine the IDs corresponding to subthreads rather than relying on program output for that. Use write(2) for output, as using std::printf() seems to be fragile to the output being interspersed between threads mid-line. Call std::this_thread::yield() to reduce the risk of one of the threads not being run. Incidentally, this seems to stop the tests from hanging on FreeBSD. Hopefully, it will also fix all the flakiness. Sponsored by: The FreeBSD Foundation https://reviews.llvm.org/D129012 Files: lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py lldb/test/API/tools/lldb-server/vCont-threads/main.cpp
Index: lldb/test/API/tools/lldb-server/vCont-threads/main.cpp =================================================================== --- lldb/test/API/tools/lldb-server/vCont-threads/main.cpp +++ lldb/test/API/tools/lldb-server/vCont-threads/main.cpp @@ -6,11 +6,13 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <string> #include <thread> #include <unistd.h> #include <vector> pseudo_barrier_t barrier; +bool can_exit_now = false; static void sigusr1_handler(int signo) { char buf[100]; @@ -18,14 +20,26 @@ "received SIGUSR1 on thread id: %" PRIx64 "\n", get_thread_id()); write(STDOUT_FILENO, buf, strlen(buf)); + + can_exit_now = true; } static void thread_func() { pseudo_barrier_wait(barrier); - for (int i = 0; i < 300; ++i) { - std::printf("thread %" PRIx64 " running\n", get_thread_id()); + for (int i = 0; i < 5; ++i) { + char buf[100]; + std::snprintf(buf, sizeof(buf), "thread %" PRIx64 " running\n", + get_thread_id()); + write(STDOUT_FILENO, buf, strlen(buf)); + + std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(200)); + if (can_exit_now) + return; } + + // if we didn't get signaled, terminate the program explicitly. + exit(0); } int main(int argc, char **argv) { @@ -36,12 +50,12 @@ signal(SIGUSR1, sigusr1_handler); std::vector<std::thread> threads; - for(int i = 0; i < num; ++i) + for (int i = 0; i < num; ++i) threads.emplace_back(thread_func); pseudo_barrier_wait(barrier); - std::puts("@started"); + std::raise(SIGSTOP); for (std::thread &thread : threads) thread.join(); Index: lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py =================================================================== --- lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py +++ lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py @@ -1,23 +1,18 @@ -import json import re -import time import gdbremote_testcase from lldbsuite.test.decorators import * from lldbsuite.test.lldbtest import * from lldbsuite.test import lldbutil -class TestGdbRemote_vContThreads(gdbremote_testcase.GdbRemoteTestCaseBase): +class TestSignal(gdbremote_testcase.GdbRemoteTestCaseBase): def start_threads(self, num): procs = self.prep_debug_monitor_and_inferior(inferior_args=[str(num)]) - # start the process and wait for output self.test_sequence.add_log_lines([ "read packet: $c#63", - {"type": "output_match", "regex": r".*@started\r\n.*"}, + {"direction": "send", "regex": "[$]T.*;reason:signal.*"}, ], True) - # then interrupt it - self.add_interrupt_packets() self.add_threadinfo_collection_packets() context = self.expect_gdbremote_sequence() @@ -29,22 +24,21 @@ self.reset_test_sequence() return threads + SIGNAL_MATCH_RE = re.compile(r"received SIGUSR1 on thread id: ([0-9a-f]+)") + def send_and_check_signal(self, vCont_data, threads): self.test_sequence.add_log_lines([ "read packet: $vCont;{0}#00".format(vCont_data), - {"type": "output_match", - "regex": len(threads) * - r".*received SIGUSR1 on thread id: ([0-9a-f]+)\r\n.*", - "capture": dict((i, "tid{0}".format(i)) for i - in range(1, len(threads)+1)), - }, + "send packet: $W00#00", ], True) - - context = self.expect_gdbremote_sequence() - self.assertIsNotNone(context) - tids = sorted(int(context["tid{0}".format(x)], 16) - for x in range(1, len(threads)+1)) - self.assertEqual(tids, sorted(threads)) + exp = self.expect_gdbremote_sequence() + self.reset_test_sequence() + tids = [] + for line in exp["O_content"].decode().splitlines(): + m = self.SIGNAL_MATCH_RE.match(line) + if m is not None: + tids.append(int(m.group(1), 16)) + self.assertEqual(sorted(tids), sorted(threads)) def get_pid(self): self.add_process_info_collection_packets() @@ -242,72 +236,3 @@ context = self.expect_gdbremote_sequence() self.assertIsNotNone(context) - - THREAD_MATCH_RE = re.compile(r"thread ([0-9a-f]+) running") - - def continue_and_get_threads_running(self, continue_packet): - self.test_sequence.add_log_lines( - ["read packet: ${}#00".format(continue_packet), - ], True) - self.expect_gdbremote_sequence() - self.reset_test_sequence() - time.sleep(1) - self.add_interrupt_packets() - exp = self.expect_gdbremote_sequence() - found = set() - for line in exp["O_content"].decode().splitlines(): - m = self.THREAD_MATCH_RE.match(line) - if m is not None: - found.add(int(m.group(1), 16)) - return found - - @skipIfWindows - @add_test_categories(["llgs"]) - def test_vCont_run_subset_of_threads(self): - self.build() - self.set_inferior_startup_launch() - - threads = set(self.start_threads(3)) - all_subthreads = self.continue_and_get_threads_running("c") - all_subthreads_list = list(all_subthreads) - self.assertEqual(len(all_subthreads), 3) - self.assertEqual(threads & all_subthreads, all_subthreads) - - # resume two threads explicitly, stop the third one implicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};c:{:x}".format(*all_subthreads_list[:2])), - set(all_subthreads_list[:2])) - - # resume two threads explicitly, stop others explicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};c:{:x};t".format(*all_subthreads_list[:2])), - set(all_subthreads_list[:2])) - - # stop one thread explicitly, resume others - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c".format(all_subthreads_list[-1])), - set(all_subthreads_list[:2])) - - # resume one thread explicitly, stop one explicitly, - # resume others - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;c:{:x};t:{:x};c".format(*all_subthreads_list[-2:])), - set(all_subthreads_list[:2])) - - # resume one thread explicitly, stop one explicitly, - # stop others implicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c:{:x}".format(*all_subthreads_list[:2])), - set(all_subthreads_list[1:2])) - - # resume one thread explicitly, stop one explicitly, - # stop others explicitly - self.assertEqual( - self.continue_and_get_threads_running( - "vCont;t:{:x};c:{:x};t".format(*all_subthreads_list[:2])), - set(all_subthreads_list[1:2])) Index: lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py =================================================================== --- /dev/null +++ lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py @@ -0,0 +1,116 @@ +import re + +import gdbremote_testcase +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class TestPartialResume(gdbremote_testcase.GdbRemoteTestCaseBase): + THREAD_MATCH_RE = re.compile(r"thread ([0-9a-f]+) running") + + def start_vCont_run_subset_of_threads_test(self): + self.build() + self.set_inferior_startup_launch() + + procs = self.prep_debug_monitor_and_inferior(inferior_args=["3"]) + # grab the main thread id + self.add_threadinfo_collection_packets() + main_thread = self.parse_threadinfo_packets( + self.expect_gdbremote_sequence()) + self.assertEqual(len(main_thread), 1) + self.reset_test_sequence() + + # run until threads start, then grab full thread list + self.test_sequence.add_log_lines([ + "read packet: $c#63", + {"direction": "send", "regex": "[$]T.*;reason:signal.*"}, + ], True) + self.add_threadinfo_collection_packets() + + all_threads = self.parse_threadinfo_packets( + self.expect_gdbremote_sequence()) + self.assertEqual(len(all_threads), 4) + self.assertIn(main_thread[0], all_threads) + self.reset_test_sequence() + + all_subthreads = set(all_threads) - set(main_thread) + self.assertEqual(len(all_subthreads), 3) + + return list(all_subthreads) + + def continue_and_get_threads_running(self, continue_packet): + self.test_sequence.add_log_lines( + ["read packet: ${}#00".format(continue_packet), + "send packet: $W00#00", + ], True) + exp = self.expect_gdbremote_sequence() + self.reset_test_sequence() + found = set() + for line in exp["O_content"].decode().splitlines(): + m = self.THREAD_MATCH_RE.match(line) + if m is not None: + found.add(int(m.group(1), 16)) + return found + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxcx(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # resume two threads explicitly, stop the third one implicitly + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;c:{:x};c:{:x}".format(*all_subthreads_list[:2])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxcxt(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # resume two threads explicitly, stop others explicitly + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;c:{:x};c:{:x};t".format(*all_subthreads_list[:2])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txc(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # stop one thread explicitly, resume others + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;t:{:x};c".format(all_subthreads_list[-1])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_cxtxc(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # resume one thread explicitly, stop one explicitly, + # resume others + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;c:{:x};t:{:x};c".format(*all_subthreads_list[-2:])), + set(all_subthreads_list[:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txcx(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # resume one thread explicitly, stop one explicitly, + # stop others implicitly + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;t:{:x};c:{:x}".format(*all_subthreads_list[:2])), + set(all_subthreads_list[1:2])) + + @skipIfWindows + @add_test_categories(["llgs"]) + def test_vCont_txcxt(self): + all_subthreads_list = self.start_vCont_run_subset_of_threads_test() + # resume one thread explicitly, stop one explicitly, + # stop others explicitly + self.assertEqual( + self.continue_and_get_threads_running( + "vCont;t:{:x};c:{:x};t".format(*all_subthreads_list[:2])), + set(all_subthreads_list[1:2]))
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits