def __init__(self, out="", err="", exit=0, isatty=None, autostart=True): self.out_file = BytesIO(b(out)) self.err_file = BytesIO(b(err)) self.exit = exit self.isatty = isatty if autostart: self.start()
def _runner(out='', err='', **kwargs): klass = kwargs.pop('klass', _Dummy) runner = klass(Context(config=Config(overrides=kwargs))) if 'exits' in kwargs: runner.returncode = Mock(return_value=kwargs.pop('exits')) out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) runner.read_proc_stdout = out_file.read runner.read_proc_stderr = err_file.read return runner
def wrapper(*args, **kwargs): args = list(args) pty, os, ioctl = args.pop(), args.pop(), args.pop() # Don't actually fork, but pretend we did (with "our" pid differing # depending on be_childish) & give 'parent fd' of 3 (typically, # first allocated non-stdin/out/err FD) pty.fork.return_value = (12345 if be_childish else 0), 3 # We don't really need to care about waiting since not truly # forking/etc, so here we just return a nonzero "pid" + sentinel # wait-status value (used in some tests about WIFEXITED etc) os.waitpid.return_value = None, Mock(name="exitstatus") # Either or both of these may get called, depending... os.WEXITSTATUS.return_value = exit os.WTERMSIG.return_value = exit # If requested, mock isatty to fake out pty detection if isatty is not None: os.isatty.return_value = isatty out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) def fakeread(fileno, count): fd = {3: out_file, 2: err_file}[fileno] ret = fd.read(count) # If asked, fake a Linux-platform trailing I/O error. if not ret and trailing_error: raise trailing_error return ret os.read.side_effect = fakeread if os_close_error: os.close.side_effect = IOError if insert_os: args.append(os) # Do the thing!!! f(*args, **kwargs) # Short-circuit if we raised an error in fakeread() if trailing_error: return # Sanity checks to make sure the stuff we mocked, actually got ran! pty.fork.assert_called_with() # Skip rest of asserts if we pretended to be the child if be_childish: return # Expect a get, and then later set, of terminal window size assert ioctl.call_args_list[0][0][1] == termios.TIOCGWINSZ assert ioctl.call_args_list[1][0][1] == termios.TIOCSWINSZ if not skip_asserts: for name in ("execve", "waitpid"): assert getattr(os, name).called # Ensure at least one of the exit status getters was called assert os.WEXITSTATUS.called or os.WTERMSIG.called # Ensure something closed the pty FD os.close.assert_called_once_with(3)
def multiple_patterns_works_as_expected(self): calls = [call(b('betty')), call(b('carnival'))] # Technically, I'd expect 'betty' to get called before 'carnival', # but under Python 3 it's reliably backwards from Python 2. # In real world situations where each prompt sits & waits for its # response, this probably wouldn't be an issue, so using # any_order=True for now. Thanks again Python 3. self._expect_response( out="beep boop I am a robot", responses={'boop': 'betty', 'robot': 'carnival'}, ).assert_has_calls(calls, any_order=True)
def wrapper(*args, **kwargs): args = list(args) pty, os, ioctl = args.pop(), args.pop(), args.pop() # Don't actually fork, but pretend we did & that main thread is # also the child (pid 0) to trigger execve call; & give 'parent fd' # of 1 (stdout). pty.fork.return_value = (12345 if be_childish else 0), 1 # We don't really need to care about waiting since not truly # forking/etc, so here we just return a nonzero "pid" + sentinel # wait-status value (used in some tests about WIFEXITED etc) os.waitpid.return_value = None, Mock(name="exitstatus") # Either or both of these may get called, depending... os.WEXITSTATUS.return_value = exit os.WTERMSIG.return_value = exit # If requested, mock isatty to fake out pty detection if isatty is not None: os.isatty.return_value = isatty out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) def fakeread(fileno, count): fd = {1: out_file, 2: err_file}[fileno] ret = fd.read(count) # If asked, fake a Linux-platform trailing I/O error. if not ret and trailing_error: raise trailing_error return ret os.read.side_effect = fakeread if insert_os: args.append(os) f(*args, **kwargs) # Short-circuit if we raised an error in fakeread() if trailing_error: return # Sanity checks to make sure the stuff we mocked, actually got ran! # TODO: inject our mocks back into the tests so they can make their # own assertions if desired pty.fork.assert_called_with() # Skip rest of asserts if we pretended to be the child if be_childish: return # Expect a get, and then later set, of terminal window size assert ioctl.call_args_list[0][0][1] == termios.TIOCGWINSZ assert ioctl.call_args_list[1][0][1] == termios.TIOCSWINSZ if not skip_asserts: for name in ("execve", "waitpid"): assert getattr(os, name).called # Ensure at least one of the exit status getters was called assert os.WEXITSTATUS.called or os.WTERMSIG.called
def string_keys_in_responses_kwarg_yield_values_as_stdin_writes(self): self._expect_response( out="the house was empty", responses={ 'empty': 'handed' }, ).assert_called_once_with(b("handed"))
def input_stream_can_be_overridden(self): klass = self._mock_stdin_writer() in_stream = StringIO("Hey, listen!") self._runner(klass=klass).run(_, in_stream=in_stream) # stdin mirroring occurs byte-by-byte calls = list(map(lambda x: call(b(x)), "Hey, listen!")) klass.write_stdin.assert_has_calls(calls, any_order=False)
def regex_keys_also_work(self): self._expect_response( out="technically, it's still debt", responses={ r'tech.*debt': 'pay it down' }, ).assert_called_once_with(b('pay it down'))
def wrapper(*args, **kwargs): args = list(args) pty, os, ioctl = args.pop(), args.pop(), args.pop() # Don't actually fork, but pretend we did & that main thread is # also the child (pid 0) to trigger execve call; & give 'parent fd' # of 1 (stdout). pty.fork.return_value = 0, 1 # We don't really need to care about waiting since not truly # forking/etc, so here we just return a nonzero "pid" + sentinel # wait-status value (used in some tests about WIFEXITED etc) os.waitpid.return_value = None, Mock(name="exitstatus") # Either or both of these may get called, depending... os.WEXITSTATUS.return_value = exit os.WTERMSIG.return_value = exit # If requested, mock isatty to fake out pty detection if isatty is not None: os.isatty.return_value = isatty out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) def fakeread(fileno, count): fd = {1: out_file, 2: err_file}[fileno] ret = fd.read(count) # If asked, fake a Linux-platform trailing I/O error. if not ret and trailing_error: raise trailing_error return ret os.read.side_effect = fakeread if insert_os: args.append(os) f(*args, **kwargs) # Short-circuit if we raised an error in fakeread() if trailing_error: return # Sanity checks to make sure the stuff we mocked, actually got ran! # TODO: inject our mocks back into the tests so they can make their # own assertions if desired pty.fork.assert_called_with() # Expect a get, and then later set, of terminal window size assert ioctl.call_args_list[0][0][1] == termios.TIOCGWINSZ assert ioctl.call_args_list[1][0][1] == termios.TIOCSWINSZ if not skip_asserts: for name in ("execve", "waitpid"): assert getattr(os, name).called # Ensure at least one of the exit status getters was called assert os.WEXITSTATUS.called or os.WTERMSIG.called
def input_defaults_to_sys_stdin(self): # Execute w/ runner class that has a mocked stdin_writer klass = self._mock_stdin_writer() self._runner(klass=klass).run(_) # Check that mocked writer was called w/ expected data # stdin mirroring occurs byte-by-byte calls = list(map(lambda x: call(b(x)), "Text!")) klass.write_stdin.assert_has_calls(calls, any_order=False)
def both_out_and_err_are_scanned(self): bye = call(b("goodbye")) # Would only be one 'bye' if only scanning stdout self._expect_response( out="hello my name is inigo", err="hello how are you", responses={"hello": "goodbye"}, ).assert_has_calls([bye, bye])
def multiple_hits_yields_multiple_responses(self): holla = call(b('how high?')) self._expect_response( out="jump, wait, jump, wait", responses={ 'jump': 'how high?' }, ).assert_has_calls([holla, holla])
def defaults_to_sys_stdin(self): # Execute w/ runner class that has a mocked stdin_writer klass = self._mock_stdin_writer() self._runner(klass=klass).run(_, out_stream=StringIO()) # Check that mocked writer was called w/ expected data # stdin mirroring occurs byte-by-byte calls = list(map(lambda x: call(b(x)), "Text!")) klass.write_stdin.assert_has_calls(calls, any_order=False)
def wrapper(*args, **kwargs): args = list(args) Popen, read, sys_stdin = args.pop(), args.pop(), args.pop() process = Popen.return_value process.returncode = exit process.stdout.fileno.return_value = 1 process.stderr.fileno.return_value = 2 # If requested, mock isatty to fake out pty detection if isatty is not None: sys_stdin.isatty = Mock(return_value=isatty) out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) def fakeread(fileno, count): fd = {1: out_file, 2: err_file}[fileno] return fd.read(count) read.side_effect = fakeread if insert_Popen: args.append(Popen) f(*args, **kwargs)
def wrapper(*args, **kwargs): args = list(args) pty, os, ioctl = args.pop(), args.pop(), args.pop() # Don't actually fork, but pretend we did & that main thread is # also the child (pid 0) to trigger execve call; & give 'parent fd' # of 1 (stdout). pty.fork.return_value = 0, 1 # We don't really need to care about waiting since not truly # forking/etc, so here we just return a nonzero "pid" + dummy value # (normally sent to WEXITSTATUS but we mock that anyway, so.) os.waitpid.return_value = None, None os.WEXITSTATUS.return_value = exit # If requested, mock isatty to fake out pty detection if isatty is not None: os.isatty.return_value = isatty out_file = BytesIO(b(out)) err_file = BytesIO(b(err)) def fakeread(fileno, count): fd = {1: out_file, 2: err_file}[fileno] ret = fd.read(count) # If asked, fake a Linux-platform trailing I/O error. if not ret and trailing_error: raise trailing_error return ret os.read.side_effect = fakeread if insert_os: args.append(os) f(*args, **kwargs) # Short-circuit if we raised an error in fakeread() if trailing_error: return # Sanity checks to make sure the stuff we mocked, actually got ran! # TODO: inject our mocks back into the tests so they can make their # own assertions if desired pty.fork.assert_called_with() # Test the 2nd call to ioctl; the 1st call is doing TIOGSWINSZ eq_(ioctl.call_args_list[1][0][1], termios.TIOCSWINSZ) if not skip_asserts: for name in ('execve', 'waitpid', 'WEXITSTATUS'): assert getattr(os, name).called
def can_be_overridden(self): klass = self._mock_stdin_writer() in_stream = StringIO("Hey, listen!") self._runner(klass=klass).run( _, in_stream=in_stream, out_stream=StringIO(), ) # stdin mirroring occurs byte-by-byte calls = list(map(lambda x: call(b(x)), "Hey, listen!")) klass.write_stdin.assert_has_calls(calls, any_order=False)
def chunk_sizes_smaller_than_patterns_still_work_ok(self): klass = self._mock_stdin_writer() klass.read_chunk_size = 1 # < len('jump') responses = {'jump': 'how high?'} runner = self._runner(klass=klass, out="jump, wait, jump, wait") runner.run(_, responses=responses, hide=True) holla = call(b('how high?')) # Responses happened, period. klass.write_stdin.assert_has_calls([holla, holla]) # And there weren't duplicates! eq_(len(klass.write_stdin.call_args_list), 2)
def patterns_span_multiple_lines(self): output = """ You only call me when you have a problem You never call me Just to say hi """ self._expect_response( out=output, responses={r'call.*problem': 'So sorry'}, ).assert_called_once_with(b('So sorry'))
def multiple_patterns_across_both_streams(self): responses = { 'boop': 'betty', 'robot': 'carnival', 'Destroy': 'your ego', 'humans': 'are awful', } calls = map(lambda x: call(b(x)), responses.values()) # CANNOT assume order due to simultaneous streams. # If we didn't say any_order=True we could get race condition fails self._expect_response( out="beep boop, I am a robot", err="Destroy all humans!", responses=responses, ).assert_has_calls(calls, any_order=True)
def _forward_local(self, kwargs, Client, mocket, select): # Tease out bits of kwargs for use in the mocking/expecting. # But leave it alone for raw passthru to the API call itself. # TODO: unhappy with how much this apes the real code & its sig... local_port = kwargs["local_port"] remote_port = kwargs.get("remote_port", local_port) local_host = kwargs.get("local_host", "localhost") remote_host = kwargs.get("remote_host", "localhost") # These aren't part of the real sig, but this is easier than trying # to reconcile the mock decorators + optional-value kwargs. meh. tunnel_exception = kwargs.pop("tunnel_exception", None) listener_exception = kwargs.pop("listener_exception", False) # Mock setup client = Client.return_value listener_sock = Mock(name="listener_sock") if listener_exception: listener_sock.bind.side_effect = listener_exception data = b("Some data") tunnel_sock = Mock(name="tunnel_sock", recv=lambda n: data) local_addr = Mock() transport = client.get_transport.return_value channel = transport.open_channel.return_value # socket.socket is only called once directly mocket.return_value = listener_sock # The 2nd socket is obtained via an accept() (which should only # fire once & raise EAGAIN after) listener_sock.accept.side_effect = chain( [(tunnel_sock, local_addr)], repeat(socket.error(errno.EAGAIN, "nothing yet")), ) obj = tunnel_sock if tunnel_exception is None else tunnel_exception select.select.side_effect = _select_result(obj) with Connection("host").forward_local(**kwargs): # Make sure we give listener thread enough time to boot up :( # Otherwise we might assert before it does things. (NOTE: # doesn't need to be much, even at 0.01s, 0/100 trials failed # (vs 45/100 with no sleep) time.sleep(0.015) assert client.connect.call_args[1]["hostname"] == "host" listener_sock.setsockopt.assert_called_once_with( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) listener_sock.setblocking.assert_called_once_with(0) listener_sock.bind.assert_called_once_with( (local_host, local_port)) if not listener_exception: listener_sock.listen.assert_called_once_with(1) transport.open_channel.assert_called_once_with( "direct-tcpip", (remote_host, remote_port), local_addr) # Local write to tunnel_sock is implied by its mocked-out # recv() call above... # NOTE: don't assert if explodey; we want to mimic "the only # error that occurred was within the thread" behavior being # tested by thread-exception-handling tests if not (tunnel_exception or listener_exception): channel.sendall.assert_called_once_with(data) # Shutdown, with another sleep because threads. time.sleep(0.015) if not listener_exception: tunnel_sock.close.assert_called_once_with() channel.close.assert_called_once_with() listener_sock.close.assert_called_once_with()
def _forward_local(self, kwargs, Client, mocket, select): # Tease out bits of kwargs for use in the mocking/expecting. # But leave it alone for raw passthru to the API call itself. # TODO: unhappy with how much this apes the real code & its sig... local_port = kwargs['local_port'] remote_port = kwargs.get('remote_port', local_port) local_host = kwargs.get('local_host', 'localhost') remote_host = kwargs.get('remote_host', 'localhost') # These aren't part of the real sig, but this is easier than trying # to reconcile the mock decorators + optional-value kwargs. meh. tunnel_exception = kwargs.pop('tunnel_exception', None) listener_exception = kwargs.pop('listener_exception', False) # Mock setup client = Client.return_value listener_sock = Mock(name='listener_sock') if listener_exception: listener_sock.bind.side_effect = listener_exception data = b("Some data") tunnel_sock = Mock(name='tunnel_sock', recv=lambda n: data) local_addr = Mock() transport = client.get_transport.return_value channel = transport.open_channel.return_value # socket.socket is only called once directly mocket.return_value = listener_sock # The 2nd socket is obtained via an accept() (which should only # fire once & raise EAGAIN after) listener_sock.accept.side_effect = chain( [(tunnel_sock, local_addr)], repeat(socket.error(errno.EAGAIN, "nothing yet")), ) obj = tunnel_sock if tunnel_exception is None else tunnel_exception select.select.side_effect = _select_result(obj) with Connection('host').forward_local(**kwargs): # Make sure we give listener thread enough time to boot up :( # Otherwise we might assert before it does things. (NOTE: # doesn't need to be much, even at 0.01s, 0/100 trials failed # (vs 45/100 with no sleep) time.sleep(0.015) assert client.connect.call_args[1]['hostname'] == 'host' listener_sock.setsockopt.assert_called_once_with( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) listener_sock.setblocking.assert_called_once_with(0) listener_sock.bind.assert_called_once_with( (local_host, local_port) ) if not listener_exception: listener_sock.listen.assert_called_once_with(1) transport.open_channel.assert_called_once_with( 'direct-tcpip', (remote_host, remote_port), local_addr, ) # Local write to tunnel_sock is implied by its mocked-out # recv() call above... # NOTE: don't assert if explodey; we want to mimic "the only # error that occurred was within the thread" behavior being # tested by thread-exception-handling tests if not (tunnel_exception or listener_exception): channel.sendall.assert_called_once_with(data) # Shutdown, with another sleep because threads. time.sleep(0.015) if not listener_exception: tunnel_sock.close.assert_called_once_with() channel.close.assert_called_once_with() listener_sock.close.assert_called_once_with()
def string_keys_in_responses_kwarg_yield_values_as_stdin_writes(self): self._expect_response( out="the house was empty", responses={'empty': 'handed'}, ).assert_called_once_with(b("handed"))
def regex_keys_also_work(self): self._expect_response( out="technically, it's still debt", responses={r'tech.*debt': 'pay it down'}, ).assert_called_once_with(b('pay it down'))
def multiple_hits_yields_multiple_responses(self): holla = call(b('how high?')) self._expect_response( out="jump, wait, jump, wait", responses={'jump': 'how high?'}, ).assert_has_calls([holla, holla])