205 lines · python
1import lldb2import os3import re4import socket5import time6 7from contextlib import closing8from lldbsuite.test.decorators import *9from lldbsuite.test.lldbtest import *10from lldbsuite.test.lldbpexpect import PExpectTest11from lldbgdbserverutils import get_lldb_server_exe12 13 14# PExpect uses many timeouts internally and doesn't play well15# under ASAN on a loaded machine..16@skipIfAsan17class TestStatusline(PExpectTest):18 # Change this value to something smaller to make debugging this test less19 # tedious.20 TIMEOUT = 6021 22 TERMINAL_HEIGHT = 1023 TERMINAL_WIDTH = 6024 25 def do_setup(self):26 # Create a target and run to a breakpoint.27 exe = self.getBuildArtifact("a.out")28 self.expect(29 "target create {}".format(exe), substrs=["Current executable set to"]30 )31 self.expect('breakpoint set -p "Break here"', substrs=["Breakpoint 1"])32 self.expect("run", substrs=["stop reason"])33 self.resize()34 35 def resize(self, height=None, width=None):36 height = self.TERMINAL_HEIGHT if not height else height37 width = self.TERMINAL_WIDTH if not width else width38 # Change the terminal dimensions. When we launch the tests, we reset39 # all the settings, leaving the terminal dimensions unset.40 self.child.setwinsize(height, width)41 42 def test(self):43 """Basic test for the statusline."""44 self.build()45 self.launch(timeout=self.TIMEOUT)46 self.do_setup()47 48 # Enable the statusline and check for the control character and that we49 # can see the target, the location and the stop reason.50 self.expect('set set separator "| "')51 self.expect(52 "set set show-statusline true",53 [54 "\x1b[1;{}r".format(self.TERMINAL_HEIGHT - 1),55 "a.out | main.c:2:11 | breakpoint 1.1 ",56 ],57 )58 59 # Change the terminal dimensions and make sure it's reflected immediately.60 self.child.setwinsize(self.TERMINAL_HEIGHT, 25)61 self.child.expect(re.escape("a.out | main.c:2:11 | bre"))62 self.child.setwinsize(self.TERMINAL_HEIGHT, self.TERMINAL_WIDTH)63 64 # Change the separator.65 self.expect('set set separator "S "', ["a.out S main.c:2:11"])66 67 # Change the format.68 self.expect(69 'set set statusline-format "target = {${target.file.basename}} ${separator}"',70 ["target = a.out S"],71 )72 self.expect('set set separator "| "')73 74 # Hide the statusline and check for the control character.75 self.expect(76 "set set show-statusline false", ["\x1b[1;{}r".format(self.TERMINAL_HEIGHT)]77 )78 79 def test_no_color(self):80 """Basic test for the statusline with colors disabled."""81 self.build()82 self.launch(use_colors=False, timeout=self.TIMEOUT)83 self.do_setup()84 85 # Enable the statusline and check for the "reverse video" control character.86 self.expect(87 "set set show-statusline true",88 [89 "\x1b[7m",90 ],91 )92 93 def test_deadlock(self):94 """Regression test for lock inversion between the statusline mutex and95 the output mutex."""96 self.build()97 self.launch(98 extra_args=["-o", "settings set use-color false"], timeout=self.TIMEOUT99 )100 self.child.expect("(lldb)")101 self.resize()102 103 exe = self.getBuildArtifact("a.out")104 105 self.expect("file {}".format(exe), ["Current executable"])106 self.expect("help", ["Debugger commands"])107 108 def test_no_target(self):109 """Test that we print "no target" when launched without a target."""110 self.launch(timeout=self.TIMEOUT)111 self.resize()112 113 self.expect("set set show-statusline true", ["no target"])114 115 @skipIfEditlineSupportMissing116 def test_resize(self):117 """Test that move the cursor when resizing."""118 self.launch(timeout=self.TIMEOUT)119 self.resize()120 self.expect("set set show-statusline true", ["no target"])121 self.resize(20, 60)122 # Check for the escape code to resize the scroll window.123 self.child.expect(re.escape("\x1b[1;19r"))124 self.child.expect("(lldb)")125 126 @skipIfRemote127 @skipIfWindows128 @skipIfDarwin129 @skipIfLinux # https://github.com/llvm/llvm-project/issues/154763130 @add_test_categories(["lldb-server"])131 def test_modulelist_deadlock(self):132 """Regression test for a deadlock that occurs when the status line is enabled before connecting133 to a gdb-remote server.134 """135 if get_lldb_server_exe() is None:136 self.skipTest("lldb-server not found")137 138 MAX_RETRY_ATTEMPTS = 10139 DELAY = 0.25140 141 def _find_free_port(host):142 for attempt in range(MAX_RETRY_ATTEMPTS):143 try:144 family, type, proto, _, _ = socket.getaddrinfo(145 host, 0, proto=socket.IPPROTO_TCP146 )[0]147 with closing(socket.socket(family, type, proto)) as sock:148 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)149 sock.bind((host, 0))150 return sock.getsockname()151 except OSError:152 time.sleep(DELAY * 2**attempt) # Exponential backoff153 raise RuntimeError(154 "Could not find a free port on {} after {} attempts.".format(155 host, MAX_RETRY_ATTEMPTS156 )157 )158 159 def _wait_for_server_ready_in_log(log_file_path, ready_message):160 """Check log file for server ready message with retry logic"""161 for attempt in range(MAX_RETRY_ATTEMPTS):162 if os.path.exists(log_file_path):163 with open(log_file_path, "r") as f:164 if ready_message in f.read():165 return166 time.sleep(pow(2, attempt) * DELAY)167 raise RuntimeError(168 "Server not ready after {} attempts.".format(MAX_RETRY_ATTEMPTS)169 )170 171 self.build()172 exe_path = self.getBuildArtifact("a.out")173 server_log_file = self.getLogBasenameForCurrentTest() + "-lldbserver.log"174 self.addTearDownHook(175 lambda: os.path.exists(server_log_file) and os.remove(server_log_file)176 )177 178 # Find a free port for the server179 addr = _find_free_port("localhost")180 connect_address = "[{}]:{}".format(*addr)181 commandline_args = [182 "gdbserver",183 connect_address,184 exe_path,185 "--log-file={}".format(server_log_file),186 "--log-channels=lldb conn",187 ]188 189 server_proc = self.spawnSubprocess(190 get_lldb_server_exe(), commandline_args, install_remote=False191 )192 self.assertIsNotNone(server_proc)193 194 # Wait for server to be ready by checking log file.195 server_ready_message = "Listen to {}".format(connect_address)196 _wait_for_server_ready_in_log(server_log_file, server_ready_message)197 198 # Launch LLDB client and connect to lldb-server with statusline enabled199 self.launch(timeout=self.TIMEOUT)200 self.resize()201 self.expect("settings set show-statusline true", ["no target"])202 self.expect(203 f"gdb-remote {connect_address}", [b"a.out \xe2\x94\x82 signal SIGSTOP"]204 )205