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        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    def build_and_run_expr(self, source, atoms, bc=False, qd=False):
68        self.build_and_run_with_source_atoms_expr(
69            source, atoms, expr=True, bc=bc, qd=qd)
70
71    def build_and_run_with_source_atoms_expr(
72            self, source, atoms, expr, bc=False, qd=False):
73        # See also Makefile and basic_type.cpp:177.
74        if bc:
75            d = {'CXX_SOURCES': source, 'EXE': self.exe_name,
76                 'CFLAGS_EXTRAS': '-DTEST_BLOCK_CAPTURED_VARS'}
77        else:
78            d = {'CXX_SOURCES': source, 'EXE': self.exe_name}
79        self.build(dictionary=d)
80        self.setTearDownCleanup(dictionary=d)
81        if expr:
82            self.generic_type_expr_tester(
83                self.exe_name, atoms, blockCaptured=bc, quotedDisplay=qd)
84        else:
85            self.generic_type_tester(
86                self.exe_name,
87                atoms,
88                blockCaptured=bc,
89                quotedDisplay=qd)
90
91    def process_launch_o(self):
92        # process launch command output redirect always goes to host the
93        # process is running on
94        if lldb.remote_platform:
95            # process launch -o requires a path that is valid on the target
96            self.assertIsNotNone(lldb.remote_platform.GetWorkingDirectory())
97            remote_path = lldbutil.append_to_process_working_directory(self,
98                "lldb-stdout-redirect.txt")
99            self.runCmd(
100                'process launch -- {remote}'.format(remote=remote_path))
101            # copy remote_path to local host
102            self.runCmd('platform get-file {remote} "{local}"'.format(
103                remote=remote_path, local=self.golden_filename))
104        else:
105            self.runCmd(
106                'process launch -o "{local}"'.format(local=self.golden_filename))
107
108    def get_golden_list(self, blockCaptured=False):
109        with open(self.golden_filename, 'r') as f:
110            go = f.read()
111
112        golden_list = []
113        # Scan the golden output line by line, looking for the pattern:
114        #
115        #     variable = 'value'
116        #
117        for line in go.split(os.linesep):
118            # We'll ignore variables of array types from inside a block.
119            if blockCaptured and '[' in line:
120                continue
121            match = self.pattern.search(line)
122            if match:
123                var, val = match.group(1), match.group(2)
124                golden_list.append((var, val))
125        return golden_list
126
127    def generic_type_tester(
128            self,
129            exe_name,
130            atoms,
131            quotedDisplay=False,
132            blockCaptured=False):
133        """Test that variables with basic types are displayed correctly."""
134        self.runCmd("file %s" % self.getBuildArtifact(exe_name),
135                    CURRENT_EXECUTABLE_SET)
136
137        # First, capture the golden output emitted by the oracle, i.e., the
138        # series of printf statements.
139        self.process_launch_o()
140
141        # This golden list contains a list of (variable, value) pairs extracted
142        # from the golden output.
143        gl = self.get_golden_list(blockCaptured)
144
145        # This test uses a #include of "basic_type.cpp" so we need to enable
146        # always setting inlined breakpoints.
147        self.runCmd('settings set target.inline-breakpoint-strategy always')
148
149        # Inherit TCC permissions. We can leave this set.
150        self.runCmd('settings set target.inherit-tcc true')
151
152        # Kill rather than detach from the inferior if something goes wrong.
153        self.runCmd('settings set target.detach-on-error false')
154
155        # And add hooks to restore the settings during tearDown().
156        self.addTearDownHook(lambda: self.runCmd(
157            "settings set target.inline-breakpoint-strategy headers"))
158
159        # Bring the program to the point where we can issue a series of
160        # 'frame variable --show-types' command.
161        if blockCaptured:
162            break_line = line_number(
163                "basic_type.cpp",
164                "// Break here to test block captured variables.")
165        else:
166            break_line = line_number(
167                "basic_type.cpp",
168                "// Here is the line we will break on to check variables.")
169        lldbutil.run_break_set_by_file_and_line(
170            self,
171            "basic_type.cpp",
172            break_line,
173            num_expected_locations=1,
174            loc_exact=True)
175
176        self.runCmd("run", RUN_SUCCEEDED)
177        self.expect("process status", STOPPED_DUE_TO_BREAKPOINT,
178                    substrs=["stop reason = breakpoint",
179                             " at basic_type.cpp:%d" % break_line,])
180
181        #self.runCmd("frame variable --show-types")
182
183        # Now iterate through the golden list, comparing against the output from
184        # 'frame variable --show-types var'.
185        for var, val in gl:
186            self.runCmd("frame variable --show-types %s" % var)
187            output = self.res.GetOutput()
188
189            # The input type is in a canonical form as a set of named atoms.
190            # The display type string must contain each and every element.
191            #
192            # Example:
193            #     runCmd: frame variable --show-types a_array_bounded[0]
194            #     output: (char) a_array_bounded[0] = 'a'
195            #
196            try:
197                dt = re.match("^\((.*)\)", output).group(1)
198            except:
199                self.fail(self.DATA_TYPE_GROKKED)
200
201            # Expect the display type string to contain each and every atoms.
202            self.expect(
203                dt, "Display type: '%s' must contain the type atoms: '%s'" %
204                (dt, atoms), exe=False, substrs=list(atoms))
205
206            # The (var, val) pair must match, too.
207            nv = ("%s = '%s'" if quotedDisplay else "%s = %s") % (var, val)
208            self.expect(output, Msg(var, val, True), exe=False,
209                        substrs=[nv])
210
211    def generic_type_expr_tester(
212            self,
213            exe_name,
214            atoms,
215            quotedDisplay=False,
216            blockCaptured=False):
217        """Test that variable expressions with basic types are evaluated correctly."""
218
219        self.runCmd("file %s" % self.getBuildArtifact(exe_name),
220                    CURRENT_EXECUTABLE_SET)
221
222        # First, capture the golden output emitted by the oracle, i.e., the
223        # series of printf statements.
224        self.process_launch_o()
225
226        # This golden list contains a list of (variable, value) pairs extracted
227        # from the golden output.
228        gl = self.get_golden_list(blockCaptured)
229
230        # This test uses a #include of "basic_type.cpp" so we need to enable
231        # always setting inlined breakpoints.
232        self.runCmd('settings set target.inline-breakpoint-strategy always')
233        # And add hooks to restore the settings during tearDown().
234        self.addTearDownHook(lambda: self.runCmd(
235            "settings set target.inline-breakpoint-strategy headers"))
236
237        # Bring the program to the point where we can issue a series of
238        # 'expr' command.
239        if blockCaptured:
240            break_line = line_number(
241                "basic_type.cpp",
242                "// Break here to test block captured variables.")
243        else:
244            break_line = line_number(
245                "basic_type.cpp",
246                "// Here is the line we will break on to check variables.")
247        lldbutil.run_break_set_by_file_and_line(
248            self,
249            "basic_type.cpp",
250            break_line,
251            num_expected_locations=1,
252            loc_exact=True)
253
254        self.runCmd("run", RUN_SUCCEEDED)
255        self.expect("process status", STOPPED_DUE_TO_BREAKPOINT,
256                    substrs=["stop reason = breakpoint",
257                             " at basic_type.cpp:%d" % break_line])
258
259        #self.runCmd("frame variable --show-types")
260
261        # Now iterate through the golden list, comparing against the output from
262        # 'expr var'.
263        for var, val in gl:
264            self.runCmd("expression %s" % var)
265            output = self.res.GetOutput()
266
267            # The input type is in a canonical form as a set of named atoms.
268            # The display type string must contain each and every element.
269            #
270            # Example:
271            #     runCmd: expr a
272            #     output: (double) $0 = 1100.12
273            #
274            try:
275                dt = re.match("^\((.*)\) \$[0-9]+ = ", output).group(1)
276            except:
277                self.fail(self.DATA_TYPE_GROKKED)
278
279            # Expect the display type string to contain each and every atoms.
280            self.expect(
281                dt, "Display type: '%s' must contain the type atoms: '%s'" %
282                (dt, atoms), exe=False, substrs=list(atoms))
283
284            # The val part must match, too.
285            valPart = ("'%s'" if quotedDisplay else "%s") % val
286            self.expect(output, Msg(var, val, False), exe=False,
287                        substrs=[valPart])
288