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