""" Test lldb-dap evaluate request """ import re import lldbdap_testcase from lldbsuite.test.decorators import skipIfWindows from lldbsuite.test.lldbtest import line_number from typing import TypedDict, Optional class EvaluateResponseBody(TypedDict, total=False): result: str variablesReference: int type: Optional[str] memoryReference: Optional[str] valueLocationReference: Optional[int] class TestDAP_evaluate(lldbdap_testcase.DAPTestCaseBase): def assertEvaluate( self, expression, result: str, want_type="", want_varref=False, want_memref=True, want_locref=False, frame_index: Optional[int] = 0, is_hex=None, ): resp = self.dap_server.request_evaluate( expression, context=self.context, is_hex=is_hex, frameIndex=frame_index ) self.assertTrue( resp["success"], f"Failed to evaluate expression {expression!r} in frame {frame_index}", ) body: EvaluateResponseBody = resp["body"] self.assertRegex( body["result"], result, f"Unexpected 'result' for expression {expression!r} in response body {body}", ) if want_varref: self.assertNotEqual( body["variablesReference"], 0, f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}", ) else: self.assertEqual( body["variablesReference"], 0, f"Unexpected 'variablesReference' for expression {expression!r} in response body {body}", ) if want_type: self.assertEqual( body["type"], want_type, f"Unexpected 'type' for expression {expression!r} in response body {body}", ) if want_memref: self.assertIn( "memoryReference", body, f"Unexpected 'memoryReference' for expression {expression!r} in response body {body}", ) if want_locref: self.assertIn( "valueLocationReference", body, f"Unexpected 'valueLocationReference' for expression {expression!r} in response body {body}", ) def assertEvaluateFailure(self, expression): response = self.dap_server.request_evaluate(expression, context=self.context) self.assertFalse( response["success"], f"Expression:'{expression}' should fail in {self.context} context, got {response!r}", ) self.assertNotIn( "result", response["body"], ) def isResultExpandedDescription(self): return self.context == "repl" def isResultShortDescription(self): return self.context == "clipboard" def isExpressionParsedExpected(self): return self.context != "hover" def run_test_evaluate_expressions( self, context=None, enableAutoVariableSummaries=False ): """ Tests the evaluate expression request at different breakpoints """ self.context = context program = self.getBuildArtifact("a.out") self.build_and_launch( program, enableAutoVariableSummaries=enableAutoVariableSummaries, ) source = "main.cpp" breakpoint_lines = [ line_number(source, "// breakpoint 1"), line_number(source, "// breakpoint 2"), line_number(source, "// breakpoint 3"), line_number(source, "// breakpoint 4"), line_number(source, "// breakpoint 5"), line_number(source, "// breakpoint 6"), line_number(source, "// breakpoint 7"), line_number(source, "// breakpoint 8"), ] breakpoint_ids = self.set_source_breakpoints(source, breakpoint_lines) self.assertEqual( len(breakpoint_ids), len(breakpoint_lines), "Did not resolve all the breakpoints.", ) breakpoint_1 = breakpoint_ids[0] breakpoint_2 = breakpoint_ids[1] breakpoint_3 = breakpoint_ids[2] breakpoint_4 = breakpoint_ids[3] breakpoint_5 = breakpoint_ids[4] breakpoint_6 = breakpoint_ids[5] breakpoint_7 = breakpoint_ids[6] breakpoint_8 = breakpoint_ids[7] self.continue_to_breakpoint(breakpoint_1) # Expressions at breakpoint 1, which is in main self.assertEvaluate("var1", "20", want_type="int") # Empty expression should equate to the previous expression. if context == "repl": self.assertEvaluate("", "20") else: self.assertEvaluateFailure("") self.assertEvaluate("var2", "21", want_type="int") if context == "repl": self.assertEvaluate("", "21", want_type="int") self.assertEvaluate("", "21", want_type="int") self.assertEvaluate("static_int", "0x0000002a", want_type="int", is_hex=True) self.assertEvaluate( "non_static_int", "0x0000002b", want_type="int", is_hex=True ) self.assertEvaluate("struct1.foo", "0x0000000f", want_type="int", is_hex=True) self.assertEvaluate("struct2->foo", "0x00000010", want_type="int", is_hex=True) self.assertEvaluate("static_int", "42", want_type="int") self.assertEvaluate("non_static_int", "43", want_type="int") self.assertEvaluate("struct1.foo", "15", want_type="int") self.assertEvaluate("struct2->foo", "16", want_type="int") if self.isResultExpandedDescription(): self.assertEvaluate( "struct1", r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)", want_type="my_struct", want_varref=True, ) self.assertEvaluate( "struct2", r"\(my_struct \*\) (struct2|\$\d+) = 0x.*", want_type="my_struct *", want_varref=True, ) self.assertEvaluate( "struct3", r"\(my_struct \*\) (struct3|\$\d+) = nullptr", want_type="my_struct *", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "struct1", "(foo = 15)", want_type="my_struct", want_varref=True, ) self.assertEvaluate( "struct2", r"0x.*", want_type="my_struct *", want_varref=True, ) self.assertEvaluate( "struct3", "nullptr", want_type="my_struct *", want_varref=True, ) else: self.assertEvaluate( "struct1", (re.escape("{foo:15}") if enableAutoVariableSummaries else "my_struct"), want_varref=True, ) self.assertEvaluate( "struct2", "0x.* {foo:16}" if enableAutoVariableSummaries else "0x.*", want_varref=True, want_type="my_struct *", ) self.assertEvaluate( "struct3", "0x.*0", want_varref=True, want_type="my_struct *" ) if context == "repl" or context is None: # In repl or unknown context expressions may be interpreted as lldb # commands since no variables have the same name as the command. self.assertEvaluate("list", r".*", want_memref=False) # Changing the frame index should not make a difference self.assertEvaluate( "version", r".*lldb.+", want_memref=False, frame_index=1 ) else: self.assertEvaluateFailure("list") # local variable of a_function self.assertEvaluateFailure("my_struct") # type name self.assertEvaluateFailure("int") # type name self.assertEvaluateFailure("foo") # member of my_struct if self.isExpressionParsedExpected(): self.assertEvaluate( "a_function", "0x.*a.out`a_function.*", want_type="int (*)(int)", want_varref=True, want_memref=False, want_locref=True, ) self.assertEvaluate( "a_function(1)", "1", want_memref=False, want_type="int" ) self.assertEvaluate("var2 + struct1.foo", "36", want_memref=False) self.assertEvaluate( "foo_func", "0x.*a.out`foo_func.*", want_type="int (*)()", want_varref=True, want_memref=False, want_locref=True, ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") self.assertEvaluateFailure("a_function(1)") self.assertEvaluateFailure("var2 + struct1.foo") self.assertEvaluateFailure("foo_func") self.assertEvaluate("foo_var", "44") # Expressions at breakpoint 2, which is an anonymous block self.continue_to_breakpoint(breakpoint_2) self.assertEvaluate("var1", "20") self.assertEvaluate("var2", "2") # different variable with the same name self.assertEvaluate("static_int", "42") self.assertEvaluate( "non_static_int", "10" ) # different variable with the same name if self.isResultExpandedDescription(): self.assertEvaluate( "struct1", r"\(my_struct\) (struct1|\$\d+) = \(foo = 15\)", want_type="my_struct", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "struct1", "(foo = 15)", want_type="my_struct", want_varref=True, ) else: self.assertEvaluate( "struct1", (re.escape("{foo:15}") if enableAutoVariableSummaries else "my_struct"), want_type="my_struct", want_varref=True, ) self.assertEvaluate("struct1.foo", "15") self.assertEvaluate("struct2->foo", "16") if self.isExpressionParsedExpected(): self.assertEvaluate( "a_function", "0x.*a.out`a_function.*", want_type="int (*)(int)", want_varref=True, want_memref=False, want_locref=True, ) self.assertEvaluate("a_function(1)", "1", want_memref=False) self.assertEvaluate("var2 + struct1.foo", "17", want_memref=False) self.assertEvaluate( "foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") self.assertEvaluateFailure("a_function(1)") self.assertEvaluateFailure("var2 + struct1.foo") self.assertEvaluateFailure("foo_func") self.assertEvaluate("foo_var", "44") # Expressions at breakpoint 3, which is inside a_function self.continue_to_breakpoint(breakpoint_3) self.assertEvaluate("list", "42") self.assertEvaluate("static_int", "42") self.assertEvaluate("non_static_int", "43") # variable from a different frame self.assertEvaluate("var1", "20", frame_index=1) if self.isExpressionParsedExpected(): # access global variable without a frame # Run in variable mode to avoid interpreting it as a command res = self.dap_server.request_evaluate( "`lldb-dap repl-mode variable", context="repl" ) self.assertTrue(res["success"]) self.assertEvaluate("static_int", "42", frame_index=None, want_memref=False) res = self.dap_server.request_evaluate( "`lldb-dap repl-mode auto", context="repl" ) self.assertTrue(res["success"]) self.assertEvaluateFailure("var1") self.assertEvaluateFailure("var2") self.assertEvaluateFailure("struct1") self.assertEvaluateFailure("struct1.foo") self.assertEvaluateFailure("struct2->foo") self.assertEvaluateFailure("var2 + struct1.foo") if self.isExpressionParsedExpected(): self.assertEvaluate( "a_function", "0x.*a.out`a_function.*", want_varref=True, want_memref=False, want_locref=True, ) self.assertEvaluate("a_function(1)", "1", want_memref=False) self.assertEvaluate("list + 1", "43", want_memref=False) self.assertEvaluate( "foo_func", "0x.*a.out`foo_func.*", want_varref=True, want_memref=False ) self.assertEvaluate("foo_var", "44") else: self.assertEvaluateFailure("a_function") self.assertEvaluateFailure("a_function(1)") self.assertEvaluateFailure("list + 1") self.assertEvaluateFailure("foo_func") self.assertEvaluate("foo_var", "44") # Now we check that values are updated after stepping self.continue_to_breakpoint(breakpoint_4) if self.isResultExpandedDescription(): self.assertEvaluate( "my_vec", r"\(std::vector\) \$\d+ = size=2 {\n \[0\] = 1\n \[1\] = 2\n}", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_vec", r"size=2 {\n \[0\] = 1\n \[1\] = 2\n}", want_varref=True ) else: self.assertEvaluate("my_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_5) if self.isResultExpandedDescription(): self.assertEvaluate( "my_vec", r"\(std::vector\) \$\d+ = size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_vec", r"size=3 {\n \[0\] = 1\n \[1\] = 2\n \[2\] = 3\n}", want_varref=True, ) else: self.assertEvaluate("my_vec", "size=3", want_varref=True) if self.isResultExpandedDescription(): self.assertEvaluate( "my_map", r"\(std::map\) \$\d+ = size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_map", r"size=2 {\n \[0\] = \(first = 1, second = 2\)\n \[1\] = \(first = 2, second = 3\)\n}", want_varref=True, ) else: self.assertEvaluate("my_map", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_6) self.assertEvaluate("my_map", "size=3", want_varref=True) if self.isResultExpandedDescription(): self.assertEvaluate( "my_bool_vec", r"\(std::vector\) \$\d+ = size=1 {\n \[0\] = true\n}", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_bool_vec", r"size=1 {\n \[0\] = true\n}", want_varref=True ) else: self.assertEvaluate("my_bool_vec", "size=1", want_varref=True) self.continue_to_breakpoint(breakpoint_7) if self.isResultExpandedDescription(): self.assertEvaluate( "my_bool_vec", r"\(std::vector\) \$\d+ = size=2 {\n \[0\] = true\n \[1\] = false\n}", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_bool_vec", r"size=2 {\n \[0\] = true\n \[1\] = false\n}", want_varref=True, ) else: self.assertEvaluate("my_bool_vec", "size=2", want_varref=True) self.continue_to_breakpoint(breakpoint_8) # Test memory read, especially with 'empty' repeat commands. if context == "repl": self.assertEvaluate( "memory read -c 1 &my_ints", ".* 05 .*\n", want_memref=False ) self.assertEvaluate("", ".* 0a .*\n", want_memref=False) self.assertEvaluate("", ".* 0f .*\n", want_memref=False) self.assertEvaluate("", ".* 14 .*\n", want_memref=False) self.assertEvaluate("", ".* 19 .*\n", want_memref=False) if self.isResultExpandedDescription(): self.assertEvaluate( "my_longs", r"\(long\[3\]\) \$\d+ = \(\[0\] = 5, \[1\] = 6, \[2\] = 7\)", want_varref=True, ) elif self.isResultShortDescription(): self.assertEvaluate( "my_longs", r"\(\[0\] = 5, \[1\] = 6, \[2\] = 7\)", want_varref=True, ) else: self.assertEvaluate( "my_longs", "{5, 6, 7}" if enableAutoVariableSummaries else r"long\[3\]", want_varref=True, ) self.continue_to_exit() @skipIfWindows def test_generic_evaluate_expressions(self): # Tests context-less expression evaluations self.run_test_evaluate_expressions(enableAutoVariableSummaries=False) @skipIfWindows def test_repl_evaluate_expressions(self): # Tests expression evaluations that are triggered from the Debug Console self.run_test_evaluate_expressions("repl", enableAutoVariableSummaries=False) @skipIfWindows def test_watch_evaluate_expressions(self): # Tests expression evaluations that are triggered from a watch expression self.run_test_evaluate_expressions("watch", enableAutoVariableSummaries=True) @skipIfWindows def test_hover_evaluate_expressions(self): # Tests expression evaluations that are triggered when hovering on the editor self.run_test_evaluate_expressions("hover", enableAutoVariableSummaries=False) @skipIfWindows def test_variable_evaluate_expressions(self): # Tests expression evaluations that are triggered in the variable explorer self.run_test_evaluate_expressions( "variables", enableAutoVariableSummaries=True ) @skipIfWindows def test_clipboard_evaluate_expressions(self): # Tests expression evaluations that are triggered when value copied in editor self.run_test_evaluate_expressions( "clipboard", enableAutoVariableSummaries=False )