Beispiel #1
0
 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)
Beispiel #2
0
 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 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_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)
Beispiel #7
0
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)