Fantom

 

//
// Copyright (c) 2007, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   9 Feb 07  Brian Frank  Creation
//

class TcpSocketTest : Test
{

//////////////////////////////////////////////////////////////////////////
// Make
//////////////////////////////////////////////////////////////////////////

  Void testMake()
  {
    s := TcpSocket.make
    verifyEq(s.isBound, false)
    verifyEq(s.isConnected, false)
    verifyEq(s.isClosed, false)
    verifyEq(s.localAddr, null)
    verifyEq(s.localPort, null)
    verifyEq(s.remoteAddr, null)
    verifyEq(s.remotePort, null)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }
    verifyErr(IOErr#) { s.shutdownIn }
    verifyErr(IOErr#) { s.shutdownOut }
    s.close
  }

//////////////////////////////////////////////////////////////////////////
// Bind
//////////////////////////////////////////////////////////////////////////

  Void testBind()
  {
    verifyBind(null, null)
    verifyBind(IpAddr.local, null)
    port := (1200..9999).random
    verifyBind(null, port)
    verifyBind(IpAddr.local, port)
  }

  Void verifyBind(IpAddr? addr, Int? port)
  {
    s := TcpSocket.make
    verifySame(s.bind(addr, port), s)

    // state
    verifyEq(s.isBound, true)
    verifyEq(s.isConnected, false)
    verifyEq(s.isClosed, false)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }

    // local address
    if (addr == null)
      verify(s.localAddr != null)
    else
      verifyEq(s.localAddr, addr)

    // local port
    if (port == null)
      verify(s.localPort > 0)
    else
      verifyEq(s.localPort, port)

    // null remote
    verifyEq(s.remoteAddr, null)
    verifyEq(s.remotePort, null)

    // duplicate port
    /* On Windows7 this doesn't fail?
    x := TcpSocket.make
    verifyErr(IOErr#) { x.bind(null, s.localPort) }
    x.close
    */

    // cleanup
    s.close

    verifyEq(s.isClosed, true)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }
  }

//////////////////////////////////////////////////////////////////////////
// Connection Failures
//////////////////////////////////////////////////////////////////////////

  Void testConnectFailures()
  {
    // local, invalid port
    s := TcpSocket.make
    verifyErr(IOErr#) { s.connect(IpAddr.local, 1969) }
    verifyEq(s.isConnected, false)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }
    s.close

    // invalid host
    t1 := Duration.now
    s = TcpSocket.make
    verifyErr(IOErr#) { s.connect(IpAddr("1.1.1.1"), 1969, 100ms) }
    t2 := Duration.now
    verifyEq(s.isConnected, false)
    verify(80ms < t2-t1 && t2-t1 < 150ms)
    s.close

    verifyEq(s.isClosed, true)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }
  }

