[Lldb-commits] [lldb] 86e4723 - [lldb] [test] Improve stability of llgs vCont-threads tests

Michał Górny via lldb-commits lldb-commits at lists.llvm.org
Thu Jul 7 07:35:09 PDT 2022


Author: Michał Górny
Date: 2022-07-07T16:33:55+02:00
New Revision: 86e472317c8fd9309b76c32ca55fcdeaf63f853b

URL: https://github.com/llvm/llvm-project/commit/86e472317c8fd9309b76c32ca55fcdeaf63f853b
DIFF: https://github.com/llvm/llvm-project/commit/86e472317c8fd9309b76c32ca55fcdeaf63f853b.diff

LOG: [lldb] [test] Improve stability of llgs vCont-threads tests

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.

Add a mutex for output to guarantee serialization.  A barrier is used
to guarantee that all threads start before SIGSTOP, and an atomic bool
is used to delay prints from happening until after SIGSTOP.

Call std::this_thread::yield() to reduce the risk of one of the threads
not being run.

This fixes the test hangs on FreeBSD.  Hopefully, it will also fix all
the flakiness on buildbots.

Sponsored by: The FreeBSD Foundation
Differential Revision: https://reviews.llvm.org/D129012

Added: 
    lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py
    lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py

Modified: 
    lldb/test/API/tools/lldb-server/vCont-threads/main.cpp

Removed: 
    lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py


################################################################################
diff  --git a/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py b/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py
new file mode 100644
index 0000000000000..2cc60d3d0a5c0
--- /dev/null
+++ b/lldb/test/API/tools/lldb-server/vCont-threads/TestPartialResume.py
@@ -0,0 +1,128 @@
+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 (main_thread[0], list(all_subthreads))
+
+    def continue_and_get_threads_running(self, main_thread, vCont_req):
+        self.test_sequence.add_log_lines(
+            ["read packet: $vCont;c:{:x};{}#00".format(main_thread, vCont_req),
+             "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):
+        main_thread, 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(
+                main_thread,
+                "c:{:x};c:{:x}".format(*all_subthreads_list[:2])),
+            set(all_subthreads_list[:2]))
+
+    @skipIfWindows
+    @add_test_categories(["llgs"])
+    def test_vCont_cxcxt(self):
+        main_thread, 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(
+                main_thread,
+                "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):
+        main_thread, 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(
+                main_thread,
+                "t:{:x};c".format(all_subthreads_list[-1])),
+            set(all_subthreads_list[:2]))
+
+    @skipIfWindows
+    @add_test_categories(["llgs"])
+    def test_vCont_cxtxc(self):
+        main_thread, 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(
+                main_thread,
+                "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):
+        main_thread, 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(
+                main_thread,
+                "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):
+        main_thread, 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(
+                main_thread,
+                "t:{:x};c:{:x};t".format(*all_subthreads_list[:2])),
+            set(all_subthreads_list[1:2]))

diff  --git a/lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py b/lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py
similarity index 71%
rename from lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py
rename to lldb/test/API/tools/lldb-server/vCont-threads/TestSignal.py
index 606875ed56213..6dde4f9555cf3 100644
--- a/lldb/test/API/tools/lldb-server/vCont-threads/TestGdbRemote_vContThreads.py
+++ b/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 @@ def start_threads(self, num):
         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 @@ def test_signal_two_signals(self):
 
         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]))

diff  --git a/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp b/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp
index 301b54f878dfc..c667f003e1d3f 100644
--- a/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp
+++ b/lldb/test/API/tools/lldb-server/vCont-threads/main.cpp
@@ -1,31 +1,54 @@
 #include "pseudo_barrier.h"
 #include "thread.h"
+#include <atomic>
 #include <chrono>
 #include <cinttypes>
 #include <csignal>
 #include <cstdio>
 #include <cstdlib>
 #include <cstring>
+#include <mutex>
 #include <thread>
 #include <unistd.h>
 #include <vector>
 
 pseudo_barrier_t barrier;
+std::mutex print_mutex;
+std::atomic<bool> can_work = ATOMIC_VAR_INIT(false);
+thread_local volatile sig_atomic_t can_exit_now = false;
 
 static void sigusr1_handler(int signo) {
-  char buf[100];
-  std::snprintf(buf, sizeof(buf),
-                "received SIGUSR1 on thread id: %" PRIx64 "\n",
-                get_thread_id());
-  write(STDOUT_FILENO, buf, strlen(buf));
+  std::lock_guard<std::mutex> lock{print_mutex};
+  std::printf("received SIGUSR1 on thread id: %" PRIx64 "\n", get_thread_id());
+  can_exit_now = true;
 }
 
 static void thread_func() {
+  // this ensures that all threads start before we SIGSTOP
   pseudo_barrier_wait(barrier);
-  for (int i = 0; i < 300; ++i) {
+
+  // wait till the main thread indicates that we can go
+  // (note: using a mutex here causes hang on FreeBSD when another thread
+  // is suspended)
+  while (!can_work.load())
+    std::this_thread::sleep_for(std::chrono::milliseconds(50));
+
+  // the mutex guarantees that two writes don't get interspersed
+  {
+    std::lock_guard<std::mutex> lock{print_mutex};
     std::printf("thread %" PRIx64 " running\n", get_thread_id());
+  }
+
+  // give other threads a fair chance to run
+  for (int i = 0; i < 5; ++i) {
+    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 +59,15 @@ int main(int argc, char **argv) {
   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);
 
+  // use the barrier to make sure all threads start before we SIGSTOP
   pseudo_barrier_wait(barrier);
+  std::raise(SIGSTOP);
 
-  std::puts("@started");
+  // allow the threads to work
+  can_work.store(true);
 
   for (std::thread &thread : threads)
     thread.join();


        


More information about the lldb-commits mailing list