4

I'm trying to test some code that reconnects to a server after a disconnect. This works perfectly fine outside the tests, but it fails to acknowledge that the socket has disconnected when running the tests.

I'm using a Gevent Stream Server to mock a real listening server:

import gevent.server
from gevent import queue


class TestServer(gevent.server.StreamServer):

    def __init__(self, *args, **kwargs):
        super(TestServer, self).__init__(*args, **kwargs)
        self.sockets = {}

    def handle(self, socket, address):
        self.sockets[address] = (socket, queue.Queue())
        socket.sendall('testing the connection\r\n')
        gevent.spawn(self.recv, address)

    def recv(self, address):
        socket = self.sockets[address][0]
        queue = self.sockets[address][1]
        print 'Connection accepted %s:%d' % address
        try:
            for data in socket.recv(1024):
                queue.put(data)
        except:
            pass

    def murder(self):
        self.stop()
        for sock in self.sockets.iteritems():
            print sock
            sock[1][0].shutdown(socket.SHUT_RDWR)
            sock[1][0].close()
        self.sockets = {}


def run_server():
    test_server = TestServer(('127.0.0.1', 10666))
    test_server.start()
    return test_server

And my test looks like this:

def test_can_reconnect(self):
    test_server = run_server()
    client_config = {'host': '127.0.0.1', 'port': 10666}
    client = Connection('test client', client_config, get_config())
    client.connect()
    assert client.socket_connected
    test_server.murder()
    #time.sleep(4) #tried sleeping. no dice.
    assert not client.socket_connected
    assert client.server_disconnect
    test_server = run_server()
    client.reconnect()
    assert client.socket_connected

It fails at assert not client.socket_connected.

I detect for "not data" during recv. If it's None, then I set some variables so that other code can decide whether or not to reconnect (don't reconnect if it was a user_disconnect and so on). This behavior works and has always worked for me in the past, I've just never tried to make a test for it until now. Is there something odd with socket connections and local function scopes or something? it's like the connection still exists even after stopping the server.

The code I'm trying to test is open: https://github.com/kyleterry/tenyks.git

If you run the tests, you will see the one I'm trying to fix fail.

Kyle
  • 929
  • 2
  • 8
  • 23

4 Answers4

2

Trying to run a unit test with a real socket is a tough row to hoe. It's going to be tricky as only one set of tests can run at a time, as the server port will be used, and it's going to be slow as the sockets get set up and torn down. To top it off if this is really a unit test you don't want to test the socket, just the code that's using the socket.

If you mock the socket calls you can throw exceptions willy nilly from the mocked code and ensure that the code making use of the socket does the right thing. You don't need a real socket to ensure that the class under test does the right thing, you can fake it if you can wrap the socket calls in an object. Pass in a reference to the socket object when constructing your class and you're ready to go.

My suggestion is to wrap the socket calls in a class that supports sendall, recv, and all the methods you call on the socket. Then you can swap out the actual Socket class with a TestReconnectSocket (or whatever) and run your tests.

Take a look at mox, a python mocking framework.

Paul Rubel
  • 26,632
  • 7
  • 60
  • 80
  • Wrapping is a kludge. In Python, there are more elegant solutions for replacing external objects like [`unittest.mock.patch`](http://docs.python.org/dev/library/unittest.mock#id4). – ivan_pozdeev Oct 27 '12 at 10:05
0

Vague response, but my immediate reaction would be that your recv() call is blocking and keeping the socket alive - have you tried making the socket non-blocking, and catching the error on close instead?

KevinL
  • 170
  • 4
0

One thing to keep in mind when testing sockets like this, is that operating systems don't like to reopen a socket soon after it has been in use. You can set a socket option to tell it to go ahead and reuse it anyways. Right after you create the socket set the socket's option:

mysocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Hopefully this will fix your issue. You may have to do it on both the server and client side depending on which one is giving you the problems.

dail
  • 46
  • 2
  • Actually, it looks like you will just need to do this on the server's socket, since the client will use a different port each time. – dail Oct 25 '12 at 18:05
0
  • you are calling shutdown(socket.SHUT_RDWR) so this doesn't seem like a problem with recv blocking.
  • however, you are using gevent.socket.socket.recv, so please check your gevent version, there is an issue with recv() that causes it to block if the underlying file descriptor is closed (version < v0.13.0)
  • you may still need gevent.sleep() to do cooperative yield and give the client an opportunity to exit the recv() call.
Community
  • 1
  • 1
dnozay
  • 23,846
  • 6
  • 82
  • 104