//////////////////////////////////////////////////////////////////////////
// Connect
//////////////////////////////////////////////////////////////////////////

  Void testConnectHttp()
  {
    doTestConnectHttp(null)
    doTestConnectHttp(30sec)
  }

  Void doTestConnectHttp(Duration? timeout)
  {
    // connect to www server
    s := TcpSocket().connect(IpAddr("hg.fantom.org"), 80, timeout)

    // verify connetion state
    verifyEq(s.isBound, true)
    verifyEq(s.isConnected, true)
    verifyEq(s.isClosed, false)
    verify((Obj?)s.in != null)
    verify((Obj?)s.out != null)
    verifyErr(Err#) { s.options.inBufferSize = 16 }
    verifyErr(Err#) { s.options.outBufferSize = 16 }

    // send very simple request line
    s.out.print("GET / HTTP/1.0\r\n\r\n").flush

    // read first response line
    res := s.in.readLine
    verify(res.startsWith("HTTP/"))

    // cleanup
    s.close
    verifyEq(s.isClosed, true)
    verifyErr(IOErr#) { s.in }
    verifyErr(IOErr#) { s.out }
  }

//////////////////////////////////////////////////////////////////////////
// Fork
//////////////////////////////////////////////////////////////////////////

  /* TODO - remove when we finalize that TcpSocket should be const
  Void testFork()
  {
    // verify duplicate name
    verifyErr(ArgErr#)
    {
       x := TcpSocket().connect(IpAddr("fantom.org"), 80)
       x.fork(Thread.current.name, &runFork(x.localPort, x.remoteAddr.numeric))
    }

    // verify non-const method
    verifyErr(NotImmutableErr#)
    {
       x := TcpSocket().connect(IpAddr("fantom.org"), 80)
       x.fork(null) |TcpSocket s| { fail }
    }

    // connect to www server
    s := TcpSocket().connect(IpAddr("fantom.org"), 80)
    so := s.options

    // fork
    t := s.fork(null, &runFork(s.localPort, s.remoteAddr.numeric))

    // verify that all methods on s now throw UnsupportedErr
    verifyErr(UnsupportedErr#) { s.isBound }
    verifyErr(UnsupportedErr#) { s.isConnected }
    verifyErr(UnsupportedErr#) { s.isClosed }
    verifyErr(UnsupportedErr#) { s.localAddr }
    verifyErr(UnsupportedErr#) { s.localPort }
    verifyErr(UnsupportedErr#) { s.remoteAddr }
    verifyErr(UnsupportedErr#) { s.remotePort  }
    verifyErr(UnsupportedErr#) { s.bind(null, null) }
    verifyErr(UnsupportedErr#) { s.connect(null, null) }
    verifyErr(UnsupportedErr#) { s.in }
    verifyErr(UnsupportedErr#) { s.out }
    verifyErr(UnsupportedErr#) { s.close }
    verifyErr(UnsupportedErr#) { s.fork(null, null) }
    verifyErr(UnsupportedErr#) { s.options }

    // verify that all socket options now throw UnsupportedErr
    verifyErr(UnsupportedErr#) { echo(so.inBufferSize) }
    verifyErr(UnsupportedErr#) { so.inBufferSize = 100}
    verifyErr(UnsupportedErr#) { echo(so.outBufferSize) }
    verifyErr(UnsupportedErr#) { so.outBufferSize = 100}
    verifyErr(UnsupportedErr#) { echo(so.keepAlive) }
    verifyErr(UnsupportedErr#) { so.keepAlive = false }
    verifyErr(UnsupportedErr#) { echo(so.receiveBufferSize) }
    verifyErr(UnsupportedErr#) { so.receiveBufferSize = 10}
    verifyErr(UnsupportedErr#) { echo(so.sendBufferSize) }
    verifyErr(UnsupportedErr#) { so.sendBufferSize = 10}
    verifyErr(UnsupportedErr#) { echo(so.linger) }
    verifyErr(UnsupportedErr#) { so.linger = null }
    verifyErr(UnsupportedErr#) { echo(so.receiveTimeout) }
    verifyErr(UnsupportedErr#) { so.receiveTimeout = null }
    verifyErr(UnsupportedErr#) { echo(so.noDelay) }
    verifyErr(UnsupportedErr#) { so.noDelay = false }
    verifyErr(UnsupportedErr#) { echo(so.trafficClass) }
    verifyErr(UnsupportedErr#) { so.trafficClass = 0 }

    // join and verify response
    verifyEq(t.join, "HTTP/1.1 200 OK")
  }

  static Obj runFork(Int localPort, Str remoteAddr, TcpSocket s)
  {
    // verify state of new detached socket
    if (s.localPort != localPort) return null
    if (s.remoteAddr.numeric != remoteAddr) return null
    if (s.remotePort != 80) return null

    // send very simple request line
    s.out.print("GET / HTTP/1.0\n\r\n\r").flush

    // read first response line
    res := s.in.readLine

    // cleanup and return response
    s.close
    return res
  }
  */

//////////////////////////////////////////////////////////////////////////
// Options
//////////////////////////////////////////////////////////////////////////

  Void testOptions()
  {
    s := TcpSocket.make
    so := s.options

    in := so.inBufferSize
    so.inBufferSize = in*2
    verifyEq(so.inBufferSize, in*2)

    out := so.outBufferSize
    so.outBufferSize = in*2+1
    verifyEq(so.outBufferSize, in*2+1)

    keepAlive := so.keepAlive
    so.keepAlive = !keepAlive
    verifyEq(so.keepAlive, !keepAlive)

    receive := so.receiveBufferSize
    so.receiveBufferSize = receive*2
    verifyEq(so.receiveBufferSize, receive*2)

    send := so.sendBufferSize
    so.sendBufferSize = send*4
    verifyEq(so.sendBufferSize, send*4)

    reuse := so.reuseAddr
    so.reuseAddr = !reuse
    verifyEq(so.reuseAddr, !reuse)

    so.linger = 2sec
    verifyEq(so.linger, 2sec)
    so.linger = null
    verifyEq(so.linger, null)

    so.receiveTimeout = 100ms
    verifyEq(so.receiveTimeout, 100ms)
    so.receiveTimeout = null
    verifyEq(so.receiveTimeout, null)

    verifyEq(so.noDelay, true) // should default to false
    so.noDelay = false
    verifyEq(so.noDelay, false)

    tc := so.trafficClass
    so.trafficClass = 0x6
    verifyEq(so.trafficClass, 0x6)

    verifyErr(UnsupportedErr#) { echo(so.broadcast) }
    verifyErr(UnsupportedErr#) { so.broadcast = false }

    xo := TcpSocket().options
    xo.copyFrom(so)
    verifyEq(xo.inBufferSize, so.inBufferSize)
    verifyEq(xo.outBufferSize, so.outBufferSize)
    verifyEq(xo.keepAlive, so.keepAlive)
    verifyEq(xo.receiveBufferSize, so.receiveBufferSize)
    verifyEq(xo.sendBufferSize, so.sendBufferSize)
    verifyEq(xo.reuseAddr, so.reuseAddr)
    verifyEq(xo.linger, so.linger)
    verifyEq(xo.receiveTimeout, so.receiveTimeout)
    verifyEq(xo.noDelay, so.noDelay)
    verifyEq(xo.trafficClass, so.trafficClass)

    s.close
  }

//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////

  /*
  Void dump(TcpSocket s)
  {
    echo("---------")
    echo("bound      = $s.isBound")
    echo("connected  = $s.isConnected")
    echo("closed     = $s.isClosed")
    echo("localAddr  = $s.localAddr")
    echo("localPort  = $s.localPort")
    echo("remoteAddr = $s.remoteAddr")
    echo("remotePort = $s.remotePort")
    echo("keepAlive  = $s.options.keepAlive")
    echo("receive    = $s.options.receiveBufferSize")
    echo("send       = $s.options.sendBufferSize")
    echo("reuseAddr  = $s.options.reuseAddr")
    echo("linger     = $s.options.linger")
    echo("timeout    = $s.options.receiveTimeout")
    echo("noDelay    = $s.options.noDelay")
    echo("trafficCls = 0x$s.options.trafficClass.toHex")
  }
  */

}