| """ |
| 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, |
| is_hex=None, |
| ): |
| resp = self.dap_server.request_evaluate( |
| expression, context=self.context, is_hex=is_hex |
| ) |
| self.assertTrue( |
| resp["success"], f"Failed to evaluate expression {expression!r}" |
| ) |
| 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): |
| self.assertNotIn( |
| "result", |
| self.dap_server.request_evaluate(expression, context=self.context)["body"], |
| ) |
| |
| def isResultExpandedDescription(self): |
| return self.context == "repl" |
| |
| 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, |
| ) |
| 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": |
| # In the repl 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) |
| 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, |
| ) |
| 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") |
| |
| 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) |
| self.assertEvaluate("my_vec", "size=2", want_varref=True) |
| self.continue_to_breakpoint(breakpoint_5) |
| self.assertEvaluate("my_vec", "size=3", want_varref=True) |
| |
| self.assertEvaluate("my_map", "size=2", want_varref=True) |
| self.continue_to_breakpoint(breakpoint_6) |
| self.assertEvaluate("my_map", "size=3", want_varref=True) |
| |
| self.assertEvaluate("my_bool_vec", "size=1", want_varref=True) |
| self.continue_to_breakpoint(breakpoint_7) |
| 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) |
| |
| 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 |
| ) |