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
//

using concurrent

class UdpSocketTest : Test
{

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

  Void testMake()
  {
    s := UdpSocket.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)
    s.close
  }

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

  Void testBind()
  {
    verifyBind(null, null)
    verifyBind(IpAddr.local, null)
    verifyBind(null, 2072)
    verifyBind(IpAddr.local, 2073)
  }

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

    // state
    verifyEq(s.isBound, true)
    verifyEq(s.isConnected, false)
    verifyEq(s.isClosed, false)

    // 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 := UdpSocket.make
    verifyErr(IOErr#) { x.bind(null, s.localPort) }
    x.close
    */

    // cleanup
    s.close

    verifyEq(s.isClosed, true)
  }

//////////////////////////////////////////////////////////////////////////
// Messaging
//////////////////////////////////////////////////////////////////////////

  Void testMessaging()
  {
    // launch server
    s := UdpSocket.make.bind(null, null)
    sactor := Actor(ActorPool()) |msg| { runServer(msg->val) }
    sfuture := sactor.send(Unsafe(s))
    Actor.sleep(50ms)

    // connect
    c := UdpSocket()
    c.connect(IpAddr.local, s.localPort)
    verifyEq(c.isBound, true)
    verifyEq(c.isConnected, true)
    verifyEq(c.remoteAddr, IpAddr.local)
    verifyEq(c.remotePort, s.localPort)

    // verify addr/port must be null
    verifyErr(ArgErr#) { c.send(UdpPacket(IpAddr.local, null, Buf.make)) }
    verifyErr(ArgErr#) { c.send(UdpPacket(null, s.localPort, Buf.make)) }

    // send "alpha"
    buf := Buf.make
    buf.print("alpha")
    c.send(UdpPacket(null, null, buf.flip))

    // receive "alpha."
    packet := c.receive
    verifyEq(packet.data.capacity, 1024)
    verifyEq(packet.data.flip.readAllStr, "alpha.")

    // send "lo" with pos=3
    buf.clear.print("hello")
    buf.flip
    3.times { buf.read }
    verifyEq(buf.pos, 3)
    c.send(UdpPacket(null, null, buf))

    // receive "lo."
    packet = c.receive
    verifyEq(packet.data.flip.readAllStr, "lo.")

    // disconnect
    c.disconnect
    verifyEq(c.isConnected, false)
    verifyEq(c.remoteAddr, null)
    verifyEq(c.remotePort, null)

    // verify addr/port cannot be null
    verifyErr(ArgErr#) { c.send(UdpPacket(IpAddr.local, null, Buf.make)) }
    verifyErr(ArgErr#) { c.send(UdpPacket(null, s.localPort, Buf.make)) }

    // send "abc"
    c.send(UdpPacket(IpAddr.local, s.localPort, (Buf)buf.clear.print("abc")->flip))

    // receive in Buf.pos=2
    buf.clear.print("xy")
    verifyEq(buf.pos, 2)
    packet = c.receive(UdpPacket(null, null, buf))
    verifyEq(packet.addr, IpAddr.local)
    verifyEq(packet.port, s.localPort)
    verifyEq(packet.data.flip.readAllStr, "xyabc.")

    // send "ABCDEFG"
    c.send(UdpPacket(IpAddr.local, s.localPort, (Buf)buf.clear.print("ABCDEFG")->flip))

    // receive with capacity too small and validate truncating
    buf.clear.capacity = 3
    verifyEq(buf.pos, 0)
    verifyEq(buf.capacity, 3)
    c.receive(packet)
    verifyEq(packet.addr, IpAddr.local)
    verifyEq(packet.port, s.localPort)
    verifyEq(packet.data.flip.readAllStr, "ABC")

    // send "0123456789"
    c.send(UdpPacket(IpAddr.local, s.localPort, (Buf)buf.clear.print("0123456789")->flip))

    // receive with capacity too small and pos=2 to validate truncating
    buf.clear.capacity = 5
    buf.print("qr")
    verifyEq(buf.pos, 2)
    verifyEq(buf.capacity, 5)
    c.receive(packet)
    verifyEq(packet.addr, IpAddr.local)
    verifyEq(packet.port, s.localPort)
    verifyEq(packet.data.flip.readAllStr, "qr012")

    // reconnect
    c.connect(IpAddr.local, s.localPort)
    verifyEq(c.isConnected, true)
    verifyEq(c.remoteAddr, IpAddr.local)
    verifyEq(c.remotePort, s.localPort)

    // send kill and join
    c.send(UdpPacket(null, null, (Buf)buf.clear.print("kill")->flip))
    sfuture.get(5sec)

    //cleanup
    s.close
    c.close
  }

  static Str runServer(UdpSocket s)
  {
    while (true)
    {
      // receive an ASCII string
      packet := s.receive
      req := packet.data.flip.readAllStr
      if (req == "kill") break

      // reflect string with "." appended
      packet.data.print(".")
      packet.data.flip
      s.send(packet)
    }
    return "ok"
  }

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

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

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

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

    send := so.sendBufferSize
    so.sendBufferSize = send/2
    verifyEq(so.sendBufferSize, send/2)

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

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

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

    verifyErr(UnsupportedErr#) { echo(so.inBufferSize) }
    verifyErr(UnsupportedErr#) { so.inBufferSize = 88 }

    verifyErr(UnsupportedErr#) { echo(so.outBufferSize) }
    verifyErr(UnsupportedErr#) { so.outBufferSize = 99 }

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

    verifyErr(UnsupportedErr#) { echo(so.linger) }
    verifyErr(UnsupportedErr#) { so.linger = null }

    verifyErr(UnsupportedErr#) { echo(so.noDelay) }
    verifyErr(UnsupportedErr#) { so.noDelay = true }

    xo := TcpSocket().options
    xo.copyFrom(so)
    verifyEq(xo.broadcast, so.broadcast)
    verifyEq(xo.receiveBufferSize, so.receiveBufferSize)
    verifyEq(xo.sendBufferSize, so.sendBufferSize)
    verifyEq(xo.reuseAddr, so.reuseAddr)
    verifyEq(xo.receiveTimeout, so.receiveTimeout)

    s.close
  }

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

  /*
  Void dump(UdpSocket 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("receive    = $s.options.receiveBufferSize")
    echo("send       = $s.options.sendBufferSize")
    echo("reuseAddr  = $s.options.reuseAddr")
    echo("timeout    = $s.options.receiveTimeout")
    echo("trafficCls = 0x$s.options.trafficClass.toHex")
  }
  */

}