9

I've written client code that's supposed to send some data through a socket and read back an answer from the remote server.

I would like to unit-test that code. The function's signature is something along the lines of:

double call_remote(double[] args, int fd);

where fd is the file descriptor of the socket to the remote server.

Now the call_remote function will, after sending the data, block on reading the answer from the server. How can I stub such a remote server for unit-testing the code?

Ideally I would like something like:

int main() {
  int stub = /* initialize stub */
  double expected = 42.0;

  assert(expected == call_remote(/* args */, stub);

  return 0;
}

double stub_behavior(double[] args) {
  return 42.0;
}

I would like stub_behavior to be called and send the 42.0 value down the stubbed file descriptor.

Any easy way I can do that?

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
lindelof
  • 34,556
  • 31
  • 99
  • 140
  • Depending on platform, you can override `read/write/send/recv` calls BTW. That way you can get the perfect stabbing the hard way. –  Jul 12 '11 at 20:12

4 Answers4

3

If this is a POSIX system, you can use fork() and socketpair():

#define N_DOUBLES_EXPECTED 10
double stub_behaviour(double []);

int initialize_stub(void)
{
    int sock[2];
    double data[N_DOUBLES_EXPECTED];

    socketpair(AF_UNIX, SOCK_STREAM, 0, sock);

    if (fork()) {
        /* Parent process */
        close(sock[0]);
        return sock[1];
    }

    /* Child process */

    close(sock[1]);

    /* read N_DOUBLES_EXPECTED in */
    read(sock[0], data, sizeof data);

    /* execute stub */
    data[0] = stub_behaviour(data);

    /* write one double back */
    write(sock[0], data, sizeof data[0]);
    close(sock[0]);
    _exit(0);
}


int main()
{
  int stub = initialize_stub();
  double expected = 42.0;

  assert(expected == call_remote(/* args */, stub);

  return 0;
}

double stub_behavior(double args[])
{
  return 42.0;
}

...of course, you will probably want to add some error checking, and alter the logic that reads the request.

The file descriptor created by socketpair() is a normal socket, and thus socket calls like send() and recv() will work fine on it.

user2565137
  • 5
  • 1
  • 2
caf
  • 233,326
  • 40
  • 323
  • 462
  • Thanks! Just out of curiosity, what does _exit() stand for? – lindelof Jul 13 '11 at 09:58
  • 1
    @lindelof: `_exit()` is like `exit()`, except that it does not call `atexit()` handlers. It generally does not flush standard IO buffers and similar, which is why it is the preferred way to exit a child process that has not called `execve()`. The code calls `_exit()` there so that the child process does not return to `main()`. – caf Jul 13 '11 at 10:08
1

You could use anything which can be accessed with a file descriptor. A file or, if you want simulate blocking behaviour, a pipe.

Note: obviosly socket specific calls (setsockopt, fcntl, ioctl, ...) wouldn't work.

Karoly Horvath
  • 94,607
  • 11
  • 117
  • 176
  • Unfortunately `send(fd,...)` doesn't work with a normal file descriptor. `write(fd,...)` does work. The man page for `send` says that it is equivalent to `write` if the *flags* argument is 0, but this is not true. – Mark Lakata Sep 26 '16 at 19:42
0

Here is a C++ implementation (I know, the original question was for C, but it is easy to convert back to C if desired). It probably doesn't work for very large strings, as the socket will probably block if the string can't be buffered. But it works for small unit tests.

/// Class creates a simple socket for testing out functions that write to a socket.
/// Usage:
///  1. Call GetSocket() to get a file description socket ID
///  2. write to that socket FD
///  3. Call ReadAll() read back all the data that was written to that socket.
///  The sockets are all closed by ReadAll(), so this is a one-use object.
///
/// \example
///  MockSocket ms;
///  int socket = ms.GetSocket();
///  send(socket,"foo bar",7);
///  ...
///  std::string s = ms.ReadAll();
///  EXPECT_EQ("foo bar",s);

class MockSocket
{
public:
    ~MockSocket()
    {
    }


    int GetSocket()
    {
        socketpair(AF_UNIX, SOCK_STREAM, 0, sockets_);
        return sockets_[0];
    }

    std::string ReadAll()
    {
        close(sockets_[0]);
        std::string s;
        char buffer[256];
        while (true)
        {
            int n = read(sockets_[1], buffer, sizeof(buffer));
            if (n > 0) s.append(buffer,n);
            if (n <= 0) break;
        }
        close(sockets_[1]);
        return s;
    }
private:
    int sockets_[2];
};
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123
0

I encountered the same situation and I'll share my approach. I created network dumps of exactly what the client should send, and what the server response should be. I then did a byte-by-byte comparison of the client request to ensure it matched. If the request is valid, I read from the response file and send it back to the client.

I'm happy to provide more details (when I'm at a machine with access to this code)

Michael Mior
  • 28,107
  • 9
  • 89
  • 113
  • Does this mean you used two separate processes? – lindelof Jul 13 '11 at 09:11
  • Yes. I forked right before executing the server send code. At that point, I was new to testing in C. I really just wanted something that worked and I figured that some testing was better than no testing, even though perhaps forking during a test isn't commonplace. – Michael Mior Jul 13 '11 at 12:56