How to write several tests for a TCPSocket in Minitest

244 Views Asked by At

I try to test a networking application. BUT I might have a knot in my brain. As far as I know the tests in minitest run parallel. On this assumption I think it is clear that when I allocate a Port in setup() it fails when several tests are run:

RuntimeError: Address already in use - bind(2) for nil port 2000 TCPServer new failed

So what is the best practise to do several tests on a server which listens on a port?

   class ServerTest < Minitest::Test

      def setup
        # -- set env variable
        ENV["conf"] = "./tc_gpio.config" 
        Thread::abort_on_exception = true
        @my_thr = Thread.start() do 
          @server = Server.new       
          @server.start
          assert @server.hi() != nil
        end
      end


      def teardown
        Thread.kill(@my_thr) # sends exit() to thr
      end

      def test_connect_and_disconnect
        sleep 1
        hostname = 'localhost'
        port = 2000
        s = TCPSocket.open(hostname, port)
        my_s = s.recvmsg()
        s.sendmsg(:set.to_s, 0) # Failes since a serialized object is expected
        my_s2 = s.recvmsg()

        assert_equal(  "READY" , my_s[0] )
        assert_equal( "eeFIN" , my_s2[0])
      end

      def test_send_command

        # fill command
        com = Command.new
        com.type = :set
        com.device_name = 'pump0'
        com.device_address = 'resource0'
        com.value = 2

        serial_com = YAML::dump(com)

        sleep 1
        hostname = 'localhost'
        port = 2000
        s = TCPSocket.open(hostname, port)
        my_s = s.recvmsg()
        s.sendmsg(serial_com, 0)
        my_s2 = s.recvmsg()


        assert_equal(  "READY" , my_s[0] )
        assert_equal( "FIN" , my_s2[0])
      end
    end
1

There are 1 best solutions below

0
On BEST ANSWER

When testing a TCP server in parallel, each instance of the server should be started with a distinct port. That can be done by specifying port number 0 when creating a socket. When port number 0 is given, the socket will be bound to a random unused port:

interface = "0.0.0.0"
port = 0
tcp_server = TCPServer.new(interface, port)

You can find out which port the TCP server socket was bound to:

bound_port = @server_socket.addr[1]

One way to use these facts is to have a server something like this:

class Server

  # Create a server instance.  If the port is unspecified, 
  # or 0, then a random ephemeral port is bound to.
  def initialize(interface: "127.0.0.1", port: 0)
    @server_socket = TCPServer.new(interface, port)
    ...
  end

  # Return the port the server socket is bound to.
  def  bound_port
    @server_socket.addr[1]
  end

  ...

end

The test then creates server instances using port 0:

server = Server.new(port: 0)

When making a connection to the server, the test uses the #bound_port accessor to find out what port to connect to:

client = TCPSocket.open("localhost", server.bound_port)

and then carries on normally.