teemperor created this revision.
teemperor added reviewers: JDevlieghere, shafik, labath.
Herald added subscribers: lldb-commits, abidh, christof.
Herald added a project: LLDB.

This patch adds two new commands to lldbtest: `expect_expr` and 
`expect_simple_expr`. These commands
are supposed to replace the current approach of calling `expect`/`runCmd` with 
`frame var`, `expr`, `p`
(and `SBFrame.EvaluateExpression` in the future).

`expect_expr` allows evaluating expressions and matching their 
result/type/error message. It allows checking
the type/error message/result of both `frame var` and `expr` without having to 
parse the output of these
commands each time (or using hacks like `substrs=[" = 1\n"]` to match the 
value). It no longer allows unintended weak
type/value checks like we currently have with calling `self.expect` (e.g. this 
can semi-randomly pass when
the result variable was assigned `$1`: `self.expect("expr 3+4", 
substrs=["1"])`).

`expect_simple_expr` allows evaluating simple expressions like printing 
variables but is testing them more thoroughly
than we currently do (by for example running the expression multiple times and 
with both `frame var` and `expr`).
If we used this method we would have caught major regressions like the one 
fixed in D67803 <https://reviews.llvm.org/D67803>.

Currently I just added the method to a few tests to demonstrate them. I'll 
migrate the rest of the test
suit over the next weeks because it's quite time intensive to rewrite all of 
this (and also because I already had some
tests that discovered bugs after I migrated them to `expect_simple_expr`).


Repository:
  rLLDB LLDB

https://reviews.llvm.org/D70314

Files:
  
lldb/packages/Python/lldbsuite/test/commands/expression/call-function/TestCallBuiltinFunction.py
  
lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
  lldb/packages/Python/lldbsuite/test/lldbtest.py

Index: lldb/packages/Python/lldbsuite/test/lldbtest.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/lldbtest.py
+++ lldb/packages/Python/lldbsuite/test/lldbtest.py
@@ -2366,6 +2366,121 @@
         self.assertTrue(matched if matching else not matched,
                         msg if msg else EXP_MSG(str, output, exe))
 
+
+    class ExpressionCommandType:
+        EXPRESSION = object()
+        FRAME_VAR = object()
+        # FIXME: SBFRAME_EVALUATE
+
+    def expect_expr(
+            self,
+            expr,
+            result_value=None,
+            result_type=None,
+            error_msg=None,
+            run_type=ExpressionCommandType.EXPRESSION
+            ):
+        """
+        Evaluates the given expression with the expression command or optionally 'frame var'
+        :param expr: The expression as a string.
+        :param result_value: The value that the expression should have. None if the value should not be checked.
+        :param result_type: The type that the expression result should have. None if the type should not be checked.
+        :param error_msg: The error message the expression should return. None if the error output should not be checked.
+        :param run_type: How the expression should be run.
+
+        result_value, result_type and error_message can have the following types which influences how
+        their values are compared to their respective output:
+          * A list of strings: expect_expr will search for the list of strings in the respective output.
+                               The output is expected to contain these strings in the listed order.
+          * Any string type: expect_expr will assume that the respective output is equal to the given string.
+        """
+        self.assertTrue(expr.strip() == expr, "Expression contains trailing/leading whitespace: '" + expr + "'")
+
+        # Run the command and extract the output.
+        if run_type in (self.ExpressionCommandType.FRAME_VAR,
+                        self.ExpressionCommandType.EXPRESSION):
+            if run_type is self.ExpressionCommandType.EXPRESSION:
+                run_cmd = "expr -- "
+            elif run_type is self.ExpressionCommandType.FRAME_VAR:
+                run_cmd = "frame var "
+            else:
+                self.assertTrue(False, "Shouldn't end up here: " + str(run_type))
+            self.ci.HandleCommand(run_cmd + expr, self.res, False)
+            err_out = self.res.GetError()
+            out = self.res.GetOutput()
+            success = self.res.Succeeded()
+            # Both 'frame var' and 'expr' have results in the format:
+            #   (<type>) <variable> = <result>
+            # This extracts these parts out of these two commands.
+            self.assertTrue(" = " in out, "No result in output: '" + out + "'")
+            var, result = out.split(" = ", 1)
+            var_name = var.split(" ")[-1]
+            var_type = var[:-len(var_name)]
+
+            # Turn '(type) ' into just 'type'
+            self.assertTrue(var_type.startswith("("))
+            self.assertTrue(var_type.endswith(") "))
+            var_type = var_type[len("("):-len(") ")]
+
+            self.assertTrue(result.endswith("\n"))
+            result = result[:-1]
+        else:
+            self.assertTrue(False, "Unknown expression type: " + str(run_type))
+
+        # Utility method that checks result_value, result_type and error_message.
+        def check_str(outer_self, expected, got):
+            # We got a list, so treat is as a list of needles we need to find in the given order.
+            if type(expected) is list:
+                remaining = got
+                for expected_part in expected:
+                    # Find the expected string.
+                    i = remaining.find(expected_part)
+                    # Assert that we found the string.
+                    outer_self.assertTrue(i != -1, "Couldn't find '" + expected_part
+                                          + "' in remaining output '" + remaining +
+                                          "'.\nFull string was: '" + got + "'")
+                    # Keep searching only the rest of the string to ensure the
+                    # strings are found in the given order.
+                    remaining = remaining[i + len(expected_part):]
+            else: # Otherwise we probably got one of Python's many string classes.
+                outer_self.assertEqual(got, expected)
+
+        # If we have a error_msg, then check that and make sure no result was specified.
+        if error_msg is not None:
+            self.assertTrue(result_value is None, "Setting result_value and error_msg doesn't make sense. Only set one of the two.")
+            self.assertTrue(result_type is None, "Setting result_type and error_msg doesn't make sense. Only set one of the two.")
+            self.assertFalse(success)
+            # Use the helper function to check the error output.
+            check_str(self, error_msg, err_out)
+            # We can exit, the rest is result checking.
+            return
+
+        # Check that we didn't get an unexpected error.
+        self.assertEqual(err_out, "", "Unexpected error output?")
+        self.assertTrue(success)
+
+        # Check result_value and result_type with check_str helper.
+        if result_value is not None:
+            check_str(self, result_value, result)
+        if result_type is not None:
+            check_str(self, result_type, var_type)
+
+    def expect_simple_expr(self, var, result_value=None, result_type=None):
+        """
+        Takes a simple expression such as a plain variable or accessing a member ('instance.member')
+        and adds it to all commands that accept these kinds of expressions ('frame var' and 'expr'
+        at the moment).
+        See expect_expr for documentation for result_value and result_type.
+        """
+        # Run the expression first with 'expr', then with 'frame var' and then with 'expr' again.
+        # We start with 'expr' as this is testing type importing to the temporary ASTContext and then
+        # importing those types to the scratch ASTContext. Frame var tests only tests importing to
+        # the persistent ASTContext which is similar to the import process to the temporary ASTContext
+        # when running 'expr'.
+        self.expect_expr(var, result_value=result_value, result_type=result_type, run_type=self.ExpressionCommandType.EXPRESSION)
+        self.expect_expr(var, result_value=result_value, result_type=result_type, run_type=self.ExpressionCommandType.FRAME_VAR)
+        self.expect_expr(var, result_value=result_value, result_type=result_type, run_type=self.ExpressionCommandType.EXPRESSION)
+
     def invoke(self, obj, name, trace=False):
         """Use reflection to call a method dynamically with no argument."""
         trace = (True if traceAlways else trace)
