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