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