Index: lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
+++ lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/map/TestDataFormatterLibccMap.py
@@ -52,28 +52,11 @@
         self.addTearDownHook(cleanup)
 
         ns = self.namespace
-        self.expect('p ii',
-                    substrs=['%s::map' % ns,
-                             'size=0',
-                             '{}'])
-        self.expect('frame var ii',
-                    substrs=['%s::map' % ns,
-                             'size=0',
-                             '{}'])
+        self.expect_simple_expr('ii', result_type=['%s::map' % ns], result_value=['size=0', '{}'])
 
         lldbutil.continue_to_breakpoint(self.process(), bkpt)
 
-        self.expect('p ii',
-                    substrs=['%s::map' % ns, 'size=2',
-                             '[0] = ',
-                             'first = 0',
-                             'second = 0',
-                             '[1] = ',
-                             'first = 1',
-                             'second = 1'])
-
-        self.expect('frame variable ii',
-                    substrs=['%s::map' % ns, 'size=2',
+        self.expect_simple_expr('ii', result_type=['%s::map' % ns], result_value=['size=2',
                              '[0] = ',
                              'first = 0',
                              'second = 0',
@@ -83,8 +66,7 @@
 
         lldbutil.continue_to_breakpoint(self.process(), bkpt)
 
-        self.expect('frame variable ii',
-                    substrs=['%s::map' % ns, 'size=4',
+        self.expect_simple_expr('ii', result_type=['%s::map' % ns], result_value=['size=4',
                              '[2] = ',
                              'first = 2',
                              'second = 0',
@@ -94,17 +76,7 @@
 
         lldbutil.continue_to_breakpoint(self.process(), bkpt)
 
-        self.expect("p ii",
-                    substrs=['%s::map' % ns, 'size=8',
-                             '[5] = ',
-                             'first = 5',
-                             'second = 0',
-                             '[7] = ',
-                             'first = 7',
-                             'second = 1'])
-
-        self.expect("frame var ii",
-                    substrs=['%s::map' % ns, 'size=8',
+        self.expect_simple_expr('ii', result_type=['%s::map' % ns], result_value=['size=8',
                              '[5] = ',
                              'first = 5',
                              'second = 0',
Index: lldb/packages/Python/lldbsuite/test/commands/expression/call-function/TestCallBuiltinFunction.py
===================================================================
--- lldb/packages/Python/lldbsuite/test/commands/expression/call-function/TestCallBuiltinFunction.py
+++ lldb/packages/Python/lldbsuite/test/commands/expression/call-function/TestCallBuiltinFunction.py
@@ -40,7 +40,7 @@
 
         # Test different builtin functions.
 
-        self.expect("expr __builtin_isinf(0.0f)", substrs=["(int) $", " = 0\n"])
-        self.expect("expr __builtin_isnormal(0.0f)", substrs=["(int) $", " = 0\n"])
-        self.expect("expr __builtin_constant_p(1)", substrs=["(int) $", " = 1\n"])
-        self.expect("expr __builtin_abs(-14)", substrs=["(int) $", " = 14\n"])
+        self.expect_expr("__builtin_isinf(0.0f)", result_type="int", result_value="0")
+        self.expect_expr("__builtin_isnormal(0.0f)", result_type="int", result_value="0")
+        self.expect_expr("__builtin_constant_p(1)", result_type="int", result_value="1")
+        self.expect_expr("__builtin_abs(-14)", result_type="int", result_value="14")
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to