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