1"""
2Test lldb-vscode setBreakpoints request
3"""
4
5
6import unittest2
7import vscode
8from lldbsuite.test.decorators import *
9from lldbsuite.test.lldbtest import *
10from lldbsuite.test import lldbutil
11import lldbvscode_testcase
12import time
13import os
14
15
16class TestVSCode_launch(lldbvscode_testcase.VSCodeTestCaseBase):
17
18    @skipIfWindows
19    @skipIfDarwin # Flaky
20    @skipIfRemote
21    def test_default(self):
22        '''
23            Tests the default launch of a simple program. No arguments,
24            environment, or anything else is specified.
25        '''
26        program = self.getBuildArtifact("a.out")
27        self.build_and_launch(program)
28        self.continue_to_exit()
29        # Now get the STDOUT and verify our program argument is correct
30        output = self.get_stdout()
31        self.assertTrue(output and len(output) > 0,
32                        "expect program output")
33        lines = output.splitlines()
34        self.assertIn(program, lines[0],
35                      "make sure program path is in first argument")
36
37    @skipIfWindows
38    @skipIfRemote
39    def test_termination(self):
40        '''
41            Tests the correct termination of lldb-vscode upon a 'disconnect'
42            request.
43        '''
44        self.create_debug_adaptor()
45        # The underlying lldb-vscode process must be alive
46        self.assertEqual(self.vscode.process.poll(), None)
47
48        # The lldb-vscode process should finish even though
49        # we didn't close the communication socket explicitly
50        self.vscode.request_disconnect()
51
52        # Wait until the underlying lldb-vscode process dies.
53        # We need to do this because the popen.wait function in python2.7
54        # doesn't have a timeout argument.
55        for _ in range(10):
56            time.sleep(1)
57            if self.vscode.process.poll() is not None:
58                break
59        # Check the return code
60        self.assertEqual(self.vscode.process.poll(), 0)
61
62    @skipIfWindows
63    @skipIfRemote
64    def test_stopOnEntry(self):
65        '''
66            Tests the default launch of a simple program that stops at the
67            entry point instead of continuing.
68        '''
69        program = self.getBuildArtifact("a.out")
70        self.build_and_launch(program, stopOnEntry=True)
71        self.set_function_breakpoints(['main'])
72        stopped_events = self.continue_to_next_stop()
73        for stopped_event in stopped_events:
74            if 'body' in stopped_event:
75                body = stopped_event['body']
76                if 'reason' in body:
77                    reason = body['reason']
78                    self.assertTrue(
79                        reason != 'breakpoint',
80                        'verify stop isn\'t "main" breakpoint')
81
82    @skipIfWindows
83    @skipIfRemote
84    def test_cwd(self):
85        '''
86            Tests the default launch of a simple program with a current working
87            directory.
88        '''
89        program = self.getBuildArtifact("a.out")
90        program_parent_dir = os.path.realpath(
91            os.path.dirname(os.path.dirname(program)))
92        self.build_and_launch(program,
93                              cwd=program_parent_dir)
94        self.continue_to_exit()
95        # Now get the STDOUT and verify our program argument is correct
96        output = self.get_stdout()
97        self.assertTrue(output and len(output) > 0,
98                        "expect program output")
99        lines = output.splitlines()
100        found = False
101        for line in lines:
102            if line.startswith('cwd = \"'):
103                quote_path = '"%s"' % (program_parent_dir)
104                found = True
105                self.assertIn(quote_path, line,
106                              "working directory '%s' not in '%s'" % (
107                                  program_parent_dir, line))
108        self.assertTrue(found, "verified program working directory")
109
110    @skipIfWindows
111    @skipIfRemote
112    def test_debuggerRoot(self):
113        '''
114            Tests the "debuggerRoot" will change the working directory of
115            the lldb-vscode debug adaptor.
116        '''
117        program = self.getBuildArtifact("a.out")
118        program_parent_dir = os.path.realpath(
119            os.path.dirname(os.path.dirname(program)))
120        commands = ['platform shell echo cwd = $PWD']
121        self.build_and_launch(program,
122                              debuggerRoot=program_parent_dir,
123                              initCommands=commands)
124        output = self.get_console()
125        self.assertTrue(output and len(output) > 0,
126                        "expect console output")
127        lines = output.splitlines()
128        prefix = 'cwd = '
129        found = False
130        for line in lines:
131            if line.startswith(prefix):
132                found = True
133                self.assertEquals(program_parent_dir, line[len(prefix):],
134                                "lldb-vscode working dir '%s' == '%s'" % (
135                                    program_parent_dir, line[6:]))
136        self.assertTrue(found, "verified lldb-vscode working directory")
137        self.continue_to_exit()
138
139    @skipIfWindows
140    @skipIfRemote
141    def test_sourcePath(self):
142        '''
143            Tests the "sourcePath" will set the target.source-map.
144        '''
145        program = self.getBuildArtifact("a.out")
146        program_dir = os.path.dirname(program)
147        self.build_and_launch(program,
148                              sourcePath=program_dir)
149        output = self.get_console()
150        self.assertTrue(output and len(output) > 0,
151                        "expect console output")
152        lines = output.splitlines()
153        prefix = '(lldb) settings set target.source-map "." '
154        found = False
155        for line in lines:
156            if line.startswith(prefix):
157                found = True
158                quoted_path = '"%s"' % (program_dir)
159                self.assertEquals(quoted_path, line[len(prefix):],
160                                "lldb-vscode working dir %s == %s" % (
161                                    quoted_path, line[6:]))
162        self.assertTrue(found, 'found "sourcePath" in console output')
163        self.continue_to_exit()
164
165    @skipIfWindows
166    @skipIfRemote
167    def test_disableSTDIO(self):
168        '''
169            Tests the default launch of a simple program with STDIO disabled.
170        '''
171        program = self.getBuildArtifact("a.out")
172        self.build_and_launch(program,
173                              disableSTDIO=True)
174        self.continue_to_exit()
175        # Now get the STDOUT and verify our program argument is correct
176        output = self.get_stdout()
177        self.assertEquals(output, None,
178                        "expect no program output")
179
180    @skipIfWindows
181    @skipIfLinux # shell argument expansion doesn't seem to work on Linux
182    @expectedFailureAll(oslist=["freebsd", "netbsd"],
183                        bugnumber="llvm.org/pr48349")
184    @skipIfRemote
185    def test_shellExpandArguments_enabled(self):
186        '''
187            Tests the default launch of a simple program with shell expansion
188            enabled.
189        '''
190        program = self.getBuildArtifact("a.out")
191        program_dir = os.path.dirname(program)
192        glob = os.path.join(program_dir, '*.out')
193        self.build_and_launch(program, args=[glob], shellExpandArguments=True)
194        self.continue_to_exit()
195        # Now get the STDOUT and verify our program argument is correct
196        output = self.get_stdout()
197        self.assertTrue(output and len(output) > 0,
198                        "expect no program output")
199        lines = output.splitlines()
200        for line in lines:
201            quote_path = '"%s"' % (program)
202            if line.startswith("arg[1] ="):
203                self.assertIn(quote_path, line,
204                              'verify "%s" expanded to "%s"' % (
205                                  glob, program))
206
207    @skipIfWindows
208    @skipIfRemote
209    def test_shellExpandArguments_disabled(self):
210        '''
211            Tests the default launch of a simple program with shell expansion
212            disabled.
213        '''
214        program = self.getBuildArtifact("a.out")
215        program_dir = os.path.dirname(program)
216        glob = os.path.join(program_dir, '*.out')
217        self.build_and_launch(program,
218                              args=[glob],
219                              shellExpandArguments=False)
220        self.continue_to_exit()
221        # Now get the STDOUT and verify our program argument is correct
222        output = self.get_stdout()
223        self.assertTrue(output and len(output) > 0,
224                        "expect no program output")
225        lines = output.splitlines()
226        for line in lines:
227            quote_path = '"%s"' % (glob)
228            if line.startswith("arg[1] ="):
229                self.assertIn(quote_path, line,
230                              'verify "%s" stayed to "%s"' % (
231                                  glob, glob))
232
233    @skipIfWindows
234    @skipIfRemote
235    def test_args(self):
236        '''
237            Tests launch of a simple program with arguments
238        '''
239        program = self.getBuildArtifact("a.out")
240        args = ["one", "with space", "'with single quotes'",
241                '"with double quotes"']
242        self.build_and_launch(program,
243                              args=args)
244        self.continue_to_exit()
245
246        # Now get the STDOUT and verify our arguments got passed correctly
247        output = self.get_stdout()
248        self.assertTrue(output and len(output) > 0,
249                        "expect program output")
250        lines = output.splitlines()
251        # Skip the first argument that contains the program name
252        lines.pop(0)
253        # Make sure arguments we specified are correct
254        for (i, arg) in enumerate(args):
255            quoted_arg = '"%s"' % (arg)
256            self.assertIn(quoted_arg, lines[i],
257                          'arg[%i] "%s" not in "%s"' % (i+1, quoted_arg, lines[i]))
258
259    @skipIfWindows
260    @skipIfRemote
261    def test_environment(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=",
267               "SPACE=Hello World"]
268        self.build_and_launch(program,
269                              env=env)
270        self.continue_to_exit()
271
272        # Now get the STDOUT and verify our arguments got passed correctly
273        output = self.get_stdout()
274        self.assertTrue(output and len(output) > 0,
275                        "expect program output")
276        lines = output.splitlines()
277        # Skip the all arguments so we have only environment vars left
278        while len(lines) and lines[0].startswith("arg["):
279            lines.pop(0)
280        # Make sure each environment variable in "env" is actually set in the
281        # program environment that was printed to STDOUT
282        for var in env:
283            found = False
284            for program_var in lines:
285                if var in program_var:
286                    found = True
287                    break
288            self.assertTrue(found,
289                            '"%s" must exist in program environment (%s)' % (
290                                var, lines))
291
292    @skipIfWindows
293    @skipIfRemote
294    @skipIf(archs=["arm", "aarch64"]) # failed run https://lab.llvm.org/buildbot/#/builders/96/builds/6933
295    def test_commands(self):
296        '''
297            Tests the "initCommands", "preRunCommands", "stopCommands",
298            "terminateCommands" and "exitCommands" that can be passed during
299            launch.
300
301            "initCommands" are a list of LLDB commands that get executed
302            before the targt is created.
303            "preRunCommands" are a list of LLDB commands that get executed
304            after the target has been created and before the launch.
305            "stopCommands" are a list of LLDB commands that get executed each
306            time the program stops.
307            "exitCommands" are a list of LLDB commands that get executed when
308            the process exits
309            "terminateCommands" are a list of LLDB commands that get executed when
310            the debugger session terminates.
311        '''
312        program = self.getBuildArtifact("a.out")
313        initCommands = ['target list', 'platform list']
314        preRunCommands = ['image list a.out', 'image dump sections a.out']
315        postRunCommands = ['help trace', 'help process trace']
316        stopCommands = ['frame variable', 'bt']
317        exitCommands = ['expr 2+3', 'expr 3+4']
318        terminateCommands = ['expr 4+2']
319        self.build_and_launch(program,
320                              initCommands=initCommands,
321                              preRunCommands=preRunCommands,
322                              postRunCommands=postRunCommands,
323                              stopCommands=stopCommands,
324                              exitCommands=exitCommands,
325                              terminateCommands=terminateCommands)
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.assertEquals(len(breakpoint_ids), len(lines),
346                        "expect correct number of breakpoints")
347
348        # Continue after launch and hit the first breakpoint.
349        # Get output from the console. This should contain both the
350        # "stopCommands" that were run after the first breakpoint was hit
351        self.continue_to_breakpoints(breakpoint_ids)
352        output = self.get_console(timeout=1.0)
353        self.verify_commands('stopCommands', output, stopCommands)
354
355        # Continue again and hit the second breakpoint.
356        # Get output from the console. This should contain both the
357        # "stopCommands" that were run after the second breakpoint was hit
358        self.continue_to_breakpoints(breakpoint_ids)
359        output = self.get_console(timeout=1.0)
360        self.verify_commands('stopCommands', output, stopCommands)
361
362        # Continue until the program exits
363        self.continue_to_exit()
364        # Get output from the console. This should contain both the
365        # "exitCommands" that were run after the second breakpoint was hit
366        # and the "terminateCommands" due to the debugging session ending
367        output = self.collect_console(duration=1.0)
368        self.verify_commands('exitCommands', output, exitCommands)
369        self.verify_commands('terminateCommands', output, terminateCommands)
370
371    @skipIfWindows
372    @skipIfRemote
373    def test_extra_launch_commands(self):
374        '''
375            Tests the "launchCommands" with extra launching settings
376        '''
377        self.build_and_create_debug_adaptor()
378        program = self.getBuildArtifact("a.out")
379
380        source = 'main.c'
381        first_line = line_number(source, '// breakpoint 1')
382        second_line = line_number(source, '// breakpoint 2')
383        # Set target binary and 2 breakpoints
384        # then we can varify the "launchCommands" get run
385        # also we can verify that "stopCommands" get run as the
386        # breakpoints get hit
387        launchCommands = [
388            'target create "%s"' % (program),
389            'breakpoint s -f main.c -l %d' % first_line,
390            'breakpoint s -f main.c -l %d' % second_line,
391            'process launch --stop-at-entry'
392        ]
393
394        initCommands = ['target list', 'platform list']
395        preRunCommands = ['image list a.out', 'image dump sections a.out']
396        stopCommands = ['frame variable', 'bt']
397        exitCommands = ['expr 2+3', 'expr 3+4']
398        self.launch(program,
399                    initCommands=initCommands,
400                    preRunCommands=preRunCommands,
401                    stopCommands=stopCommands,
402                    exitCommands=exitCommands,
403                    launchCommands=launchCommands)
404
405        # Get output from the console. This should contain both the
406        # "initCommands" and the "preRunCommands".
407        output = self.get_console()
408        # Verify all "initCommands" were found in console output
409        self.verify_commands('initCommands', output, initCommands)
410        # Verify all "preRunCommands" were found in console output
411        self.verify_commands('preRunCommands', output, preRunCommands)
412
413        # Verify all "launchCommands" were founc in console output
414        # After execution, program should launch
415        self.verify_commands('launchCommands', output, launchCommands)
416        # Verify the "stopCommands" here
417        self.continue_to_next_stop()
418        output = self.get_console(timeout=1.0)
419        self.verify_commands('stopCommands', output, stopCommands)
420
421        # Continue and hit the second breakpoint.
422        # Get output from the console. This should contain both the
423        # "stopCommands" that were run after the first breakpoint was hit
424        self.continue_to_next_stop()
425        output = self.get_console(timeout=1.0)
426        self.verify_commands('stopCommands', output, stopCommands)
427
428        # Continue until the program exits
429        self.continue_to_exit()
430        # Get output from the console. This should contain both the
431        # "exitCommands" that were run after the second breakpoint was hit
432        output = self.get_console(timeout=1.0)
433        self.verify_commands('exitCommands', output, exitCommands)
434
435    @skipIfWindows
436    @skipIfNetBSD # Hangs on NetBSD as well
437    @skipIfDarwin
438    @skipIf(archs=["arm", "aarch64"]) # Example of a flaky run http://lab.llvm.org:8011/builders/lldb-aarch64-ubuntu/builds/5540/steps/test/logs/stdio
439    def test_terminate_commands(self):
440        '''
441            Tests that the "terminateCommands", that can be passed during
442            launch, are run when the debugger is disconnected.
443        '''
444        self.build_and_create_debug_adaptor()
445        program = self.getBuildArtifact("a.out")
446
447        terminateCommands = ['expr 4+2']
448        self.launch(program=program,
449                    terminateCommands=terminateCommands)
450        self.get_console()
451        # Once it's disconnected the console should contain the
452        # "terminateCommands"
453        self.vscode.request_disconnect(terminateDebuggee=True)
454        output = self.collect_console(duration=1.0)
455        self.verify_commands('terminateCommands', output, terminateCommands)
456