https://github.com/Teemperor updated 
https://github.com/llvm/llvm-project/pull/201171

>From 7f58ba0c2e7d6812da6432f773725888788d5410 Mon Sep 17 00:00:00 2001
From: Raphael Isemann <[email protected]>
Date: Tue, 2 Jun 2026 12:50:45 +0100
Subject: [PATCH 1/2] [lldb][test] Always call quit when tearing down pexpect
 tests

Right now we manually have to call quit at the end of each pexpect test.
This patches makes this call automatic.

This also makes tests that missed the call and where previously
waiting for a timeout faster. For example, TestClangREPL.py now only
takes about 10 seconds to run instead of 1 minute.
---
 .../Python/lldbsuite/test/lldbpexpect.py      | 19 +++++++++++++++++--
 .../formatting/TestAproposFormatting.py       |  3 ---
 .../TestMultilineCompletion.py                |  1 -
 .../TestMultilineNavigation.py                |  3 ---
 .../API/commands/gui/basic/TestGuiBasic.py    |  1 -
 .../gui/basicdebug/TestGuiBasicDebug.py       |  1 -
 .../gui/breakpoints/TestGuiBreakpoints.py     |  1 -
 .../TestGuiExpandThreadsTree.py               |  1 -
 .../gui/spawn-threads/TestGuiSpawnThreads.py  |  1 -
 .../gui/viewlarge/TestGuiViewLarge.py         |  1 -
 .../autosuggestion/TestAutosuggestion.py      |  3 ---
 .../completion/TestIOHandlerCompletion.py     |  1 -
 .../iohandler/resize/TestIOHandlerResize.py   |  1 -
 .../sigint/TestIOHandlerPythonREPLSigint.py   |  2 --
 .../sigint/TestProcessIOHandlerInterrupt.py   |  1 -
 .../stdio/TestIOHandlerProcessSTDIO.py        |  1 -
 .../test/API/iohandler/unicode/TestUnicode.py |  1 -
 lldb/test/API/repl/clang/TestClangREPL.py     |  1 -
 .../API/terminal/TestDisabledBreakpoints.py   |  1 -
 lldb/test/API/terminal/TestEditline.py        |  1 -
 20 files changed, 17 insertions(+), 28 deletions(-)

diff --git a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py 
b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
index ff8c4dd3fc037..60b4f7f5d4a4b 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
@@ -100,9 +100,24 @@ def expect(self, cmd, substrs=None):
                 self.child.expect_exact(s)
         self.expect_prompt()
 
+    def tearDown(self):
+        # Ensure the child is always cleaned up, even if the test didn't call
+        # quit() explicitly or failed before reaching it.
+        if self.child is not None:
+            self.quit()
+        super().tearDown()
+
     def quit(self, gracefully=True):
-        self.child.sendeof()
-        self.child.close(force=not gracefully)
+        if self.child is None:
+            return
+
+        try:
+            self.child.sendeof()
+            self.child.close(force=not gracefully)
+        except OSError:
+            # This can happen if LLDB quit itself because it is running in
+            # batch mode.
+            pass
         self.child = None
 
     def cursor_forward_escape_seq(self, chars_to_move):
diff --git a/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py 
b/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
index b9a8f52dbdc16..a3b44a3ca1115 100644
--- a/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
+++ b/lldb/test/API/commands/apropos/formatting/TestAproposFormatting.py
@@ -32,7 +32,6 @@ def test_apropos_with_settings_alignment(self):
         )
 
         self.expect_prompt()
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -73,7 +72,6 @@ def test_apropos_highlights_matches(self):
         # Settings descriptions.
         self.child.expect_exact("'" + ansi_green + "plug" + ansi_reset + "in")
         self.expect_prompt()
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -95,4 +93,3 @@ def test_apropos_highlights_across_line_break(self):
         self.child.expect_exact(ansi_green + "will")
         self.child.expect_exact("this" + ansi_reset)
         self.expect_prompt()
