From e19a572345b8ea764582209bf8678d56c32874c9 Mon Sep 17 00:00:00 2001 From: Craig Rodrigues Date: Fri, 11 Nov 2016 00:52:06 -0800 Subject: [PATCH] Fix whitespace to reduce twistedchecker warnings --- src/twisted/conch/avatar.py | 3 +++ src/twisted/conch/checkers.py | 8 ++++++- src/twisted/conch/endpoints.py | 2 ++ src/twisted/conch/interfaces.py | 23 +++++++++++++++++++-- src/twisted/conch/ls.py | 14 ++++++------- src/twisted/conch/manhole.py | 17 +++++++++++++++ src/twisted/conch/manhole_ssh.py | 9 +++++++- src/twisted/conch/mixin.py | 6 ++++-- src/twisted/conch/recvline.py | 12 +++++++---- src/twisted/conch/stdio.py | 3 +++ src/twisted/conch/tap.py | 2 +- src/twisted/conch/test/test_cftp.py | 31 ++++++++++++++++++++++++++++ src/twisted/conch/test/test_conch.py | 4 +++- 13 files changed, 115 insertions(+), 19 deletions(-) diff --git a/src/twisted/conch/avatar.py b/src/twisted/conch/avatar.py index 9f638651e87..2f6850dbb8e 100644 --- a/src/twisted/conch/avatar.py +++ b/src/twisted/conch/avatar.py @@ -17,6 +17,7 @@ def __init__(self): self.channelLookup = {} self.subsystemLookup = {} + def lookupChannel(self, channelType, windowSize, maxPacket, data): klass = self.channelLookup.get(channelType, None) if not klass: @@ -26,6 +27,7 @@ def lookupChannel(self, channelType, windowSize, maxPacket, data): remoteMaxPacket=maxPacket, data=data, avatar=self) + def lookupSubsystem(self, subsystem, data): log.msg(repr(self.subsystemLookup)) klass = self.subsystemLookup.get(subsystem, None) @@ -33,6 +35,7 @@ def lookupSubsystem(self, subsystem, data): return False return klass(data, avatar=self) + def gotGlobalRequest(self, requestType, data): # XXX should this use method dispatch? requestType = nativeString(requestType.replace(b'-', b'_')) diff --git a/src/twisted/conch/checkers.py b/src/twisted/conch/checkers.py index ea41dc91add..d4fcf5bc78d 100644 --- a/src/twisted/conch/checkers.py +++ b/src/twisted/conch/checkers.py @@ -152,6 +152,7 @@ def requestAvatarId(self, credentials): d.addErrback(self._ebRequestAvatarId) return d + def _cbRequestAvatarId(self, validKey, credentials): """ Check whether the credentials themselves are valid, now that we know @@ -238,6 +239,7 @@ def checkKey(self, credentials): continue return False + def _ebRequestAvatarId(self, f): if not f.check(UnauthorizedLogin): log.msg(f) @@ -263,6 +265,7 @@ def __init__(self): self.checkers = {} self.successfulCredentials = {} + def get_credentialInterfaces(self): return _keys(self.checkers) @@ -274,6 +277,7 @@ def registerChecker(self, checker, *credentialInterfaces): for credentialInterface in credentialInterfaces: self.checkers[credentialInterface] = checker + def requestAvatarId(self, credentials): """ Part of the L{ICredentialsChecker} interface. Called by a portal with @@ -295,6 +299,7 @@ def requestAvatarId(self, credentials): return defer.fail(UnhandledCredentials("No checker for %s" % \ ', '.join(map(reflect.qual, ifac)))) + def _cbGoodAuthentication(self, avatarId, credentials): """ Called if a checker has verified the credentials. We call our @@ -311,6 +316,7 @@ def _cbGoodAuthentication(self, avatarId, credentials): else: raise error.NotEnoughAuthentication() + def areDone(self, avatarId): """ Override to determine if the authentication is finished for a given @@ -579,7 +585,7 @@ def _verifyKey(self, pubKey, credentials): try: if pubKey.verify(credentials.signature, credentials.sigData): return credentials.username - except: # any error should be treated as a failed login + except: # Any error should be treated as a failed login log.err() raise UnauthorizedLogin('Error while verifying key') diff --git a/src/twisted/conch/endpoints.py b/src/twisted/conch/endpoints.py index ad2c5f63798..bcf0ce2f646 100644 --- a/src/twisted/conch/endpoints.py +++ b/src/twisted/conch/endpoints.py @@ -765,6 +765,7 @@ def _opener(self): @classmethod def _knownHosts(cls): """ + @return: A L{KnownHostsFile} instance pointed at the user's personal I{known hosts} file. @type: L{KnownHostsFile} @@ -830,6 +831,7 @@ def __init__(self, connection): def secureConnection(self): """ + @return: A L{Deferred} that fires synchronously with the already-established connection object. """ diff --git a/src/twisted/conch/interfaces.py b/src/twisted/conch/interfaces.py index b5aede978cc..f4873bcf693 100644 --- a/src/twisted/conch/interfaces.py +++ b/src/twisted/conch/interfaces.py @@ -54,6 +54,8 @@ def gotGlobalRequest(requestType, data): The method is called with arguments of windowSize, maxPacket, data. """ + + class ISession(Interface): def getPty(term, windowSize, modes): @@ -94,6 +96,7 @@ def closed(): """ + class ISFTPServer(Interface): """ SFTP subsystem for server-side communication. @@ -108,6 +111,7 @@ class ISFTPServer(Interface): and represents the logged-in user. """) + def gotVersion(otherVersion, extData): """ Called when the client sends their version info. @@ -123,6 +127,7 @@ def gotVersion(otherVersion, extData): """ return {} + def openFile(filename, flags, attrs): """ Called when the clients asks to open a file. @@ -153,6 +158,7 @@ def openFile(filename, flags, attrs): with the object. """ + def removeFile(filename): """ Remove the given file. @@ -163,6 +169,7 @@ def removeFile(filename): @param filename: the name of the file as a string. """ + def renameFile(oldpath, newpath): """ Rename the given file. @@ -175,6 +182,7 @@ def renameFile(oldpath, newpath): @param newpath: the new file name. """ + def makeDirectory(path, attrs): """ Make a directory. @@ -187,6 +195,7 @@ def makeDirectory(path, attrs): Its meaning is the same as the attrs in the L{openFile} method. """ + def removeDirectory(path): """ Remove a directory (non-recursively) @@ -200,6 +209,7 @@ def removeDirectory(path): @param path: the directory to remove. """ + def openDirectory(path): """ Open a directory for scanning. @@ -231,6 +241,7 @@ def openDirectory(path): @param path: the directory to open. """ + def getAttrs(path, followLinks): """ Return the attributes for the given path. @@ -244,6 +255,7 @@ def getAttrs(path, followLinks): return attributes for the specified path. """ + def setAttrs(path, attrs): """ Set the attributes for the path. @@ -256,6 +268,7 @@ def setAttrs(path, attrs): L{openFile}. """ + def readLink(path): """ Find the root of a set of symbolic links. @@ -266,6 +279,7 @@ def readLink(path): @param path: the path of the symlink to read. """ + def makeLink(linkPath, targetPath): """ Create a symbolic link. @@ -277,6 +291,7 @@ def makeLink(linkPath, targetPath): @param targetPath: the path of the target of the link as a string. """ + def realPath(path): """ Convert any path to an absolute path. @@ -287,6 +302,7 @@ def realPath(path): @param path: the path to convert as a string. """ + def extendedRequest(extendedName, extendedData): """ This is the extension mechanism for SFTP. The other side can send us @@ -338,6 +354,7 @@ def matchesHost(hostname): def toString(): """ + @return: a serialized string representation of this entry, suitable for inclusion in a known_hosts file. (Newline not included.) @@ -360,6 +377,7 @@ def close(): Deferred that is called back when the close succeeds. """ + def readChunk(offset, length): """ Read from the file. @@ -375,6 +393,7 @@ def readChunk(offset, length): this should read the requested number (up to the end of the file). """ + def writeChunk(offset, data): """ Write to the file. @@ -386,6 +405,7 @@ def writeChunk(offset, data): @param data: a string that is the data to write. """ + def getAttrs(): """ Return the attributes for the file. @@ -394,6 +414,7 @@ def getAttrs(): argument to L{openFile} or a L{Deferred} that is called back with same. """ + def setAttrs(attrs): """ Set the attributes for the file. @@ -404,5 +425,3 @@ def setAttrs(attrs): @param attrs: a dictionary in the same format as the attrs argument to L{openFile}. """ - - diff --git a/src/twisted/conch/ls.py b/src/twisted/conch/ls.py index a8a9e79b38c..85da665dc93 100644 --- a/src/twisted/conch/ls.py +++ b/src/twisted/conch/ls.py @@ -8,7 +8,7 @@ from time import time, strftime, localtime from twisted.python.compat import _PY3 -# locale-independent month names to use instead of strftime's +# Locale-independent month names to use instead of strftime's _MONTH_NAMES = dict(list(zip( list(range(1, 13)), "Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split()))) @@ -30,19 +30,19 @@ def lsLine(name, s): elif stat.S_ISLNK(ft): perms[0] = ord('l') elif stat.S_ISSOCK(ft): perms[0] = ord('s') else: perms[0] = ord('!') - # user + # User if mode&stat.S_IRUSR:perms[1] = ord('r') if mode&stat.S_IWUSR:perms[2] = ord('w') if mode&stat.S_IXUSR:perms[3] = ord('x') - # group + # Group if mode&stat.S_IRGRP:perms[4] = ord('r') if mode&stat.S_IWGRP:perms[5] = ord('w') if mode&stat.S_IXGRP:perms[6] = ord('x') - # other + # Other if mode&stat.S_IROTH:perms[7] = ord('r') if mode&stat.S_IWOTH:perms[8] = ord('w') if mode&stat.S_IXOTH:perms[9] = ord('x') - # suid/sgid + # Suid/sgid if mode&stat.S_ISUID: if perms[3] == ord('x'): perms[3] = ord('s') else: perms[3] = ord('S') @@ -67,10 +67,10 @@ def lsLine(name, s): str(s.st_size).rjust(8), ' ', ] - # need to specify the month manually, as strftime depends on locale + # Need to specify the month manually, as strftime depends on locale ttup = localtime(s.st_mtime) sixmonths = 60 * 60 * 24 * 7 * 26 - if s.st_mtime + sixmonths < time(): # last edited more than 6mo ago + if s.st_mtime + sixmonths < time(): # Last edited more than 6mo ago strtime = strftime("%%s %d %Y ", ttup) else: strtime = strftime("%%s %d %H:%M ", ttup) diff --git a/src/twisted/conch/manhole.py b/src/twisted/conch/manhole.py index bc3f1950669..507e3e9b620 100644 --- a/src/twisted/conch/manhole.py +++ b/src/twisted/conch/manhole.py @@ -35,15 +35,20 @@ class FileWrapper: def __init__(self, o): self.o = o + def flush(self): pass + def write(self, data): self.o.addOutput(data.replace('\r\n', '\n')) + def writelines(self, lines): self.write(''.join(lines)) + + class ManholeInterpreter(code.InteractiveInterpreter): """Interactive Interpreter with special output and Deferred support. @@ -65,10 +70,12 @@ def __init__(self, handler, locals=None, filename=""): self.filename = filename self.resetBuffer() + def resetBuffer(self): """Reset the input buffer.""" self.buffer = [] + def push(self, line): """Push a line to the interpreter. @@ -90,6 +97,7 @@ def push(self, line): self.resetBuffer() return more + def runcode(self, *a, **kw): orighook, sys.displayhook = sys.displayhook, self.displayhook try: @@ -101,6 +109,7 @@ def runcode(self, *a, **kw): finally: sys.displayhook = orighook + def displayhook(self, obj): self.locals['_'] = obj if isinstance(obj, defer.Deferred): @@ -120,19 +129,24 @@ def displayhook(self, obj): elif obj is not None: self.write(repr(obj)) + def _cbDisplayDeferred(self, result, k, obj): self.write("Deferred #%d called back: %r" % (k, result), True) del self._pendingDeferreds[id(obj)] return result + def _ebDisplayDeferred(self, failure, k, obj): self.write("Deferred #%d failed: %r" % (k, failure.getErrorMessage()), True) del self._pendingDeferreds[id(obj)] return failure + def write(self, data, async=False): self.handler.addOutput(data, async) + + CTRL_C = '\x03' CTRL_D = '\x04' CTRL_BACKSLASH = '\x1c' @@ -140,6 +154,8 @@ def write(self, data, async=False): CTRL_A = '\x01' CTRL_E = '\x05' + + class Manhole(recvline.HistoricRecvLine): """Mediator between a fancy line source and an interactive interpreter. @@ -157,6 +173,7 @@ def __init__(self, namespace=None): if namespace is not None: self.namespace = namespace.copy() + def connectionMade(self): recvline.HistoricRecvLine.connectionMade(self) self.interpreter = ManholeInterpreter(self, self.namespace) diff --git a/src/twisted/conch/manhole_ssh.py b/src/twisted/conch/manhole_ssh.py index e72da367587..84b242f24e9 100644 --- a/src/twisted/conch/manhole_ssh.py +++ b/src/twisted/conch/manhole_ssh.py @@ -18,7 +18,8 @@ class _Glue: - """A feeble class for making one attribute look like another. + """ + A feeble class for making one attribute look like another. This should be replaced with a real class at some point, probably. Try not to write new code that uses it. @@ -26,6 +27,7 @@ class _Glue: def __init__(self, **kw): self.__dict__.update(kw) + def __getattr__(self, name): raise AttributeError(self.name, "has no attribute", name) @@ -70,15 +72,18 @@ class TerminalSession(components.Adapter): def getPty(self, term, windowSize, attrs): self.height, self.width = windowSize[:2] + def openShell(self, proto): self.transportFactory( proto, self.chainedProtocolFactory(), iconch.IConchUser(self.original), self.width, self.height) + def execCommand(self, proto, cmd): raise econch.ConchError("Cannot execute commands") + def closed(self): pass @@ -112,10 +117,12 @@ def _getAvatar(self, avatarId): return user + def __init__(self, transportFactory=None): if transportFactory is not None: self.transportFactory = transportFactory + def requestAvatar(self, avatarId, mind, *interfaces): for i in interfaces: if i is iconch.IConchUser: diff --git a/src/twisted/conch/mixin.py b/src/twisted/conch/mixin.py index 99439cf1475..976e9ad18f6 100644 --- a/src/twisted/conch/mixin.py +++ b/src/twisted/conch/mixin.py @@ -14,7 +14,8 @@ from twisted.internet import reactor class BufferingMixin: - """Mixin which adds write buffering. + """ + Mixin which adds write buffering. """ _delayedWriteCall = None data = None @@ -46,7 +47,8 @@ def write(self, data): def flush(self): - """Flush the buffer immediately. + """ + Flush the buffer immediately. """ self._delayedWriteCall = None self.transport.writeSequence(self.data) diff --git a/src/twisted/conch/recvline.py b/src/twisted/conch/recvline.py index 293d7db7b85..29ed73b2f06 100644 --- a/src/twisted/conch/recvline.py +++ b/src/twisted/conch/recvline.py @@ -19,7 +19,8 @@ _counters = {} class Logging(object): - """Wrapper which logs attribute lookups. + """ + Wrapper which logs attribute lookups. This was useful in debugging something, I guess. I forget what. It can probably be deleted or moved somewhere more appropriate. @@ -51,7 +52,8 @@ def __getattribute__(self, name): @implementer(insults.ITerminalTransport) class TransportSequence(object): - """An L{ITerminalTransport} implementation which forwards calls to + """ + An L{ITerminalTransport} implementation which forwards calls to one or more other L{ITerminalTransport}s. This is a cheap way for servers to keep track of the state they @@ -86,7 +88,8 @@ def %s(self, *a, **kw): class LocalTerminalBufferMixin(object): - """A mixin for RecvLine subclasses which records the state of the terminal. + """ + A mixin for RecvLine subclasses which records the state of the terminal. This is accomplished by performing all L{ITerminalTransport} operations on both the transport passed to makeConnection and an instance of helper.TerminalBuffer. @@ -109,7 +112,8 @@ def __str__(self): class RecvLine(insults.TerminalProtocol): - """L{TerminalProtocol} which adds line editing features. + """ + L{TerminalProtocol} which adds line editing features. Clients will be prompted for lines of input with all the usual features: character echoing, left and right arrow support for diff --git a/src/twisted/conch/stdio.py b/src/twisted/conch/stdio.py index 580e5280776..04cd6212723 100644 --- a/src/twisted/conch/stdio.py +++ b/src/twisted/conch/stdio.py @@ -19,11 +19,14 @@ class UnexpectedOutputError(Exception): pass + + class TerminalProcessProtocol(protocol.ProcessProtocol): def __init__(self, proto): self.proto = proto self.onConnection = defer.Deferred() + def connectionMade(self): self.proto.makeConnection(self) self.onConnection.callback(None) diff --git a/src/twisted/conch/tap.py b/src/twisted/conch/tap.py index d610c835c19..f622854a068 100644 --- a/src/twisted/conch/tap.py +++ b/src/twisted/conch/tap.py @@ -37,7 +37,7 @@ class Options(usage.Options, strcred.AuthOptionMixin): def __init__(self, *a, **kw): usage.Options.__init__(self, *a, **kw) - # call the default addCheckers (for backwards compatibility) that will + # Call the default addCheckers (for backwards compatibility) that will # be used if no --auth option is provided - note that conch's # UNIXPasswordDatabase is used, instead of twisted.plugins.cred_unix's # checker diff --git a/src/twisted/conch/test/test_cftp.py b/src/twisted/conch/test/test_cftp.py index cc93faee0d3..66401953934 100644 --- a/src/twisted/conch/test/test_cftp.py +++ b/src/twisted/conch/test/test_cftp.py @@ -80,6 +80,7 @@ class ListingTests(TestCase): if getattr(time, 'tzset', None) is None: skip = "Cannot test timestamp formatting code without time.tzset" + def setUp(self): """ Patch the L{ls} module's time function so the results of L{lsLine} are @@ -767,11 +768,13 @@ class FileTransferTestRealm: def __init__(self, testDir): self.testDir = testDir + def requestAvatar(self, avatarID, mind, *interfaces): a = FileTransferTestAvatar(self.testDir) return interfaces[0], a, lambda: None + class SFTPTestProcess(protocol.ProcessProtocol): """ Protocol for testing cftp. Provides an interface between Python (where all @@ -790,6 +793,7 @@ def __init__(self, onOutReceived): self._expectingCommand = None self._processEnded = False + def clearBuffer(self): """ Clear any buffered data received from stdout. Should be private. @@ -798,6 +802,7 @@ def clearBuffer(self): self._linesReceived = [] self._lineBuffer = b'' + def outReceived(self, data): """ Called by Twisted when the cftp client prints data to stdout. @@ -815,6 +820,7 @@ def outReceived(self, data): self.buffer += data self._checkForCommand() + def _checkForCommand(self): prompt = b'cftp> ' if self._expectingCommand and self._lineBuffer == prompt: @@ -825,18 +831,21 @@ def _checkForCommand(self): d, self._expectingCommand = self._expectingCommand, None d.callback(buf) + def errReceived(self, data): """ Called by Twisted when the cftp client prints data to stderr. """ log.msg('err: %s' % data) + def getBuffer(self): """ Return the contents of the buffer of data received from stdout. """ return self.buffer + def runCommand(self, command): """ Issue the given command via the cftp client. Return a C{Deferred} that @@ -855,6 +864,7 @@ def runCommand(self, command): self.transport.write(command + b'\n') return self._expectingCommand + def runScript(self, commands): """ Run each command in sequence and return a Deferred that fires when all @@ -870,6 +880,7 @@ def runScript(self, commands): dl = [sem.run(self.runCommand, command) for command in commands] return defer.gatherResults(dl) + def killProcess(self): """ Kill the process if it is still running. @@ -885,6 +896,7 @@ def killProcess(self): self.transport.signalProcess('KILL') return self.onProcessEnd + def processEnded(self, reason): """ Called by Twisted when the cftp client process ends. @@ -895,6 +907,7 @@ def processEnded(self, reason): d.callback(None) + class CFTPClientTestBase(SFTPTestBase): def setUp(self): with open('dsa_test.pub', 'wb') as f: @@ -906,6 +919,7 @@ def setUp(self): f.write(b'127.0.0.1 ' + test_ssh.publicRSA_openssh) return SFTPTestBase.setUp(self) + def startServer(self): realm = FileTransferTestRealm(self.testDir) p = portal.Portal(realm) @@ -914,6 +928,7 @@ def startServer(self): fac.portal = p self.server = reactor.listenTCP(0, fac, interface="127.0.0.1") + def stopServer(self): if not hasattr(self.server.factory, 'proto'): return self._cbStopServer(None) @@ -923,9 +938,11 @@ def stopServer(self): d.addCallback(self._cbStopServer) return d + def _cbStopServer(self, ignored): return defer.maybeDeferred(self.server.stopListening) + def tearDown(self): for f in ['dsa_test.pub', 'dsa_test', 'kh_test']: try: @@ -984,17 +1001,20 @@ def setUp(self): env=encodedEnv) return d + def tearDown(self): d = self.stopServer() d.addCallback(lambda _: self.processProtocol.killProcess()) return d + def _killProcess(self, ignored): try: self.processProtocol.transport.signalProcess('KILL') except error.ProcessExitedAlready: pass + def runCommand(self, command): """ Run the given command with the cftp client. Return a C{Deferred} that @@ -1003,6 +1023,7 @@ def runCommand(self, command): """ return self.processProtocol.runCommand(command) + def runScript(self, *commands): """ Run the given commands with the cftp client. Returns a C{Deferred} @@ -1011,6 +1032,7 @@ def runScript(self, *commands): """ return self.processProtocol.runScript(commands) + def testCdPwd(self): """ Test that 'pwd' reports the current remote directory, that 'lpwd' @@ -1035,6 +1057,7 @@ def cmdOutput(output): [homeDir.path, os.getcwd(), '', homeDir.path]) return d + def testChAttrs(self): """ Check that 'ls -l' output includes the access permissions and that @@ -1085,6 +1108,7 @@ def testHelp(self): d.addCallback(self.assertEqual, helpText) return d + def assertFilesEqual(self, name1, name2, msg=None): """ Assert that the files at C{name1} and C{name2} contain exactly the @@ -1310,10 +1334,12 @@ def setUp(self): CFTPClientTestBase.setUp(self) self.startServer() + def tearDown(self): CFTPClientTestBase.tearDown(self) return self.stopServer() + def _getBatchOutput(self, f): fn = self.mktemp() with open(fn, 'w') as fp: @@ -1344,6 +1370,7 @@ def _cleanup(res): return d + def testBatchFile(self): """Test whether batch file function of cftp ('cftp -b batchfile'). This works by treating the file as a list of commands to be run. @@ -1352,6 +1379,7 @@ def testBatchFile(self): ls exit """ + def _cbCheckResult(res): res = res.split(b'\n') log.msg('RES %s' % repr(res)) @@ -1363,6 +1391,7 @@ def _cbCheckResult(res): d.addCallback(_cbCheckResult) return d + def testError(self): """Test that an error in the batch file stops running the batch. """ @@ -1370,6 +1399,7 @@ def testError(self): pwd exit """ + def _cbCheckResult(res): self.assertNotIn(self.testDir.asBytesMode().path, res) @@ -1377,6 +1407,7 @@ def _cbCheckResult(res): d.addCallback(_cbCheckResult) return d + def testIgnoredError(self): """Test that a minus sign '-' at the front of a line ignores any errors. diff --git a/src/twisted/conch/test/test_conch.py b/src/twisted/conch/test/test_conch.py index 2a0ecc817dd..45d664b86bc 100644 --- a/src/twisted/conch/test/test_conch.py +++ b/src/twisted/conch/test/test_conch.py @@ -74,6 +74,7 @@ class StdioInteractingSessionTests(unittest.TestCase): if StdioInteractingSession is None: skip = _reason + def test_eofReceived(self): """ L{twisted.conch.scripts.conch.SSHSession.eofReceived} loses the @@ -238,7 +239,6 @@ class ConchTestForwardingPort(protocol.Protocol): is ended. """ - def __init__(self, protocol, data): """ @type protocol: L{ConchTestForwardingProcess} @@ -510,6 +510,7 @@ class OpenSSHClientMixin: if not which('ssh'): skip = "no ssh command-line client available" + def execute(self, remoteCommand, process, sshArgs=''): """ Connects to the SSH server started in L{ConchServerSetupMixin.setUp} by @@ -658,6 +659,7 @@ class CmdLineClientTests(ForwardingMixin, unittest.TestCase): if runtime.platformType == 'win32': skip = "can't run cmdline client on win32" + def execute(self, remoteCommand, process, sshArgs='', conchArgs=None): """ As for L{OpenSSHClientTestCase.execute}, except it runs the 'conch'