| """ | |
| 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.assertEvaluateFailure("(float) var2") | |
| 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<int>\) \$\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<int>\) \$\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<int, int>\) \$\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<bool>\) \$\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<bool>\) \$\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 | |
| ) |