-        self.quit()
diff --git 
a/lldb/test/API/commands/expression/multiline-completion/TestMultilineCompletion.py
 
b/lldb/test/API/commands/expression/multiline-completion/TestMultilineCompletion.py
index c578a1d4149fa..1dddeabe0442f 100644
--- 
a/lldb/test/API/commands/expression/multiline-completion/TestMultilineCompletion.py
+++ 
b/lldb/test/API/commands/expression/multiline-completion/TestMultilineCompletion.py
@@ -64,4 +64,3 @@ def test_basic_completion(self):
         self.child.expect_exact("only_local ")
         self.exit_expression_editor()
 
-        self.quit()
diff --git 
a/lldb/test/API/commands/expression/multiline-navigation/TestMultilineNavigation.py
 
b/lldb/test/API/commands/expression/multiline-navigation/TestMultilineNavigation.py
index ce5f2f798bd32..90f05c73ad42d 100644
--- 
a/lldb/test/API/commands/expression/multiline-navigation/TestMultilineNavigation.py
+++ 
b/lldb/test/API/commands/expression/multiline-navigation/TestMultilineNavigation.py
@@ -36,7 +36,6 @@ def test_nav_arrow_up(self):
         # and not 123 (the one we initially typed).
         self.child.expect_exact("(int) $0 = 124")
 
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -68,7 +67,6 @@ def test_nav_arrow_down(self):
         # us back to the second line.
         self.child.expect_exact("(int) $0 = 334")
 
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -101,4 +99,3 @@ def test_nav_arrow_up_empty(self):
         self.child.send("\n\n")
         self.expect_prompt()
 
-        self.quit()
diff --git a/lldb/test/API/commands/gui/basic/TestGuiBasic.py 
b/lldb/test/API/commands/gui/basic/TestGuiBasic.py
index 0c864e6b5f2bf..842121e19b854 100644
--- a/lldb/test/API/commands/gui/basic/TestGuiBasic.py
+++ b/lldb/test/API/commands/gui/basic/TestGuiBasic.py
@@ -51,4 +51,3 @@ def test_gui(self):
         self.child.send(escape_key)
 
         self.expect_prompt()
-        self.quit()
diff --git a/lldb/test/API/commands/gui/basicdebug/TestGuiBasicDebug.py 
b/lldb/test/API/commands/gui/basicdebug/TestGuiBasicDebug.py
index 835ddeb97a5f3..ecb7556eefeeb 100644
--- a/lldb/test/API/commands/gui/basicdebug/TestGuiBasicDebug.py
+++ b/lldb/test/API/commands/gui/basicdebug/TestGuiBasicDebug.py
@@ -58,4 +58,3 @@ def test_gui(self):
         self.child.send(escape_key)
 
         self.expect_prompt()
-        self.quit()
diff --git a/lldb/test/API/commands/gui/breakpoints/TestGuiBreakpoints.py 
b/lldb/test/API/commands/gui/breakpoints/TestGuiBreakpoints.py
index ebc01880a294c..2d76e7dc5caa5 100644
--- a/lldb/test/API/commands/gui/breakpoints/TestGuiBreakpoints.py
+++ b/lldb/test/API/commands/gui/breakpoints/TestGuiBreakpoints.py
@@ -72,4 +72,3 @@ def test_gui(self):
         self.child.send(escape_key)
 
         self.expect_prompt()
-        self.quit()
diff --git 
a/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py 
b/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py
index 266601bafe7ad..aeecc790f554f 100644
--- a/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py
+++ b/lldb/test/API/commands/gui/expand-threads-tree/TestGuiExpandThreadsTree.py
@@ -55,4 +55,3 @@ def test_gui(self):
         self.child.send(escape_key)
 
         self.expect_prompt()
