1"""Test Python APIs for process IO.""" 2 3from __future__ import print_function 4 5 6import os 7import lldb 8from lldbsuite.test.decorators import * 9from lldbsuite.test.lldbtest import * 10from lldbsuite.test import lldbutil 11 12 13class ProcessIOTestCase(TestBase): 14 15 mydir = TestBase.compute_mydir(__file__) 16 NO_DEBUG_INFO_TESTCASE = True 17 18 def setup_test(self): 19 # Get the full path to our executable to be debugged. 20 self.exe = self.getBuildArtifact("process_io") 21 self.local_input_file = self.getBuildArtifact("input.txt") 22 self.local_output_file = self.getBuildArtifact("output.txt") 23 self.local_error_file = self.getBuildArtifact("error.txt") 24 25 self.input_file = os.path.join( 26 self.get_process_working_directory(), "input.txt") 27 self.output_file = os.path.join( 28 self.get_process_working_directory(), "output.txt") 29 self.error_file = os.path.join( 30 self.get_process_working_directory(), "error.txt") 31 self.lines = ["Line 1", "Line 2", "Line 3"] 32 33 @skipIfWindows # stdio manipulation unsupported on Windows 34 @add_test_categories(['pyapi']) 35 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 36 @skipIfDarwinEmbedded # I/O redirection like this is not supported on remote iOS devices yet <rdar://problem/54581135> 37 def test_stdin_by_api(self): 38 """Exercise SBProcess.PutSTDIN().""" 39 self.setup_test() 40 self.build() 41 self.create_target() 42 self.run_process(True) 43 output = self.process.GetSTDOUT(1000) 44 self.check_process_output(output, output) 45 46 @skipIfWindows # stdio manipulation unsupported on Windows 47 @add_test_categories(['pyapi']) 48 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 49 def test_stdin_redirection(self): 50 """Exercise SBLaunchInfo::AddOpenFileAction() for STDIN without specifying STDOUT or STDERR.""" 51 self.setup_test() 52 self.build() 53 self.create_target() 54 self.redirect_stdin() 55 self.run_process(False) 56 output = self.process.GetSTDOUT(1000) 57 self.check_process_output(output, output) 58 59 @skipIfWindows # stdio manipulation unsupported on Windows 60 @add_test_categories(['pyapi']) 61 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 62 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 63 def test_stdout_redirection(self): 64 """Exercise SBLaunchInfo::AddOpenFileAction() for STDOUT without specifying STDIN or STDERR.""" 65 self.setup_test() 66 self.build() 67 self.create_target() 68 self.redirect_stdout() 69 self.run_process(True) 70 output = self.read_output_file_and_delete() 71 error = self.process.GetSTDOUT(1000) 72 self.check_process_output(output, error) 73 74 @skipIfWindows # stdio manipulation unsupported on Windows 75 @add_test_categories(['pyapi']) 76 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 77 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 78 def test_stderr_redirection(self): 79 """Exercise SBLaunchInfo::AddOpenFileAction() for STDERR without specifying STDIN or STDOUT.""" 80 self.setup_test() 81 self.build() 82 self.create_target() 83 self.redirect_stderr() 84 self.run_process(True) 85 output = self.process.GetSTDOUT(1000) 86 error = self.read_error_file_and_delete() 87 self.check_process_output(output, error) 88 89 @skipIfWindows # stdio manipulation unsupported on Windows 90 @add_test_categories(['pyapi']) 91 @expectedFlakeyLinux(bugnumber="llvm.org/pr26437") 92 @skipIfDarwinEmbedded # debugserver can't create/write files on the device 93 def test_stdout_stderr_redirection(self): 94 """Exercise SBLaunchInfo::AddOpenFileAction() for STDOUT and STDERR without redirecting STDIN.""" 95 self.setup_test() 96 self.build() 97 self.create_target() 98 self.redirect_stdout() 99 self.redirect_stderr() 100 self.run_process(True) 101 output = self.read_output_file_and_delete() 102 error = self.read_error_file_and_delete() 103 self.check_process_output(output, error) 104 105 # target_file - path on local file system or remote file system if running remote 106 # local_file - path on local system 107 def read_file_and_delete(self, target_file, local_file): 108 if lldb.remote_platform: 109 self.runCmd('platform get-file "{remote}" "{local}"'.format( 110 remote=target_file, local=local_file)) 111 112 self.assertTrue( 113 os.path.exists(local_file), 114 'Make sure "{local}" file exists'.format( 115 local=local_file)) 116 f = open(local_file, 'r') 117 contents = f.read() 118 f.close() 119 120 # TODO: add 'platform delete-file' file command 121 # if lldb.remote_platform: 122 # self.runCmd('platform delete-file "{remote}"'.format(remote=target_file)) 123 os.unlink(local_file) 124 return contents 125 126 def read_output_file_and_delete(self): 127 return self.read_file_and_delete( 128 self.output_file, self.local_output_file) 129 130 def read_error_file_and_delete(self): 131 return self.read_file_and_delete( 132 self.error_file, self.local_error_file) 133 134 def create_target(self): 135 '''Create the target and launch info that will be used by all tests''' 136 self.target = self.dbg.CreateTarget(self.exe) 137 self.launch_info = lldb.SBLaunchInfo([self.exe]) 138 self.launch_info.SetWorkingDirectory( 139 self.get_process_working_directory()) 140 141 def redirect_stdin(self): 142 '''Redirect STDIN (file descriptor 0) to use our input.txt file 143 144 Make the input.txt file to use when redirecting STDIN, setup a cleanup action 145 to delete the input.txt at the end of the test in case exceptions are thrown, 146 and redirect STDIN in the launch info.''' 147 f = open(self.local_input_file, 'w') 148 for line in self.lines: 149 f.write(line + "\n") 150 f.close() 151 152 if lldb.remote_platform: 153 self.runCmd('platform put-file "{local}" "{remote}"'.format( 154 local=self.local_input_file, remote=self.input_file)) 155 156 # This is the function to remove the custom formats in order to have a 157 # clean slate for the next test case. 158 def cleanup(): 159 os.unlink(self.local_input_file) 160 # TODO: add 'platform delete-file' file command 161 # if lldb.remote_platform: 162 # self.runCmd('platform delete-file "{remote}"'.format(remote=self.input_file)) 163 164 # Execute the cleanup function during test case tear down. 165 self.addTearDownHook(cleanup) 166 self.launch_info.AddOpenFileAction(0, self.input_file, True, False) 167 168 def redirect_stdout(self): 169 '''Redirect STDOUT (file descriptor 1) to use our output.txt file''' 170 self.launch_info.AddOpenFileAction(1, self.output_file, False, True) 171 172 def redirect_stderr(self): 173 '''Redirect STDERR (file descriptor 2) to use our error.txt file''' 174 self.launch_info.AddOpenFileAction(2, self.error_file, False, True) 175 176 def run_process(self, put_stdin): 177 '''Run the process to completion and optionally put lines to STDIN via the API if "put_stdin" is True''' 178 # Set the breakpoints 179 self.breakpoint = self.target.BreakpointCreateBySourceRegex( 180 'Set breakpoint here', lldb.SBFileSpec("main.c")) 181 self.assertTrue( 182 self.breakpoint.GetNumLocations() > 0, 183 VALID_BREAKPOINT) 184 185 # Launch the process, and do not stop at the entry point. 186 error = lldb.SBError() 187 # This should launch the process and it should exit by the time we get back 188 # because we have synchronous mode enabled 189 self.process = self.target.Launch(self.launch_info, error) 190 191 self.assertTrue( 192 error.Success(), 193 "Make sure process launched successfully") 194 self.assertTrue(self.process, PROCESS_IS_VALID) 195 196 if self.TraceOn(): 197 print("process launched.") 198 199 # Frame #0 should be at our breakpoint. 200 threads = lldbutil.get_threads_stopped_at_breakpoint( 201 self.process, self.breakpoint) 202 203 self.assertTrue(len(threads) == 1) 204 self.thread = threads[0] 205 self.frame = self.thread.frames[0] 206 self.assertTrue(self.frame, "Frame 0 is valid.") 207 208 if self.TraceOn(): 209 print("process stopped at breakpoint, sending STDIN via LLDB API.") 210 211 # Write data to stdin via the public API if we were asked to 212 if put_stdin: 213 for line in self.lines: 214 self.process.PutSTDIN(line + "\n") 215 216 # Let process continue so it will exit 217 self.process.Continue() 218 state = self.process.GetState() 219 self.assertTrue(state == lldb.eStateExited, PROCESS_IS_VALID) 220 221 def check_process_output(self, output, error): 222 # Since we launched the process without specifying stdin/out/err, 223 # a pseudo terminal is used for stdout/err, and we are satisfied 224 # once "input line=>1" appears in stdout. 225 # See also main.c. 226 if self.TraceOn(): 227 print("output = '%s'" % output) 228 print("error = '%s'" % error) 229 230 for line in self.lines: 231 check_line = 'input line to stdout: %s' % (line) 232 self.assertTrue( 233 check_line in output, 234 "verify stdout line shows up in STDOUT") 235 for line in self.lines: 236 check_line = 'input line to stderr: %s' % (line) 237 self.assertTrue( 238 check_line in error, 239 "verify stderr line shows up in STDERR") 240