mock adapter: Group all replies for one request under one call to MockReply
[govpp.git] / core / core_test.go
index c95ef04..e4fbf63 100644 (file)
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package core
+package core_test
 
 import (
        "testing"
 
-       "gerrit.fd.io/r/govpp/adapter/mock"
-       "gerrit.fd.io/r/govpp/api"
-       "gerrit.fd.io/r/govpp/core/bin_api/vpe"
-       "gerrit.fd.io/r/govpp/examples/bin_api/interfaces"
+       "git.fd.io/govpp.git/adapter/mock"
+       "git.fd.io/govpp.git/api"
+       "git.fd.io/govpp.git/core"
+       "git.fd.io/govpp.git/core/bin_api/vpe"
+       "git.fd.io/govpp.git/examples/bin_api/interfaces"
+       "git.fd.io/govpp.git/examples/bin_api/stats"
 
        . "github.com/onsi/gomega"
 )
 
 type testCtx struct {
        mockVpp *mock.VppAdapter
-       conn    *Connection
+       conn    *core.Connection
        ch      *api.Channel
 }
 
-func setupTest(t *testing.T) *testCtx {
+func setupTest(t *testing.T, bufferedChan bool) *testCtx {
        RegisterTestingT(t)
 
        ctx := &testCtx{}
        ctx.mockVpp = &mock.VppAdapter{}
 
        var err error
-       ctx.conn, err = Connect(ctx.mockVpp)
+       ctx.conn, err = core.Connect(ctx.mockVpp)
        Expect(err).ShouldNot(HaveOccurred())
 
-       ctx.ch, err = ctx.conn.NewAPIChannel()
+       if bufferedChan {
+               ctx.ch, err = ctx.conn.NewAPIChannelBuffered(100, 100)
+       } else {
+               ctx.ch, err = ctx.conn.NewAPIChannel()
+       }
        Expect(err).ShouldNot(HaveOccurred())
 
        return ctx
@@ -53,7 +59,7 @@ func (ctx *testCtx) teardownTest() {
 }
 
 func TestSimpleRequest(t *testing.T) {
-       ctx := setupTest(t)
+       ctx := setupTest(t, false)
        defer ctx.teardownTest()
 
        ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: -5})
@@ -76,12 +82,14 @@ func TestSimpleRequest(t *testing.T) {
 }
 
 func TestMultiRequest(t *testing.T) {
-       ctx := setupTest(t)
+       ctx := setupTest(t, false)
        defer ctx.teardownTest()
 
+       msgs := []api.Message{}
        for m := 0; m < 10; m++ {
-               ctx.mockVpp.MockReply(&interfaces.SwInterfaceDetails{})
+               msgs = append(msgs, &interfaces.SwInterfaceDetails{})
        }
+       ctx.mockVpp.MockReply(msgs...)
        ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
 
        // send multipart request
@@ -107,7 +115,7 @@ func TestMultiRequest(t *testing.T) {
 }
 
 func TestNotifications(t *testing.T) {
-       ctx := setupTest(t)
+       ctx := setupTest(t, false)
        defer ctx.teardownTest()
 
        // subscribe for notification
@@ -144,18 +152,48 @@ func TestNotifications(t *testing.T) {
        Expect(err).ShouldNot(HaveOccurred())
 }
 
+func TestNilConnection(t *testing.T) {
+       RegisterTestingT(t)
+       var conn *core.Connection
+
+       ch, err := conn.NewAPIChannel()
+       Expect(ch).Should(BeNil())
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("nil"))
+
+       ch, err = conn.NewAPIChannelBuffered(1, 1)
+       Expect(ch).Should(BeNil())
+       Expect(err).Should(HaveOccurred())
+       Expect(err.Error()).To(ContainSubstring("nil"))
+}
+
 func TestDoubleConnection(t *testing.T) {
-       ctx := setupTest(t)
+       ctx := setupTest(t, false)
        defer ctx.teardownTest()
 
-       conn, err := Connect(ctx.mockVpp)
+       conn, err := core.Connect(ctx.mockVpp)
        Expect(err).Should(HaveOccurred())
        Expect(err.Error()).To(ContainSubstring("only one connection per process"))
        Expect(conn).Should(BeNil())
 }
 
+func TestAsyncConnection(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       ctx.conn.Disconnect()
+       conn, ch, err := core.AsyncConnect(ctx.mockVpp)
+       ctx.conn = conn
+
+       Expect(err).ShouldNot(HaveOccurred())
+       Expect(conn).ShouldNot(BeNil())
+
+       ev := <-ch
+       Expect(ev.State).Should(BeEquivalentTo(core.Connected))
+}
+
 func TestFullBuffer(t *testing.T) {
-       ctx := setupTest(t)
+       ctx := setupTest(t, false)
        defer ctx.teardownTest()
 
        // close the default API channel
@@ -175,9 +213,9 @@ func TestFullBuffer(t *testing.T) {
        vppReply := <-ctx.ch.ReplyChan
        Expect(vppReply).ShouldNot(BeNil())
 
-       received := false
+       var received bool
        select {
-       case vppReply = <-ctx.ch.ReplyChan:
+       case <-ctx.ch.ReplyChan:
                received = true // this should not happen
        default:
                received = false // no reply to be received
@@ -188,14 +226,14 @@ func TestFullBuffer(t *testing.T) {
 func TestCodec(t *testing.T) {
        RegisterTestingT(t)
 
-       codec := &MsgCodec{}
+       codec := &core.MsgCodec{}
 
        // request
-       data, err := codec.EncodeMsg(&vpe.CreateLoopback{MacAddress: []byte{1, 2, 3, 4, 5, 6}}, 11)
+       data, err := codec.EncodeMsg(&interfaces.CreateLoopback{MacAddress: []byte{1, 2, 3, 4, 5, 6}}, 11)
        Expect(err).ShouldNot(HaveOccurred())
        Expect(data).ShouldNot(BeEmpty())
 
-       msg1 := &vpe.CreateLoopback{}
+       msg1 := &interfaces.CreateLoopback{}
        err = codec.DecodeMsg(data, msg1)
        Expect(err).ShouldNot(HaveOccurred())
        Expect(msg1.MacAddress).To(BeEquivalentTo([]byte{1, 2, 3, 4, 5, 6}))
@@ -211,11 +249,11 @@ func TestCodec(t *testing.T) {
        Expect(msg2.Retval).To(BeEquivalentTo(55))
 
        // other
-       data, err = codec.EncodeMsg(&vpe.VnetIP4FibCounters{VrfID: 77}, 33)
+       data, err = codec.EncodeMsg(&stats.VnetIP4FibCounters{VrfID: 77}, 33)
        Expect(err).ShouldNot(HaveOccurred())
        Expect(data).ShouldNot(BeEmpty())
 
-       msg3 := &vpe.VnetIP4FibCounters{}
+       msg3 := &stats.VnetIP4FibCounters{}
        err = codec.DecodeMsg(data, msg3)
        Expect(err).ShouldNot(HaveOccurred())
        Expect(msg3.VrfID).To(BeEquivalentTo(77))
@@ -224,7 +262,7 @@ func TestCodec(t *testing.T) {
 func TestCodecNegative(t *testing.T) {
        RegisterTestingT(t)
 
-       codec := &MsgCodec{}
+       codec := &core.MsgCodec{}
 
        // nil message for encoding
        data, err := codec.EncodeMsg(nil, 15)
@@ -242,3 +280,263 @@ func TestCodecNegative(t *testing.T) {
        Expect(err).Should(HaveOccurred())
        Expect(err.Error()).To(ContainSubstring("EOF"))
 }
+
+func TestSimpleRequestsWithSequenceNumbers(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       var reqCtx []*api.RequestCtx
+       for i := 0; i < 10; i++ {
+               ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
+               req := &vpe.ControlPing{}
+               reqCtx = append(reqCtx, ctx.ch.SendRequest(req))
+       }
+
+       for i := 0; i < 10; i++ {
+               reply := &vpe.ControlPingReply{}
+               err := reqCtx[i].ReceiveReply(reply)
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(reply.Retval).To(BeEquivalentTo(i))
+       }
+}
+
+func TestMultiRequestsWithSequenceNumbers(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       msgs := []api.Message{}
+       for i := 0; i < 10; i++ {
+               msgs = append(msgs, &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)})
+       }
+       ctx.mockVpp.MockReply(msgs...)
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{})
+
+       // send multipart request
+       reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+
+       cnt := 0
+       for {
+               Expect(cnt < 11).To(BeTrue())
+
+               // receive a reply
+               reply := &interfaces.SwInterfaceDetails{}
+               lastReplyReceived, err := reqCtx.ReceiveReply(reply)
+
+               if lastReplyReceived {
+                       break // break out of the loop
+               }
+
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(reply.SwIfIndex).To(BeEquivalentTo(cnt))
+
+               cnt++
+       }
+
+       Expect(cnt).To(BeEquivalentTo(10))
+}
+
+func TestSimpleRequestWithTimeout(t *testing.T) {
+       ctx := setupTest(t, true)
+       defer ctx.teardownTest()
+
+       // reply for a previous timeouted requests to be ignored
+       ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
+               Msg:    &vpe.ControlPingReply{Retval: 1},
+               SeqNum: 0,
+       })
+
+       // send reply later
+       req1 := &vpe.ControlPing{}
+       reqCtx1 := ctx.ch.SendRequest(req1)
+
+       reply := &vpe.ControlPingReply{}
+       err := reqCtx1.ReceiveReply(reply)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
+
+       ctx.mockVpp.MockReplyWithContext(
+               // reply for the previous request
+               mock.MsgWithContext{
+                       Msg:    &vpe.ControlPingReply{Retval: 1},
+                       SeqNum: 1,
+               },
+               // reply for the next request
+               mock.MsgWithContext{
+                       Msg:    &vpe.ControlPingReply{Retval: 2},
+                       SeqNum: 2,
+               })
+
+       // next request
+       req2 := &vpe.ControlPing{}
+       reqCtx2 := ctx.ch.SendRequest(req2)
+
+       // second request should ignore the first reply and return the second one
+       reply = &vpe.ControlPingReply{}
+       err = reqCtx2.ReceiveReply(reply)
+       Expect(err).To(BeNil())
+       Expect(reply.Retval).To(BeEquivalentTo(2))
+}
+
+func TestSimpleRequestsWithMissingReply(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       // request without reply
+       req1 := &vpe.ControlPing{}
+       reqCtx1 := ctx.ch.SendRequest(req1)
+
+       // another request without reply
+       req2 := &vpe.ControlPing{}
+       reqCtx2 := ctx.ch.SendRequest(req2)
+
+       // third request with reply
+       ctx.mockVpp.MockReplyWithContext(mock.MsgWithContext{
+               Msg:    &vpe.ControlPingReply{Retval: 3},
+               SeqNum: 3,
+       })
+       req3 := &vpe.ControlPing{}
+       reqCtx3 := ctx.ch.SendRequest(req3)
+
+       // the first two should fail, but not consume reply for the 3rd
+       reply := &vpe.ControlPingReply{}
+       err := reqCtx1.ReceiveReply(reply)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+       reply = &vpe.ControlPingReply{}
+       err = reqCtx2.ReceiveReply(reply)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 2"))
+
+       // the second request should succeed
+       reply = &vpe.ControlPingReply{}
+       err = reqCtx3.ReceiveReply(reply)
+       Expect(err).To(BeNil())
+       Expect(reply.Retval).To(BeEquivalentTo(3))
+}
+
+func TestMultiRequestsWithErrors(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       // replies for a previous timeouted requests to be ignored
+       msgs := []mock.MsgWithContext{}
+       msgs = append(msgs,
+               mock.MsgWithContext{
+                       Msg:    &vpe.ControlPingReply{Retval: 1},
+                       SeqNum: 0xffff - 1,
+               },
+               mock.MsgWithContext{
+                       Msg:    &vpe.ControlPingReply{Retval: 1},
+                       SeqNum: 0xffff,
+               },
+               mock.MsgWithContext{
+                       Msg:    &vpe.ControlPingReply{Retval: 1},
+                       SeqNum: 0,
+               })
+
+       for i := 0; i < 10; i++ {
+               msgs = append(msgs,
+                       mock.MsgWithContext{
+                               Msg:       &interfaces.SwInterfaceDetails{SwIfIndex: uint32(i)},
+                               SeqNum:    1,
+                               Multipart: true,
+                       })
+       }
+       // missing finalizing control ping
+
+       // reply for a next request
+       msgs = append(msgs,
+               mock.MsgWithContext{
+                       Msg:       &vpe.ControlPingReply{Retval: 2},
+                       SeqNum:    2,
+                       Multipart: false,
+               })
+
+       // queue replies
+       ctx.mockVpp.MockReplyWithContext(msgs...)
+
+       // send multipart request
+       reqCtx := ctx.ch.SendMultiRequest(&interfaces.SwInterfaceDump{})
+
+       for i := 0; i < 10; i++ {
+               // receive multi-part replies
+               reply := &interfaces.SwInterfaceDetails{}
+               lastReplyReceived, err := reqCtx.ReceiveReply(reply)
+
+               Expect(lastReplyReceived).To(BeFalse())
+               Expect(err).ShouldNot(HaveOccurred())
+               Expect(reply.SwIfIndex).To(BeEquivalentTo(i))
+       }
+
+       // missing closing control ping
+       reply := &interfaces.SwInterfaceDetails{}
+       _, err := reqCtx.ReceiveReply(reply)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+       // try again - still fails and nothing consumed
+       _, err = reqCtx.ReceiveReply(reply)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(Equal("missing binary API reply with sequence number: 1"))
+
+       // reply for the second request has not been consumed
+       reqCtx2 := ctx.ch.SendRequest(&vpe.ControlPing{})
+       reply2 := &vpe.ControlPingReply{}
+       err = reqCtx2.ReceiveReply(reply2)
+       Expect(err).To(BeNil())
+       Expect(reply2.Retval).To(BeEquivalentTo(2))
+}
+
+func TestRequestsOrdering(t *testing.T) {
+       ctx := setupTest(t, false)
+       defer ctx.teardownTest()
+
+       // the orderings of SendRequest and ReceiveReply calls should match, otherwise
+       // some replies will get thrown away
+
+       // first request
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 1})
+       req1 := &vpe.ControlPing{}
+       reqCtx1 := ctx.ch.SendRequest(req1)
+
+       // second request
+       ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: 2})
+       req2 := &vpe.ControlPing{}
+       reqCtx2 := ctx.ch.SendRequest(req2)
+
+       // if reply for the second request is read first, the reply for the first
+       // request gets thrown away.
+       reply2 := &vpe.ControlPingReply{}
+       err := reqCtx2.ReceiveReply(reply2)
+       Expect(err).To(BeNil())
+       Expect(reply2.Retval).To(BeEquivalentTo(2))
+
+       // first request has already been considered closed
+       reply1 := &vpe.ControlPingReply{}
+       err = reqCtx1.ReceiveReply(reply1)
+       Expect(err).ToNot(BeNil())
+       Expect(err.Error()).To(HavePrefix("no reply received within the timeout period"))
+}
+
+func TestCycleOverSetOfSequenceNumbers(t *testing.T) {
+       ctx := setupTest(t, true)
+       defer ctx.teardownTest()
+
+       numIters := 0xffff + 100
+       reqCtx := make(map[int]*api.RequestCtx)
+
+       for i := 0; i < numIters+30; /* receiver is 30 reqs behind */ i++ {
+               if i < numIters {
+                       ctx.mockVpp.MockReply(&vpe.ControlPingReply{Retval: int32(i)})
+                       req := &vpe.ControlPing{}
+                       reqCtx[i] = ctx.ch.SendRequest(req)
+               }
+               if i > 30 {
+                       reply := &vpe.ControlPingReply{}
+                       err := reqCtx[i-30].ReceiveReply(reply)
+                       Expect(err).ShouldNot(HaveOccurred())
+                       Expect(reply.Retval).To(BeEquivalentTo(i - 30))
+               }
+       }
+}