-        self.quit()
diff --git a/lldb/test/API/commands/gui/spawn-threads/TestGuiSpawnThreads.py 
b/lldb/test/API/commands/gui/spawn-threads/TestGuiSpawnThreads.py
index c504abe8b5ef2..3592b502e9845 100644
--- a/lldb/test/API/commands/gui/spawn-threads/TestGuiSpawnThreads.py
+++ b/lldb/test/API/commands/gui/spawn-threads/TestGuiSpawnThreads.py
@@ -48,4 +48,3 @@ def test_gui(self):
         self.child.send(escape_key)
         self.expect_prompt()
 
-        self.quit()
diff --git a/lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py 
b/lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py
index 8dbf5e8cdcda5..a144f2d9ab2b6 100644
--- a/lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py
+++ b/lldb/test/API/commands/gui/viewlarge/TestGuiViewLarge.py
@@ -75,4 +75,3 @@ def test_gui(self):
         self.child.send(escape_key)
 
         self.expect_prompt()
-        self.quit()
diff --git a/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py 
b/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
index 0f714a156e50f..401b06f6c9b49 100644
--- a/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
+++ b/lldb/test/API/iohandler/autosuggestion/TestAutosuggestion.py
@@ -46,7 +46,6 @@ def test_autosuggestion_add_spaces(self):
             + " "
         )
 
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -132,7 +131,6 @@ def test_autosuggestion(self):
         self.child.send(ctrl_f + "help breakpoint" + "\n")
         self.child.expect_exact(breakpoint_output_needle)
 
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing
@@ -156,4 +154,3 @@ def test_autosuggestion_custom_ansi_prefix_suffix(self):
         self.child.expect_exact(self.ANSI_RED + "ame variable" + 
self.ANSI_CYAN)
         self.child.send("\n")
 
-        self.quit()
diff --git a/lldb/test/API/iohandler/completion/TestIOHandlerCompletion.py 
b/lldb/test/API/iohandler/completion/TestIOHandlerCompletion.py
index 6128393558f9a..43eb2d752ec70 100644
--- a/lldb/test/API/iohandler/completion/TestIOHandlerCompletion.py
+++ b/lldb/test/API/iohandler/completion/TestIOHandlerCompletion.py
@@ -85,4 +85,3 @@ def test_completion(self):
         self.child.send("regoinvalid\t")
         self.expect_prompt()
 
-        self.quit()
diff --git a/lldb/test/API/iohandler/resize/TestIOHandlerResize.py 
b/lldb/test/API/iohandler/resize/TestIOHandlerResize.py
index 84a8ab9f4d417..3f0c6a91298b7 100644
--- a/lldb/test/API/iohandler/resize/TestIOHandlerResize.py
+++ b/lldb/test/API/iohandler/resize/TestIOHandlerResize.py
@@ -33,4 +33,3 @@ def test_resize(self):
         self.child.expect_exact(
             "(lldb) This is a long sentence missing its first letter."
         )
-        self.quit()
diff --git a/lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py 
b/lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py
index 63bda4b4871ad..1532d7942dfe3 100644
--- a/lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py
+++ b/lldb/test/API/iohandler/sigint/TestIOHandlerPythonREPLSigint.py
@@ -49,7 +49,6 @@ def test_while_evaluating_code(self):
         # Send EOF to quit the Python REPL.
         self.child.sendeof()
 
-        self.quit()
 
     # PExpect uses many timeouts internally and doesn't play well
     # under ASAN on a loaded machine..
@@ -71,4 +70,3 @@ def test_while_waiting_on_input(self):
         # Send EOF to quit the Python REPL.
         self.child.sendeof()
 
-        self.quit()
diff --git a/lldb/test/API/iohandler/sigint/TestProcessIOHandlerInterrupt.py 
b/lldb/test/API/iohandler/sigint/TestProcessIOHandlerInterrupt.py
index 7ceb2d5ac38dd..7ae122db1614c 100644
--- a/lldb/test/API/iohandler/sigint/TestProcessIOHandlerInterrupt.py
+++ b/lldb/test/API/iohandler/sigint/TestProcessIOHandlerInterrupt.py
@@ -42,4 +42,3 @@ def test(self):
         self.child.expect("Process .* exited")
         self.expect_prompt()
 
