xref: /llvm-project/lldb/test/API/types/AbstractBase.py (revision 2238dcc39358353cac21df75c3c3286ab20b8f53)
1"""
2Abstract base class of basic types provides a generic type tester method.
3"""
4
5import os
6import re
7import lldb
8from lldbsuite.test.lldbtest import *
9import lldbsuite.test.lldbutil as lldbutil
10
11
12def Msg(var, val, using_frame_variable):
13    return "'%s %s' matches the output (from compiled code): %s" % (
14        "frame variable --show-types" if using_frame_variable else "expression",
15        var,
16        val,
17    )
18
19
20class GenericTester(TestBase):
21    # This is the pattern by design to match the " var = 'value'" output from
22    # printf() stmts (see basic_type.cpp).
23    pattern = re.compile(" (\*?a[^=]*) = '([^=]*)'$")
24
25    # Assert message.
26    DATA_TYPE_GROKKED = "Data type from expr parser output is parsed correctly"
27
28    def setUp(self):
29        # Call super's setUp().
30        TestBase.setUp(self)
31        # We'll use the test method name as the exe_name.
32        # There are a bunch of test cases under test/types and we don't want the
33        # module cacheing subsystem to be confused with executable name "a.out"
34        # used for all the test cases.
35        self.exe_name = self.testMethodName
36        golden = "{}-golden-output.txt".format(self.testMethodName)
37        self.golden_filename = self.getBuildArtifact(golden)
38
39    def tearDown(self):
40        """Cleanup the test byproducts."""
41        if os.path.exists(self.golden_filename):
42            os.remove(self.golden_filename)
43        TestBase.tearDown(self)
44
45    # ==========================================================================#
46    # Functions build_and_run() and build_and_run_expr() are generic functions #
47    # which are called from the Test*Types*.py test cases.  The API client is  #
48    # responsible for supplying two mandatory arguments: the source file, e.g.,#
49    # 'int.cpp', and the atoms, e.g., set(['unsigned', 'long long']) to the    #
50    # functions.  There are also three optional keyword arguments of interest, #
51    # as follows:                                                              #
52    #                                                                          #
53    # bc -> blockCaptured (defaulted to False)                                 #
54    #         True: testing vars of various basic types from inside a block    #
55    #         False: testing vars of various basic types from a function       #
56    # qd -> quotedDisplay (defaulted to False)                                 #
57    #         True: the output from 'frame var' or 'expr var' contains a pair  #
58    #               of single quotes around the value                          #
59    #         False: no single quotes are to be found around the value of      #
60    #                variable                                                  #
61    # ==========================================================================#
62
63    def build_and_run(self, source, atoms, bc=False, qd=False):
64        self.build_and_run_with_source_atoms_expr(
65            source, atoms, expr=False, bc=bc, qd=qd
66        )
67
68    def build_and_run_expr(self, source, atoms, bc=False, qd=False):
69        self.build_and_run_with_source_atoms_expr(
70            source, atoms, expr=True, bc=bc, qd=qd
71        )
72
73    def build_and_run_with_source_atoms_expr(
74        self, source, atoms, expr, bc=False, qd=False
75    ):
76        # See also Makefile and basic_type.cpp:177.
77        if bc:
78            d = {
79                "CXX_SOURCES": source,
80                "EXE": self.exe_name,
81                "CFLAGS_EXTRAS": "-DTEST_BLOCK_CAPTURED_VARS",
82            }
83        else:
84            d = {"CXX_SOURCES": source, "EXE": self.exe_name}
85        self.build(dictionary=d)
86        self.setTearDownCleanup(dictionary=d)
87        if expr:
88            self.generic_type_expr_tester(
89                self.exe_name, atoms, blockCaptured=bc, quotedDisplay=qd
90            )
91        else:
92            self.generic_type_tester(
93                self.exe_name, atoms, blockCaptured=bc, quotedDisplay=qd
94            )
95
96    def process_launch_o(self):
97        # process launch command output redirect always goes to host the
98        # process is running on
99        if lldb.remote_platform:
100            # process launch -o requires a path that is valid on the target
101            self.assertIsNotNone(lldb.remote_platform.GetWorkingDirectory())
102            remote_path = lldbutil.append_to_process_working_directory(
103                self, "lldb-stdout-redirect.txt"
104            )
105            self.runCmd("process launch -- {remote}".format(remote=remote_path))
106            # copy remote_path to local host
107            self.runCmd(
108                'platform get-file {remote} "{local}"'.format(
109                    remote=remote_path, local=self.golden_filename
110                )
111            )
112        else:
113            self.runCmd(
114                'process launch -o "{local}"'.format(local=self.golden_filename)
115            )
116
117    def get_golden_list(self, blockCaptured=False):
118        with open(self.golden_filename, "r") as f:
119            go = f.read()
120
121        golden_list = []
122        # Scan the golden output line by line, looking for the pattern:
123        #
124        #     variable = 'value'
125        #
126        for line in go.split(os.linesep):
127            # We'll ignore variables of array types from inside a block.
128            if blockCaptured and "[" in line:
129                continue
130            match = self.pattern.search(line)
131            if match:
132                var, val = match.group(1), match.group(2)
133                golden_list.append((var, val))
134        return golden_list
135
136    def generic_type_tester(
137        self, exe_name, atoms, quotedDisplay=False, blockCaptured=False
138    ):
139        """Test that variables with basic types are displayed correctly."""
140        self.runCmd("file %s" % self.getBuildArtifact(exe_name), CURRENT_EXECUTABLE_SET)
141
142        # First, capture the golden output emitted by the oracle, i.e., the
143        # series of printf statements.
144        self.process_launch_o()
145
146        # This golden list contains a list of (variable, value) pairs extracted
147        # from the golden output.
148        gl = self.get_golden_list(blockCaptured)
149
150        # This test uses a #include of "basic_type.cpp" so we need to enable
151        # always setting inlined breakpoints.
152        self.runCmd("settings set target.inline-breakpoint-strategy always")
153
154        # Inherit TCC permissions. We can leave this set.
155        self.runCmd("settings set target.inherit-tcc true")
156
157        # Kill rather than detach from the inferior if something goes wrong.
158        self.runCmd("settings set target.detach-on-error false")
159
160        # And add hooks to restore the settings during tearDown().
161        self.addTearDownHook(
162            lambda: self.runCmd(
163                "settings set target.inline-breakpoint-strategy headers"
164            )
165        )
166
167        # Bring the program to the point where we can issue a series of
168        # 'frame variable --show-types' command.
169        if blockCaptured:
170            break_line = line_number(
171                "basic_type.cpp", "// Break here to test block captured variables."
172            )
173        else:
174            break_line = line_number(
175                "basic_type.cpp",
176                "// Here is the line we will break on to check variables.",
177            )
178        lldbutil.run_break_set_by_file_and_line(
179            self, "basic_type.cpp", break_line, num_expected_locations=1, loc_exact=True
180        )
181
182        self.runCmd("run", RUN_SUCCEEDED)
183        self.expect(
184            "process status",
185            STOPPED_DUE_TO_BREAKPOINT,
186            substrs=[
187                "stop reason = breakpoint",
188                " at basic_type.cpp:%d" % break_line,
189            ],
190        )
191
192        # self.runCmd("frame variable --show-types")
193
194        # Now iterate through the golden list, comparing against the output from
195        # 'frame variable --show-types var'.
196        for var, val in gl:
197            self.runCmd("frame variable --show-types %s" % var)
198            output = self.res.GetOutput()
199
200            # The input type is in a canonical form as a set of named atoms.
201            # The display type string must contain each and every element.
202            #
203            # Example:
204            #     runCmd: frame variable --show-types a_array_bounded[0]
205            #     output: (char) a_array_bounded[0] = 'a'
206            #
207            try:
208                dt = re.match("^\((.*)\)", output).group(1)
209            except:
210                self.fail(self.DATA_TYPE_GROKKED)
211
212            # Expect the display type string to contain each and every atoms.
213            self.expect(
214                dt,
215                "Display type: '%s' must contain the type atoms: '%s'" % (dt, atoms),
216                exe=False,
217                substrs=list(atoms),
218            )
219
220            # The (var, val) pair must match, too.
221            nv = ("%s = '%s'" if quotedDisplay else "%s = %s") % (var, val)
222            self.expect(output, Msg(var, val, True), exe=False, substrs=[nv])
223
224    def generic_type_expr_tester(
225        self, exe_name, atoms, quotedDisplay=False, blockCaptured=False
226    ):
227        """Test that variable expressions with basic types are evaluated correctly."""
228
229        self.runCmd("file %s" % self.getBuildArtifact(exe_name), CURRENT_EXECUTABLE_SET)
230
231        # First, capture the golden output emitted by the oracle, i.e., the
232        # series of printf statements.
233        self.process_launch_o()
234
235        # This golden list contains a list of (variable, value) pairs extracted
236        # from the golden output.
237        gl = self.get_golden_list(blockCaptured)
238
239        # This test uses a #include of "basic_type.cpp" so we need to enable
240        # always setting inlined breakpoints.
241        self.runCmd("settings set target.inline-breakpoint-strategy always")
242        # And add hooks to restore the settings during tearDown().
243        self.addTearDownHook(
244            lambda: self.runCmd(
245                "settings set target.inline-breakpoint-strategy headers"
246            )
247        )
248
249        # Bring the program to the point where we can issue a series of
250        # 'expr' command.
251        if blockCaptured:
252            break_line = line_number(
253                "basic_type.cpp", "// Break here to test block captured variables."
254            )
255        else:
256            break_line = line_number(
257                "basic_type.cpp",
258                "// Here is the line we will break on to check variables.",
259            )
260        lldbutil.run_break_set_by_file_and_line(
261            self, "basic_type.cpp", break_line, num_expected_locations=1, loc_exact=True
262        )
263
264        self.runCmd("run", RUN_SUCCEEDED)
265        self.expect(
266            "process status",
267            STOPPED_DUE_TO_BREAKPOINT,
268            substrs=["stop reason = breakpoint", " at basic_type.cpp:%d" % break_line],
269        )
270
271        # self.runCmd("frame variable --show-types")
272
273        # Now iterate through the golden list, comparing against the output from
274        # 'expr var'.
275        for var, val in gl:
276            self.runCmd("expression %s" % var)
277            output = self.res.GetOutput()
278
279            # The input type is in a canonical form as a set of named atoms.
280            # The display type string must contain each and every element.
281            #
282            # Example:
283            #     runCmd: expr a
284            #     output: (double) $0 = 1100.12
285            #
286            try:
287                dt = re.match("^\((.*)\) \$[0-9]+ = ", output).group(1)
288            except:
289                self.fail(self.DATA_TYPE_GROKKED)
290
291            # Expect the display type string to contain each and every atoms.
292            self.expect(
293                dt,
294                "Display type: '%s' must contain the type atoms: '%s'" % (dt, atoms),
295                exe=False,
296                substrs=list(atoms),
297            )
298
299            # The val part must match, too.
300            valPart = ("'%s'" if quotedDisplay else "%s") % val
301            self.expect(output, Msg(var, val, False), exe=False, substrs=[valPart])
302