def setUp(self): AvatarTestCase.setUp(self) self.avatar = CodehostingAvatar(self.aliceUserDict, None) # The logging system will try to get the id of avatar.transport, so # let's give it something to take the id of. self.avatar.transport = object() self.reactor = MockReactor() self.session = ExecOnlySession(self.avatar, self.reactor)
def test_environment(self): # The environment for the executed process can be specified in the # ExecOnlySession constructor. session = ExecOnlySession(self.avatar, self.reactor, environment={'FOO': 'BAR'}) protocol = ProcessProtocol() session.execCommand(protocol, 'yes') self.assertEqual({'FOO': 'BAR'}, session.environment) self.assertEqual([(protocol, 'yes', ['yes'], { 'FOO': 'BAR' }, None, None, None, 0, None)], self.reactor.log)
def test_environment(self): # The environment for the executed process can be specified in the # ExecOnlySession constructor. session = ExecOnlySession( self.avatar, self.reactor, environment={'FOO': 'BAR'}) protocol = ProcessProtocol() session.execCommand(protocol, 'yes') self.assertEqual({'FOO': 'BAR'}, session.environment) self.assertEqual( [(protocol, 'yes', ['yes'], {'FOO': 'BAR'}, None, None, None, 0, None)], self.reactor.log)
def test_environmentInGetAvatarAdapter(self): # We can pass the environment into getAvatarAdapter so that it is used # when we adapt the session. adapter = ExecOnlySession.getAvatarAdapter( environment={'FOO': 'BAR'}) session = adapter(self.avatar) self.assertEqual({'FOO': 'BAR'}, session.environment)
def test_getAvatarAdapter(self): # getAvatarAdapter is a convenience classmethod so that # ExecOnlySession can be easily registered as an adapter for Conch # avatars. from twisted.internet import reactor adapter = ExecOnlySession.getAvatarAdapter() session = adapter(self.avatar) self.failUnless(isinstance(session, ExecOnlySession), "ISession(avatar) doesn't adapt to ExecOnlySession. " "Got %r instead." % (session,)) self.assertIs(self.avatar, session.avatar) self.assertIs(reactor, session.reactor)
class TestExecOnlySession(AvatarTestCase): """Tests for ExecOnlySession. Conch delegates responsiblity for executing commands to an object that implements ISession. The smart server only needs to handle `execCommand` and a couple of other book-keeping methods. The methods that relate to running a shell or creating a pseudo-terminal raise NotImplementedErrors. """ def setUp(self): AvatarTestCase.setUp(self) self.avatar = CodehostingAvatar(self.aliceUserDict, None) # The logging system will try to get the id of avatar.transport, so # let's give it something to take the id of. self.avatar.transport = object() self.reactor = MockReactor() self.session = ExecOnlySession(self.avatar, self.reactor) def test_getPtyIsANoOp(self): # getPty is called on the way to establishing a shell. Since we don't # give out shells, it should be a no-op. Raising an exception would # log an OOPS, so we won't do that. self.assertEqual(None, self.session.getPty(None, None, None)) def test_openShellNotImplemented(self): # openShell closes the connection. protocol = MockProcessTransport('bash') self.session.openShell(protocol) self.assertEqual([('writeExtended', connection.EXTENDED_DATA_STDERR, 'No shells on this server.\r\n'), ('loseConnection', )], protocol.log) def test_windowChangedNotImplemented(self): # windowChanged raises a NotImplementedError. It doesn't matter what # we pass it. self.assertRaises(NotImplementedError, self.session.windowChanged, None) def test_providesISession(self): # ExecOnlySession must provide ISession. self.assertTrue(ISession.providedBy(self.session), "ExecOnlySession doesn't implement ISession") def test_closedDoesNothingWhenNoCommand(self): # When no process has been created, 'closed' is a no-op. self.assertEqual(None, self.session._transport) self.session.closed() self.assertEqual(None, self.session._transport) def test_closedTerminatesProcessAndDisconnects(self): # ExecOnlySession provides a 'closed' method that is generally # responsible for killing the child process and cleaning things up. # From the outside, it just looks like a successful no-op. From the # inside, it tells the process transport to end the connection between # the SSH server and the child process. protocol = ProcessProtocol() self.session.execCommand(protocol, 'cat /etc/hostname') self.session.closed() self.assertEqual([('signalProcess', 'HUP'), ('loseConnection', )], self.session._transport.log) def test_closedDisconnectsIfProcessCantBeTerminated(self): # 'closed' still calls 'loseConnection' on the transport, even if the # OS raises an error when we try to SIGHUP the process. protocol = ProcessProtocol() # MockTransport will raise an OSError on signalProcess if the executed # command is 'raise-os-error'. self.session.execCommand(protocol, 'raise-os-error') self.session.closed() self.assertEqual([('loseConnection', )], self.session._transport.log) def test_closedDisconnectsIfProcessAlreadyTerminated(self): # 'closed' still calls 'loseConnection' on the transport, even if the # process is already terminated protocol = ProcessProtocol() # MockTransport will raise a ProcessExitedAlready on signalProcess if # the executed command is 'already-terminated'. self.session.execCommand(protocol, 'already-terminated') self.session.closed() self.assertEqual([('loseConnection', )], self.session._transport.log) def test_getCommandToRunSplitsCommandLine(self): # getCommandToRun takes a command line and splits it into the name of # an executable to run and a sequence of arguments. command = 'cat foo bar' executable, arguments = self.session.getCommandToRun(command) self.assertEqual('cat', executable) self.assertEqual(['cat', 'foo', 'bar'], list(arguments)) def test_execCommandSpawnsProcess(self): # ExecOnlySession.execCommand spawns the appropriate process. protocol = ProcessProtocol() command = 'cat /etc/hostname' self.session.execCommand(protocol, command) executable, arguments = self.session.getCommandToRun(command) self.assertEqual([ (protocol, executable, arguments, None, None, None, None, 0, None) ], self.reactor.log) def test_eofReceivedDoesNothingWhenNoCommand(self): # When no process has been created, 'eofReceived' is a no-op. self.assertEqual(None, self.session._transport) self.session.eofReceived() self.assertEqual(None, self.session._transport) def test_eofReceivedClosesStdin(self): # 'eofReceived' closes standard input when called while a command is # running. protocol = ProcessProtocol() self.session.execCommand(protocol, 'cat /etc/hostname') self.session.eofReceived() self.assertEqual([('closeStdin', )], self.session._transport.log) def test_getAvatarAdapter(self): # getAvatarAdapter is a convenience classmethod so that # ExecOnlySession can be easily registered as an adapter for Conch # avatars. from twisted.internet import reactor adapter = ExecOnlySession.getAvatarAdapter() session = adapter(self.avatar) self.assertTrue( isinstance(session, ExecOnlySession), "ISession(avatar) doesn't adapt to ExecOnlySession. " "Got %r instead." % (session, )) self.assertIs(self.avatar, session.avatar) self.assertIs(reactor, session.reactor) def test_environment(self): # The environment for the executed process can be specified in the # ExecOnlySession constructor. session = ExecOnlySession(self.avatar, self.reactor, environment={'FOO': 'BAR'}) protocol = ProcessProtocol() session.execCommand(protocol, 'yes') self.assertEqual({'FOO': 'BAR'}, session.environment) self.assertEqual([(protocol, 'yes', ['yes'], { 'FOO': 'BAR' }, None, None, None, 0, None)], self.reactor.log) def test_environmentInGetAvatarAdapter(self): # We can pass the environment into getAvatarAdapter so that it is used # when we adapt the session. adapter = ExecOnlySession.getAvatarAdapter(environment={'FOO': 'BAR'}) session = adapter(self.avatar) self.assertEqual({'FOO': 'BAR'}, session.environment)
class TestExecOnlySession(AvatarTestCase): """Tests for ExecOnlySession. Conch delegates responsiblity for executing commands to an object that implements ISession. The smart server only needs to handle `execCommand` and a couple of other book-keeping methods. The methods that relate to running a shell or creating a pseudo-terminal raise NotImplementedErrors. """ def setUp(self): AvatarTestCase.setUp(self) self.avatar = CodehostingAvatar(self.aliceUserDict, None) # The logging system will try to get the id of avatar.transport, so # let's give it something to take the id of. self.avatar.transport = object() self.reactor = MockReactor() self.session = ExecOnlySession(self.avatar, self.reactor) def test_getPtyIsANoOp(self): # getPty is called on the way to establishing a shell. Since we don't # give out shells, it should be a no-op. Raising an exception would # log an OOPS, so we won't do that. self.assertEqual(None, self.session.getPty(None, None, None)) def test_openShellNotImplemented(self): # openShell closes the connection. protocol = MockProcessTransport('bash') self.session.openShell(protocol) self.assertEqual( [('writeExtended', connection.EXTENDED_DATA_STDERR, 'No shells on this server.\r\n'), ('loseConnection',)], protocol.log) def test_windowChangedNotImplemented(self): # windowChanged raises a NotImplementedError. It doesn't matter what # we pass it. self.assertRaises(NotImplementedError, self.session.windowChanged, None) def test_providesISession(self): # ExecOnlySession must provide ISession. self.failUnless(ISession.providedBy(self.session), "ExecOnlySession doesn't implement ISession") def test_closedDoesNothingWhenNoCommand(self): # When no process has been created, 'closed' is a no-op. self.assertEqual(None, self.session._transport) self.session.closed() self.assertEqual(None, self.session._transport) def test_closedTerminatesProcessAndDisconnects(self): # ExecOnlySession provides a 'closed' method that is generally # responsible for killing the child process and cleaning things up. # From the outside, it just looks like a successful no-op. From the # inside, it tells the process transport to end the connection between # the SSH server and the child process. protocol = ProcessProtocol() self.session.execCommand(protocol, 'cat /etc/hostname') self.session.closed() self.assertEqual( [('signalProcess', 'HUP'), ('loseConnection',)], self.session._transport.log) def test_closedDisconnectsIfProcessCantBeTerminated(self): # 'closed' still calls 'loseConnection' on the transport, even if the # OS raises an error when we try to SIGHUP the process. protocol = ProcessProtocol() # MockTransport will raise an OSError on signalProcess if the executed # command is 'raise-os-error'. self.session.execCommand(protocol, 'raise-os-error') self.session.closed() self.assertEqual( [('loseConnection',)], self.session._transport.log) def test_closedDisconnectsIfProcessAlreadyTerminated(self): # 'closed' still calls 'loseConnection' on the transport, even if the # process is already terminated protocol = ProcessProtocol() # MockTransport will raise a ProcessExitedAlready on signalProcess if # the executed command is 'already-terminated'. self.session.execCommand(protocol, 'already-terminated') self.session.closed() self.assertEqual([('loseConnection',)], self.session._transport.log) def test_getCommandToRunSplitsCommandLine(self): # getCommandToRun takes a command line and splits it into the name of # an executable to run and a sequence of arguments. command = 'cat foo bar' executable, arguments = self.session.getCommandToRun(command) self.assertEqual('cat', executable) self.assertEqual(['cat', 'foo', 'bar'], list(arguments)) def test_execCommandSpawnsProcess(self): # ExecOnlySession.execCommand spawns the appropriate process. protocol = ProcessProtocol() command = 'cat /etc/hostname' self.session.execCommand(protocol, command) executable, arguments = self.session.getCommandToRun(command) self.assertEqual([(protocol, executable, arguments, None, None, None, None, 0, None)], self.reactor.log) def test_eofReceivedDoesNothingWhenNoCommand(self): # When no process has been created, 'eofReceived' is a no-op. self.assertEqual(None, self.session._transport) self.session.eofReceived() self.assertEqual(None, self.session._transport) def test_eofReceivedClosesStdin(self): # 'eofReceived' closes standard input when called while a command is # running. protocol = ProcessProtocol() self.session.execCommand(protocol, 'cat /etc/hostname') self.session.eofReceived() self.assertEqual([('closeStdin',)], self.session._transport.log) def test_getAvatarAdapter(self): # getAvatarAdapter is a convenience classmethod so that # ExecOnlySession can be easily registered as an adapter for Conch # avatars. from twisted.internet import reactor adapter = ExecOnlySession.getAvatarAdapter() session = adapter(self.avatar) self.failUnless(isinstance(session, ExecOnlySession), "ISession(avatar) doesn't adapt to ExecOnlySession. " "Got %r instead." % (session,)) self.assertIs(self.avatar, session.avatar) self.assertIs(reactor, session.reactor) def test_environment(self): # The environment for the executed process can be specified in the # ExecOnlySession constructor. session = ExecOnlySession( self.avatar, self.reactor, environment={'FOO': 'BAR'}) protocol = ProcessProtocol() session.execCommand(protocol, 'yes') self.assertEqual({'FOO': 'BAR'}, session.environment) self.assertEqual( [(protocol, 'yes', ['yes'], {'FOO': 'BAR'}, None, None, None, 0, None)], self.reactor.log) def test_environmentInGetAvatarAdapter(self): # We can pass the environment into getAvatarAdapter so that it is used # when we adapt the session. adapter = ExecOnlySession.getAvatarAdapter( environment={'FOO': 'BAR'}) session = adapter(self.avatar) self.assertEqual({'FOO': 'BAR'}, session.environment)