-        self.quit()
diff --git a/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py 
b/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py
index 27299f9cd6961..a63cde5d1000b 100644
--- a/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py
+++ b/lldb/test/API/iohandler/stdio/TestIOHandlerProcessSTDIO.py
@@ -26,4 +26,3 @@ def test(self):
         self.child.expect_exact("stdout: baz")
 
         self.child.sendcontrol("d")
-        self.quit()
diff --git a/lldb/test/API/iohandler/unicode/TestUnicode.py 
b/lldb/test/API/iohandler/unicode/TestUnicode.py
index c9bb1c2af0bd0..903744fb77681 100644
--- a/lldb/test/API/iohandler/unicode/TestUnicode.py
+++ b/lldb/test/API/iohandler/unicode/TestUnicode.py
@@ -27,4 +27,3 @@ def test_unicode_input(self):
             substrs=["error: '\u1234' is not a valid command".encode("utf-8")],
         )
 
-        self.quit()
diff --git a/lldb/test/API/repl/clang/TestClangREPL.py 
b/lldb/test/API/repl/clang/TestClangREPL.py
index 97d91d1b8f7a5..8189e2db6124e 100644
--- a/lldb/test/API/repl/clang/TestClangREPL.py
+++ b/lldb/test/API/repl/clang/TestClangREPL.py
@@ -54,7 +54,6 @@ def test_basic_completion(self):
         # Try using the persistent variable from before.
         self.expect_repl("$persistent + 10", substrs=["(long) $2 = 17"])
 
-        self.quit()
 
     # PExpect uses many timeouts internally and doesn't play well
     # under ASAN on a loaded machine..
diff --git a/lldb/test/API/terminal/TestDisabledBreakpoints.py 
b/lldb/test/API/terminal/TestDisabledBreakpoints.py
index a644c94c8a178..c29a220439e45 100644
--- a/lldb/test/API/terminal/TestDisabledBreakpoints.py
+++ b/lldb/test/API/terminal/TestDisabledBreakpoints.py
@@ -22,4 +22,3 @@ def test_disabling_breakpoints_with_color(self):
         self.expect("b main")
         self.expect("br dis")
         self.expect("br l", substrs=[ansi_red_color_code + "1:"])
-        self.quit()
diff --git a/lldb/test/API/terminal/TestEditline.py 
b/lldb/test/API/terminal/TestEditline.py
index 4696b1e1b112e..91ec1c51719e2 100644
--- a/lldb/test/API/terminal/TestEditline.py
+++ b/lldb/test/API/terminal/TestEditline.py
@@ -42,7 +42,6 @@ def test_left_right_arrow(self):
                 substrs=["Syntax: print"],
             )
 
-        self.quit()
 
     @skipIfAsan
     @skipIfEditlineSupportMissing

>From b74637f51b422818315c95e68e82a438d34d60ad Mon Sep 17 00:00:00 2001
From: Raphael Isemann <[email protected]>
Date: Tue, 2 Jun 2026 12:45:58 +0100
Subject: [PATCH 2/2] [lldb][test] Faster shut down for pexpect tests

Our pexpect tests spend most of their time in the shutdown logic
waiting for the test child to shut down. For example, our editline
tests spend about 95% of their 40s runtime just waiting for the
pexpect child to terminate.

One of the reasons is that the ptyprocess terminate approach
uses a timeout to give the child time to shut down and be cleaned
up by the kernel. While this timeout makes sense, our timeout is
extremely long (6s) since 56fb7456950d2564d16500e40c5719c954a6987a .

Because the default ptyprocess implementation is designed for very
short timeouts (0.1s), it just sleeps and then checks the process
status. For our long timeout, the child most likely already terminated
way before the timeout on a fast system. However, because we have
some very slow builders, we cannot reduce this timeout without
making tests flaky again.

This patch replaces the default cleanup procedure with a custom one
that polls the child status after each attempt to shut it down. If
we find that the child is gone during polling, we immediately exit.

