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