[Python-Dev] unittest and sockets. Ugh!?

Michael Gilfix mgilfix@eecs.tufts.edu
Sat, 8 Jun 2002 17:26:28 -0400


  Could someone please offer some advice/comments on this?  While
restructuring test_socket.py, I've come across the following
(limitation?) problem with the unittest module. To test socket stuff,
I really need to use two separate processes or threads.  Since
fork() is much better supported, here's my attempt at porting the
_fileobject tests into unittest. I really like the structure of the
test (hopefully you guys agree) and this is how I'd like to lay it out
ideally for testing things like accept/connect, etc, using the various
layers of my inhertiance hierarchy.

  However, this test won't work because I get an error binding to the
socket in the setUp function because the socket is already taken. Is
this because unittest dispatches the tests at roughly the same
time? I'm not quite sure why this is failing in sequence (perhaps I'm
missing something). In addition, I thought that the setUp/tearDown
functions were shared between all tests within a class, not called
for each test but this does not seem true. If I want the setup to be
shared between all tests, do I have to override __init__? Another
issue is that unittest doesn't seem to like that I've forked. It
considers it to be the equivalent of two tests. Perhaps I shouldn't
care, provided that they all pass anyway.

  Any other stuff that I've seen that uses forking/threading doesn't
seem to use the unittest style framework. Perhaps I shouldn't be using
this and should just write outside of it? That would be a shame since
I like many of the features of the framework but some seem limiting.

                    -- Mike

=======================================================================

#!/usr/bin/env python

import unittest
import test_support

import socket
import os
import time

PORT = 50007
HOST = 'localhost'

class SocketTest(unittest.TestCase):

    def setUp(self):
        canfork = hasattr (os, 'fork')
        if not canfork:
            raise test_support.TestSkipped, \
                  "Platform does not support forking."

        # Use this to figure out who we are in the tests
        self.parent = os.fork()

        if self.parent:
            self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.s.bind((HOST, PORT))
            self.s.listen(1)
        else:
            self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        time.sleep(1) # So we can catch up

    def tearDown(self):
        self.s.close ()
        self.s = None

class SocketConnectedTest(SocketTest):

    SYNCH_MSG = 'Michael Gilfix was here'

    def setUp(self):
        SocketTest.setUp(self)
        if self.parent:
            conn, addr = self.s.accept()
            self.conn = conn
        else:
            self.s.connect((HOST, PORT))
            self.conn = self.s

    def tearDown(self):
        if self.parent:
            self.conn.close()
        self.conn = None
        SocketTest.tearDown(self)

    def synchronize(self):
        time.sleep(1)
        if self.parent:
            msg = self.conn.recv(len(self.SYNCH_MSG))
            self.assertEqual(msg, self.SYNCH_MSG, "Parent synchronization error")
            self.conn.send(msg)
        else:
            self.conn.send(msg)
            msg = self.conn.recv(len(self.SYNCH_MSG))
            self.assertEqual(msg, self.SYNCH_MSG, "Child synchronization error")
        time.sleep(1)

class FileObjectClassTestCase(SocketConnectedTest):

    def setUp(self):
        SocketConnectedTest.setUp(self)
        # Create a file object for both the parent/client processes
        self.f = socket._fileobject(self.conn, 'rb', 8192)

    def tearDown(self):
        self.f.close()
        SocketConnectedTest.tearDown(self)

    def testSmallRead(self):
        """Performing small read test."""
        if self.parent:
            first_seg = self.f.read(7)
            second_seg = self.f.read(25)
            msg = ''.join((first_seg, second_seg))
            self.assertEqual(msg, self.SYNCH_MSG, "Error performing small read.")
        else:
            self.f.write(self.SYNCH_MSG)
            self.f.flush()

    def testUnbufferedRead(self):
        """Performing unbuffered read test."""
        if self.parent:
            buf = ''
            while 1:
                char = self.f.read(1)
                self.failIf(not char, "Error performing unbuffered read.")
                buf += char
                if buf == self.SYNCH_MSG:
                    break
        else:
            self.f.write(self.SYNCH_MSG)
            self.f.flush()

    def testReadline(self):
        """Performing readline test."""
        if self.parent:
            line = self.f.readline()
            self.assertEqual(line, self.SYNCH_MSG, "Error performing readline.")
        else:
            self.f.write(self.SYNCH_MSG)
            self.f.flush()

def suite():
    suite = unitest.TestSuite()
    suite.addTest(unittest.makeSuite(FileObjectClassTestCase))
    return suite

if __name__ == '__main__':
    unittest.main()

--
Michael Gilfix
mgilfix@eecs.tufts.edu

For my gpg public key:
http://www.eecs.tufts.edu/~mgilfix/contact.html