xref: /llvm-project/lldb/test/API/source-manager/TestSourceManager.py (revision 841be9854c496adb1944fbf33af055814366ec86)
1"""
2Test lldb core component: SourceManager.
3
4Test cases:
5
6o test_display_source_python:
7  Test display of source using the SBSourceManager API.
8o test_modify_source_file_while_debugging:
9  Test the caching mechanism of the source manager.
10"""
11
12from __future__ import print_function
13
14import lldb
15from lldbsuite.test.decorators import *
16from lldbsuite.test.lldbtest import *
17from lldbsuite.test import lldbutil
18
19
20def ansi_underline_surround_regex(inner_regex_text):
21    # return re.compile(r"\[4m%s\[0m" % inner_regex_text)
22    return "4.+\033\\[4m%s\033\\[0m" % inner_regex_text
23
24def ansi_color_surround_regex(inner_regex_text):
25    return "\033\\[3[0-7]m%s\033\\[0m" % inner_regex_text
26
27class SourceManagerTestCase(TestBase):
28
29    mydir = TestBase.compute_mydir(__file__)
30
31    NO_DEBUG_INFO_TESTCASE = True
32
33    def setUp(self):
34        # Call super's setUp().
35        TestBase.setUp(self)
36        # Find the line number to break inside main().
37        self.file = self.getBuildArtifact("main-copy.c")
38        self.line = line_number("main.c", '// Set break point at this line.')
39
40    def get_expected_stop_column_number(self):
41        """Return the 1-based column number of the first non-whitespace
42        character in the breakpoint source line."""
43        stop_line = get_line(self.file, self.line)
44        # The number of spaces that must be skipped to get to the first non-
45        # whitespace character --- where we expect the debugger breakpoint
46        # column to be --- is equal to the number of characters that get
47        # stripped off the front when we lstrip it, plus one to specify
48        # the character column after the initial whitespace.
49        return len(stop_line) - len(stop_line.lstrip()) + 1
50
51    def do_display_source_python_api(self, use_color, needle_regex, highlight_source=False):
52        self.build()
53        exe = self.getBuildArtifact("a.out")
54        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
55
56        target = self.dbg.CreateTarget(exe)
57        self.assertTrue(target, VALID_TARGET)
58
59        # Launch the process, and do not stop at the entry point.
60        args = None
61        envp = None
62        process = target.LaunchSimple(
63            args, envp, self.get_process_working_directory())
64        self.assertIsNotNone(process)
65
66        #
67        # Exercise Python APIs to display source lines.
68        #
69
70        # Setup whether we should use ansi escape sequences, including color
71        # and styles such as underline.
72        self.dbg.SetUseColor(use_color)
73        # Disable syntax highlighting if needed.
74
75        self.runCmd("settings set highlight-source " + str(highlight_source).lower())
76
77        filespec = lldb.SBFileSpec(self.file, False)
78        source_mgr = self.dbg.GetSourceManager()
79        # Use a string stream as the destination.
80        stream = lldb.SBStream()
81        column = self.get_expected_stop_column_number()
82        context_before = 2
83        context_after = 2
84        current_line_prefix = "=>"
85        source_mgr.DisplaySourceLinesWithLineNumbersAndColumn(
86            filespec, self.line, column, context_before, context_after,
87            current_line_prefix, stream)
88
89        #    2
90        #    3    int main(int argc, char const *argv[]) {
91        # => 4        printf("Hello world.\n"); // Set break point at this line.
92        #    5        return 0;
93        #    6    }
94        self.expect(stream.GetData(), "Source code displayed correctly:\n" + stream.GetData(),
95                    exe=False,
96                    patterns=['=>', '%d.*Hello world' % self.line,
97                              needle_regex])
98
99        # Boundary condition testings for SBStream().  LLDB should not crash!
100        stream.Print(None)
101        stream.RedirectToFile(None, True)
102
103    @add_test_categories(['pyapi'])
104    def test_display_source_python_dumb_terminal(self):
105        """Test display of source using the SBSourceManager API, using a
106        dumb terminal and thus no color support (the default)."""
107        use_color = False
108        self.do_display_source_python_api(use_color, r"\s+\^")
109
110    @add_test_categories(['pyapi'])
111    def test_display_source_python_ansi_terminal(self):
112        """Test display of source using the SBSourceManager API, using a
113        dumb terminal and thus no color support (the default)."""
114        use_color = True
115        underline_regex = ansi_underline_surround_regex(r"printf")
116        self.do_display_source_python_api(use_color, underline_regex)
117
118    @add_test_categories(['pyapi'])
119    def test_display_source_python_ansi_terminal_syntax_highlighting(self):
120        """Test display of source using the SBSourceManager API and check for
121        the syntax highlighted output"""
122        use_color = True
123        syntax_highlighting = True;
124
125        # Just pick 'int' as something that should be colored.
126        color_regex = ansi_color_surround_regex("int")
127        self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)
128
129        # Same for 'char'.
130        color_regex = ansi_color_surround_regex("char")
131        self.do_display_source_python_api(use_color, color_regex, syntax_highlighting)
132
133        # Test that we didn't color unrelated identifiers.
134        self.do_display_source_python_api(use_color, r" main\(", syntax_highlighting)
135        self.do_display_source_python_api(use_color, r"\);", syntax_highlighting)
136
137    def test_move_and_then_display_source(self):
138        """Test that target.source-map settings work by moving main.c to hidden/main.c."""
139        self.build()
140        exe = self.getBuildArtifact("a.out")
141        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
142
143        # Move main.c to hidden/main.c.
144        hidden = self.getBuildArtifact("hidden")
145        lldbutil.mkdir_p(hidden)
146        main_c_hidden = os.path.join(hidden, "main-copy.c")
147        os.rename(self.file, main_c_hidden)
148
149        if self.TraceOn():
150            system([["ls"]])
151            system([["ls", "hidden"]])
152
153        # Set source remapping with invalid replace path and verify we get an
154        # error
155        self.expect(
156            "settings set target.source-map /a/b/c/d/e /q/r/s/t/u",
157            error=True,
158            substrs=['''error: the replacement path doesn't exist: "/q/r/s/t/u"'''])
159
160        # 'make -C' has resolved current directory to its realpath form.
161        builddir_real = os.path.realpath(self.getBuildDir())
162        hidden_real = os.path.realpath(hidden)
163        # Set target.source-map settings.
164        self.runCmd("settings set target.source-map %s %s" %
165                    (builddir_real, hidden_real))
166        # And verify that the settings work.
167        self.expect("settings show target.source-map",
168                    substrs=[builddir_real, hidden_real])
169
170        # Display main() and verify that the source mapping has been kicked in.
171        self.expect("source list -n main", SOURCE_DISPLAYED_CORRECTLY,
172                    substrs=['Hello world'])
173
174    @skipIf(oslist=["windows"], bugnumber="llvm.org/pr44431")
175    def test_modify_source_file_while_debugging(self):
176        """Modify a source file while debugging the executable."""
177        self.build()
178        exe = self.getBuildArtifact("a.out")
179        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
180
181        lldbutil.run_break_set_by_file_and_line(
182            self, "main-copy.c", self.line, num_expected_locations=1, loc_exact=True)
183
184        self.runCmd("run", RUN_SUCCEEDED)
185
186        # The stop reason of the thread should be breakpoint.
187        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
188                    substrs=['stopped',
189                             'main-copy.c:%d' % self.line,
190                             'stop reason = breakpoint'])
191
192        # Display some source code.
193        self.expect(
194            "source list -f main-copy.c -l %d" %
195            self.line,
196            SOURCE_DISPLAYED_CORRECTLY,
197            substrs=['Hello world'])
198
199        # The '-b' option shows the line table locations from the debug information
200        # that indicates valid places to set source level breakpoints.
201
202        # The file to display is implicit in this case.
203        self.runCmd("source list -l %d -c 3 -b" % self.line)
204        output = self.res.GetOutput().splitlines()[0]
205
206        # If the breakpoint set command succeeded, we should expect a positive number
207        # of breakpoints for the current line, i.e., self.line.
208        import re
209        m = re.search('^\[(\d+)\].*// Set break point at this line.', output)
210        if not m:
211            self.fail("Fail to display source level breakpoints")
212        self.assertTrue(int(m.group(1)) > 0)
213
214        # Read the main.c file content.
215        with io.open(self.file, 'r', newline='\n') as f:
216            original_content = f.read()
217            if self.TraceOn():
218                print("original content:", original_content)
219
220        # Modify the in-memory copy of the original source code.
221        new_content = original_content.replace('Hello world', 'Hello lldb', 1)
222
223        # Modify the source code file.
224        with io.open(self.file, 'w', newline='\n') as f:
225            time.sleep(1)
226            f.write(new_content)
227            if self.TraceOn():
228                print("new content:", new_content)
229                print(
230                    "os.path.getmtime() after writing new content:",
231                    os.path.getmtime(self.file))
232
233        # Display the source code again.  We should see the updated line.
234        self.expect(
235            "source list -f main-copy.c -l %d" %
236            self.line,
237            SOURCE_DISPLAYED_CORRECTLY,
238            substrs=['Hello lldb'])
239
240    @expectedFailureAll(oslist=["windows"], bugnumber="llvm.org/pr44432")
241    def test_set_breakpoint_with_absolute_path(self):
242        self.build()
243        hidden = self.getBuildArtifact("hidden")
244        lldbutil.mkdir_p(hidden)
245        # 'make -C' has resolved current directory to its realpath form.
246        builddir_real = os.path.realpath(self.getBuildDir())
247        hidden_real = os.path.realpath(hidden)
248        self.runCmd("settings set target.source-map %s %s" %
249                    (builddir_real, hidden_real))
250
251        exe = self.getBuildArtifact("a.out")
252        main = os.path.join(builddir_real, "hidden", "main-copy.c")
253        self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
254
255        lldbutil.run_break_set_by_file_and_line(
256            self, main, self.line, num_expected_locations=1, loc_exact=False)
257
258        self.runCmd("run", RUN_SUCCEEDED)
259
260        # The stop reason of the thread should be breakpoint.
261        self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
262                    substrs=['stopped',
263                             'main-copy.c:%d' % self.line,
264                             'stop reason = breakpoint'])
265