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