This patch reduces the time the pexpect tests significantly. Each
editline test for example now runs in 2 seconds on my machine compared
to 40s before. Also, all editline tests I'm aware of are now no longer
in the top 20 slowest test ranking on my setup.
---
 .../Python/lldbsuite/test/lldbpexpect.py      | 79 +++++++++++++++++--
 1 file changed, 74 insertions(+), 5 deletions(-)

diff --git a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py 
b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
index 60b4f7f5d4a4b..7b48def504809 100644
--- a/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
+++ b/lldb/packages/Python/lldbsuite/test/lldbpexpect.py
@@ -1,6 +1,8 @@
 # System modules
 import os
 import sys
+import time
+import signal
 
 # LLDB Modules
 import lldb
@@ -72,8 +74,14 @@ def launch(
             env=env,
             encoding=encoding,
         )
-        self.child.ptyproc.delayafterclose = timeout / 10
-        self.child.ptyproc.delayafterterminate = timeout / 10
+
+        # The interval in which we poll the LLDB process if we are
+        # closing/terminating the process.
+        self.polling_interval = 0.05
+        # Timeout for each attempt to shut down LLDB at the end
+        # of the test. Note that there can be multiple attempts
+        # to shut down LLDB.
+        self.shutdown_timeout = timeout / 10
 
         if post_spawn is not None:
             post_spawn()
@@ -104,16 +112,77 @@ def tearDown(self):
         # Ensure the child is always cleaned up, even if the test didn't call
         # quit() explicitly or failed before reaching it.
         if self.child is not None:
-            self.quit()
+            self.quit(gracefully=True)
         super().tearDown()
 
+    def _poll_until_dead(self, proc):
+        """Poll proc.isalive() for up to shutdown_timeout seconds.
+        Returns True if the process exited within that window."""
+        deadline = time.monotonic() + self.shutdown_timeout
+        while time.monotonic() < deadline:
+            if not proc.isalive():
+                return True
+            time.sleep(self.polling_interval)
+        return not proc.isalive()
+
+    def _terminate_child(self, force=False):
+        """Terminate the child using signals."""
+        proc = self.child.ptyproc
+
+        if not proc.isalive():
+            return True
+        try:
+            # List of signals to try, starting from a friendly
+            # SIGHUP, SIGINT to SIGKILL if all else fails.
+            signals = [signal.SIGHUP, signal.SIGINT]
+            if force:
+                signals.append(signal.SIGKILL)
+            for sig in signals:
+                proc.kill(sig)
+                if self._poll_until_dead(proc):
+                    return True
+            return False
+        except OSError:
+            # This handles a TOCTOU issue where we think the process is
+            # considered as 'isalive', then is actually killed by the
+            # kernel and then we try to kill it.
+            time.sleep(self.polling_interval)
+            return not proc.isalive()
+
+    def _close_child(self, force=False):
+        """Close the connection to the child and terminate it if needed."""
+
+        ptyproc = self.child.ptyproc
+        if ptyproc.closed:
+            return
+
+        # Close the PTY master fd first.
+        try:
+            self.child.sendeof()
+            ptyproc.fileobj.close()
+        except OSError:
+            # This could happen if LLDB shut down at the same time.
+            pass
+
+        if not force:
+            # Poll briefly for the process to respond to the EOF before
+            # escalating to signals.
+            self._poll_until_dead(ptyproc)
+
+        if ptyproc.isalive():
+            # Kill the child with signals. This should always succeed.
+            assert self._terminate_child(force=force)
+
+        # Make sure the object is now marked as closed.
+        ptyproc.fd = -1
+        ptyproc.closed = True
+
     def quit(self, gracefully=True):
         if self.child is None:
             return
 
         try:
-            self.child.sendeof()
-            self.child.close(force=not gracefully)
+            self._close_child(force=not gracefully)
         except OSError:
             # This can happen if LLDB quit itself because it is running in
             # batch mode.

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to