xref: /llvm-project/lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py (revision d4c17891126a79ae49237a7de0f9948aeedcd177)
1"""
2Test lldb-dap setBreakpoints request
3"""
4
5import dap_server
6from lldbsuite.test.decorators import *
7from lldbsuite.test.lldbtest import *
8from lldbsuite.test import lldbutil
9import lldbdap_testcase
10import time
11import os
12import re
13
14
15class TestDAP_launch(lldbdap_testcase.DAPTestCaseBase):
16    def test_default(self):
17        """
18        Tests the default launch of a simple program. No arguments,
19        environment, or anything else is specified.
20        """
21        program = self.getBuildArtifact("a.out")
22        self.build_and_launch(program)
23        self.continue_to_exit()
24        # Now get the STDOUT and verify our program argument is correct
25        output = self.get_stdout()
26        self.assertTrue(output and len(output) > 0, "expect program output")
27        lines = output.splitlines()
28        self.assertIn(program, lines[0], "make sure program path is in first argument")
29
30    def test_termination(self):
31        """
32        Tests the correct termination of lldb-dap upon a 'disconnect'
33        request.
34        """
35        self.create_debug_adaptor()
36        # The underlying lldb-dap process must be alive
37        self.assertEqual(self.dap_server.process.poll(), None)
38
39        # The lldb-dap process should finish even though
40        # we didn't close the communication socket explicitly
41        self.dap_server.request_disconnect()
42
43        # Wait until the underlying lldb-dap process dies.
44        self.dap_server.process.wait(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
45
46        # Check the return code
47        self.assertEqual(self.dap_server.process.poll(), 0)
48
49    def test_stopOnEntry(self):
50        """
51        Tests the default launch of a simple program that stops at the
52        entry point instead of continuing.
53        """
54        program = self.getBuildArtifact("a.out")
55        self.build_and_launch(program, stopOnEntry=True)
56        self.set_function_breakpoints(["main"])
57        stopped_events = self.continue_to_next_stop()
58        for stopped_event in stopped_events:
59            if "body" in stopped_event:
60                body = stopped_event["body"]
61                if "reason" in body:
62                    reason = body["reason"]
63                    self.assertNotEqual(
64                        reason, "breakpoint", 'verify stop isn\'t "main" breakpoint'
65                    )
66
67    def test_cwd(self):
68        """
69        Tests the default launch of a simple program with a current working
70        directory.
71        """
72        program = self.getBuildArtifact("a.out")
73        program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))
74        self.build_and_launch(program, cwd=program_parent_dir)
75        self.continue_to_exit()
76        # Now get the STDOUT and verify our program argument is correct
77        output = self.get_stdout()
78        self.assertTrue(output and len(output) > 0, "expect program output")
79        lines = output.splitlines()
80        found = False
81        for line in lines:
82            if line.startswith('cwd = "'):
83                quote_path = '"%s"' % (program_parent_dir)
84                found = True
85                self.assertIn(
86                    quote_path,
87                    line,
88                    "working directory '%s' not in '%s'" % (program_parent_dir, line),
89                )
90        self.assertTrue(found, "verified program working directory")
91
92    def test_debuggerRoot(self):
93        """
94        Tests the "debuggerRoot" will change the working directory of
95        the lldb-dap debug adaptor.
96        """
97        program = self.getBuildArtifact("a.out")
98        program_parent_dir = os.path.realpath(os.path.dirname(os.path.dirname(program)))
99
100        var = "%cd%" if lldbplatformutil.getHostPlatform() == "windows" else "$PWD"
101        commands = [f"platform shell echo cwd = {var}"]
102
103        self.build_and_launch(
104            program, debuggerRoot=program_parent_dir, initCommands=commands
105        )
106        output = self.get_console()
107        self.assertTrue(output and len(output) > 0, "expect console output")
108        lines = output.splitlines()
109        prefix = "cwd = "
110        found = False
111        for line in lines:
112            if line.startswith(prefix):
113                found = True
114                self.assertEqual(
115                    program_parent_dir,
116                    line.strip()[len(prefix) :],
117                    "lldb-dap working dir '%s' == '%s'"
118                    % (program_parent_dir, line[len(prefix) :]),
119                )
120        self.assertTrue(found, "verified lldb-dap working directory")
121        self.continue_to_exit()
122
123    def test_sourcePath(self):
124        """
125        Tests the "sourcePath" will set the target.source-map.
126        """
127        program = self.getBuildArtifact("a.out")
128        program_dir = os.path.dirname(program)
129        self.build_and_launch(program, sourcePath=program_dir)
130        output = self.get_console()
131        self.assertTrue(output and len(output) > 0, "expect console output")
132        lines = output.splitlines()
133        prefix = '(lldb) settings set target.source-map "." '
134        found = False
135        for line in lines:
136            if line.startswith(prefix):
137                found = True
138                quoted_path = '"%s"' % (program_dir)
139                self.assertEqual(
140                    quoted_path,
141                    line[len(prefix) :],
142                    "lldb-dap working dir %s == %s" % (quoted_path, line[6:]),
143                )
144        self.assertTrue(found, 'found "sourcePath" in console output')
145        self.continue_to_exit()
146
147    def test_disableSTDIO(self):
148        """
149        Tests the default launch of a simple program with STDIO disabled.
150        """
151        program = self.getBuildArtifact("a.out")
152        self.build_and_launch(program, disableSTDIO=True)
153        self.continue_to_exit()
154        # Now get the STDOUT and verify our program argument is correct
155        output = self.get_stdout()
156        self.assertEqual(output, None, "expect no program output")
157
158    @skipIfWindows
159    @skipIfLinux  # shell argument expansion doesn't seem to work on Linux
160    @expectedFailureAll(oslist=["freebsd", "netbsd"], bugnumber="llvm.org/pr48349")
161    def test_shellExpandArguments_enabled(self):
162        """
163        Tests the default launch of a simple program with shell expansion
164        enabled.
165        """
166        program = self.getBuildArtifact("a.out")
167        program_dir = os.path.dirname(program)
168        glob = os.path.join(program_dir, "*.out")
169        self.build_and_launch(program, args=[glob], shellExpandArguments=True)
170        self.continue_to_exit()
171        # Now get the STDOUT and verify our program argument is correct
172        output = self.get_stdout()
173        self.assertTrue(output and len(output) > 0, "expect no program output")
174        lines = output.splitlines()
175        for line in lines:
176            quote_path = '"%s"' % (program)
177            if line.startswith("arg[1] ="):
178                self.assertIn(
179                    quote_path, line, 'verify "%s" expanded to "%s"' % (glob, program)
180                )
181
182    def test_shellExpandArguments_disabled(self):
183        """
184        Tests the default launch of a simple program with shell expansion
185        disabled.
186        """
187        program = self.getBuildArtifact("a.out")
188        program_dir = os.path.dirname(program)
189        glob = os.path.join(program_dir, "*.out")
190        self.build_and_launch(program, args=[glob], shellExpandArguments=False)
191        self.continue_to_exit()
192        # Now get the STDOUT and verify our program argument is correct
193        output = self.get_stdout()
194        self.assertTrue(output and len(output) > 0, "expect no program output")
195        lines = output.splitlines()
196        for line in lines:
197            quote_path = '"%s"' % (glob)
198            if line.startswith("arg[1] ="):
199                self.assertIn(
200                    quote_path, line, 'verify "%s" stayed to "%s"' % (glob, glob)
201                )
202
203    def test_args(self):
204        """
205        Tests launch of a simple program with arguments
206        """
207        program = self.getBuildArtifact("a.out")
208        args = ["one", "with space", "'with single quotes'", '"with double quotes"']
209        self.build_and_launch(program, args=args)
210        self.continue_to_exit()
211
212        # Now get the STDOUT and verify our arguments got passed correctly
213        output = self.get_stdout()
214        self.assertTrue(output and len(output) > 0, "expect program output")
215        lines = output.splitlines()
216        # Skip the first argument that contains the program name
217        lines.pop(0)
218        # Make sure arguments we specified are correct
219        for i, arg in enumerate(args):
220            quoted_arg = '"%s"' % (arg)
221            self.assertIn(
222                quoted_arg,
223                lines[i],
224                'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
225            )
226
227    def test_environment_with_object(self):
228        """
229        Tests launch of a simple program with environment variables
230        """
231        program = self.getBuildArtifact("a.out")
232        env = {
233            "NO_VALUE": "",
234            "WITH_VALUE": "BAR",
235            "EMPTY_VALUE": "",
236            "SPACE": "Hello World",
237        }
238
239        self.build_and_launch(program, env=env)
240        self.continue_to_exit()
241
242        # Now get the STDOUT and verify our arguments got passed correctly
243        output = self.get_stdout()
244        self.assertTrue(output and len(output) > 0, "expect program output")
245        lines = output.splitlines()
246        # Skip the all arguments so we have only environment vars left
247        while len(lines) and lines[0].startswith("arg["):
248            lines.pop(0)
249        # Make sure each environment variable in "env" is actually set in the
250        # program environment that was printed to STDOUT
251        for var in env:
252            found = False
253            for program_var in lines:
254                if var in program_var:
255                    found = True
256                    break
257            self.assertTrue(
258                found, '"%s" must exist in program environment (%s)' % (var, lines)
259            )
260
261    def test_environment_with_array(self):
262        """
263        Tests launch of a simple program with environment variables
264        """
265        program = self.getBuildArtifact("a.out")
266        env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", "SPACE=Hello World"]
267
268        self.build_and_launch(program, env=env)
269        self.continue_to_exit()
270
271        # Now get the STDOUT and verify our arguments got passed correctly
272        output = self.get_stdout()
273        self.assertTrue(output and len(output) > 0, "expect program output")
274        lines = output.splitlines()
275        # Skip the all arguments so we have only environment vars left
276        while len(lines) and lines[0].startswith("arg["):
277            lines.pop(0)
278        # Make sure each environment variable in "env" is actually set in the
279        # program environment that was printed to STDOUT
280        for var in env:
281            found = False
282            for program_var in lines:
283                if var in program_var:
284                    found = True
285                    break
286            self.assertTrue(
287                found, '"%s" must exist in program environment (%s)' % (var, lines)
288            )
289
290    @skipIf(
291        archs=["arm", "aarch64"]
292    )  # failed run https://lab.llvm.org/buildbot/#/builders/96/builds/6933
293    def test_commands(self):
294        """
295        Tests the "initCommands", "preRunCommands", "stopCommands",
296        "terminateCommands" and "exitCommands" that can be passed during
297        launch.
298
299        "initCommands" are a list of LLDB commands that get executed
300        before the targt is created.
301        "preRunCommands" are a list of LLDB commands that get executed
302        after the target has been created and before the launch.
303        "stopCommands" are a list of LLDB commands that get executed each
304        time the program stops.
305        "exitCommands" are a list of LLDB commands that get executed when
306        the process exits
307        "terminateCommands" are a list of LLDB commands that get executed when
308        the debugger session terminates.
309        """
310        program = self.getBuildArtifact("a.out")
311        initCommands = ["target list", "platform list"]
312        preRunCommands = ["image list a.out", "image dump sections a.out"]
313        postRunCommands = ["help trace", "help process trace"]
314        stopCommands = ["frame variable", "bt"]
315        exitCommands = ["expr 2+3", "expr 3+4"]
316        terminateCommands = ["expr 4+2"]
317        self.build_and_launch(
318            program,
319            initCommands=initCommands,
320            preRunCommands=preRunCommands,
321            postRunCommands=postRunCommands,
322            stopCommands=stopCommands,
323            exitCommands=exitCommands,
324            terminateCommands=terminateCommands,
325        )
326
327        # Get output from the console. This should contain both the
328        # "initCommands" and the "preRunCommands".
329        output = self.get_console()
330        # Verify all "initCommands" were found in console output
331        self.verify_commands("initCommands", output, initCommands)
332        # Verify all "preRunCommands" were found in console output
333        self.verify_commands("preRunCommands", output, preRunCommands)
334        # Verify all "postRunCommands" were found in console output
335        self.verify_commands("postRunCommands", output, postRunCommands)
336
337        source = "main.c"
338        first_line = line_number(source, "// breakpoint 1")
339        second_line = line_number(source, "// breakpoint 2")
340        lines = [first_line, second_line]
341
342        # Set 2 breakpoints so we can verify that "stopCommands" get run as the
343        # breakpoints get hit
344        breakpoint_ids = self.set_source_breakpoints(source, lines)
345        self.assertEqual(
346            len(breakpoint_ids), len(lines), "expect correct number of breakpoints"
347        )
348
349        # Continue after launch and hit the first breakpoint.
350        # Get output from the console. This should contain both the
351        # "stopCommands" that were run after the first breakpoint was hit
352        self.continue_to_breakpoints(breakpoint_ids)
353        output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
354        self.verify_commands("stopCommands", output, stopCommands)
355
356        # Continue again and hit the second breakpoint.
357        # Get output from the console. This should contain both the
358        # "stopCommands" that were run after the second breakpoint was hit
359        self.continue_to_breakpoints(breakpoint_ids)
360        output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
361        self.verify_commands("stopCommands", output, stopCommands)
362
363        # Continue until the program exits
364        self.continue_to_exit()
365        # Get output from the console. This should contain both the
366        # "exitCommands" that were run after the second breakpoint was hit
367        # and the "terminateCommands" due to the debugging session ending
368        output = self.collect_console(
369            timeout_secs=1.0,
370            pattern=terminateCommands[0],
371        )
372        self.verify_commands("exitCommands", output, exitCommands)
373        self.verify_commands("terminateCommands", output, terminateCommands)
374
375    def test_extra_launch_commands(self):
376        """
377        Tests the "launchCommands" with extra launching settings
378        """
379        self.build_and_create_debug_adaptor()
380        program = self.getBuildArtifact("a.out")
381
382        source = "main.c"
383        first_line = line_number(source, "// breakpoint 1")
384        second_line = line_number(source, "// breakpoint 2")
385        # Set target binary and 2 breakpoints
386        # then we can varify the "launchCommands" get run
387        # also we can verify that "stopCommands" get run as the
388        # breakpoints get hit
389        launchCommands = [
390            'target create "%s"' % (program),
391            "breakpoint s -f main.c -l %d" % first_line,
392            "breakpoint s -f main.c -l %d" % second_line,
393            "process launch --stop-at-entry",
394        ]
395
396        initCommands = ["target list", "platform list"]
397        preRunCommands = ["image list a.out", "image dump sections a.out"]
398        stopCommands = ["frame variable", "bt"]
399        exitCommands = ["expr 2+3", "expr 3+4"]
400        self.launch(
401            program,
402            initCommands=initCommands,
403            preRunCommands=preRunCommands,
404            stopCommands=stopCommands,
405            exitCommands=exitCommands,
406            launchCommands=launchCommands,
407        )
408
409        # Get output from the console. This should contain both the
410        # "initCommands" and the "preRunCommands".
411        output = self.get_console()
412        # Verify all "initCommands" were found in console output
413        self.verify_commands("initCommands", output, initCommands)
414        # Verify all "preRunCommands" were found in console output
415        self.verify_commands("preRunCommands", output, preRunCommands)
416
417        # Verify all "launchCommands" were found in console output
418        # After execution, program should launch
419        self.verify_commands("launchCommands", output, launchCommands)
420        # Verify the "stopCommands" here
421        self.continue_to_next_stop()
422        output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
423        self.verify_commands("stopCommands", output, stopCommands)
424
425        # Continue and hit the second breakpoint.
426        # Get output from the console. This should contain both the
427        # "stopCommands" that were run after the first breakpoint was hit
428        self.continue_to_next_stop()
429        output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
430        self.verify_commands("stopCommands", output, stopCommands)
431
432        # Continue until the program exits
433        self.continue_to_exit()
434        # Get output from the console. This should contain both the
435        # "exitCommands" that were run after the second breakpoint was hit
436        output = self.get_console(timeout=lldbdap_testcase.DAPTestCaseBase.timeoutval)
437        self.verify_commands("exitCommands", output, exitCommands)
438
439    def test_failing_launch_commands(self):
440        """
441        Tests "launchCommands" failures prevents a launch.
442        """
443        self.build_and_create_debug_adaptor()
444        program = self.getBuildArtifact("a.out")
445
446        # Run an invalid launch command, in this case a bad path.
447        bad_path = os.path.join("bad", "path")
448        launchCommands = ['!target create "%s%s"' % (bad_path, program)]
449
450        initCommands = ["target list", "platform list"]
451        preRunCommands = ["image list a.out", "image dump sections a.out"]
452        response = self.launch(
453            program,
454            initCommands=initCommands,
455            preRunCommands=preRunCommands,
456            launchCommands=launchCommands,
457            expectFailure=True,
458        )
459
460        self.assertFalse(response["success"])
461        self.assertRegex(
462            response["message"],
463            r"Failed to run launch commands\. See the Debug Console for more details",
464        )
465
466        # Get output from the console. This should contain both the
467        # "initCommands" and the "preRunCommands".
468        output = self.get_console()
469        # Verify all "initCommands" were found in console output
470        self.verify_commands("initCommands", output, initCommands)
471        # Verify all "preRunCommands" were found in console output
472        self.verify_commands("preRunCommands", output, preRunCommands)
473
474        # Verify all "launchCommands" were founc in console output
475        # The launch should fail due to the invalid command.
476        self.verify_commands("launchCommands", output, launchCommands)
477        self.assertRegex(output, re.escape(bad_path) + r".*does not exist")
478
479    @skipIfNetBSD  # Hangs on NetBSD as well
480    @skipIf(archs=["arm", "aarch64"], oslist=["linux"])
481    def test_terminate_commands(self):
482        """
483        Tests that the "terminateCommands", that can be passed during
484        launch, are run when the debugger is disconnected.
485        """
486        self.build_and_create_debug_adaptor()
487        program = self.getBuildArtifact("a.out")
488
489        terminateCommands = ["expr 4+2"]
490        self.launch(
491            program=program,
492            terminateCommands=terminateCommands,
493            disconnectAutomatically=False,
494        )
495        self.get_console()
496        # Once it's disconnected the console should contain the
497        # "terminateCommands"
498        self.dap_server.request_disconnect(terminateDebuggee=True)
499        output = self.collect_console(
500            timeout_secs=1.0,
501            pattern=terminateCommands[0],
502        )
503        self.verify_commands("terminateCommands", output, terminateCommands)
504
505    def test_version(self):
506        """
507        Tests that "initialize" response contains the "version" string the same
508        as the one returned by "version" command.
509        """
510        program = self.getBuildArtifact("a.out")
511        self.build_and_launch(program)
512
513        source = "main.c"
514        breakpoint_line = line_number(source, "// breakpoint 1")
515        lines = [breakpoint_line]
516        # Set breakpoint in the thread function so we can step the threads
517        breakpoint_ids = self.set_source_breakpoints(source, lines)
518        self.continue_to_breakpoints(breakpoint_ids)
519
520        version_eval_response = self.dap_server.request_evaluate(
521            "`version", context="repl"
522        )
523        version_eval_output = version_eval_response["body"]["result"]
524
525        # The first line is the prompt line like "(lldb) version", so we skip it.
526        version_eval_output_without_prompt_line = version_eval_output.splitlines()[1:]
527        lldb_json = self.dap_server.get_initialize_value("__lldb")
528        version_string = lldb_json["version"]
529        self.assertEqual(
530            version_eval_output_without_prompt_line,
531            version_string.splitlines(),
532            "version string does not match",
533        )
534