зеркало из https://github.com/Azure/go-amqp.git
1374 строки
45 KiB
Go
1374 строки
45 KiB
Go
package amqp
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"reflect"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/Azure/go-amqp/internal/encoding"
|
|
"github.com/Azure/go-amqp/internal/fake"
|
|
"github.com/Azure/go-amqp/internal/frames"
|
|
"github.com/Azure/go-amqp/internal/test"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSenderInvalidOptions(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", &SenderOptions{
|
|
SettlementMode: SenderSettleMode(3).Ptr(),
|
|
})
|
|
cancel()
|
|
require.Error(t, err)
|
|
require.Nil(t, snd)
|
|
}
|
|
|
|
func TestSenderMethodsNoSend(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
require.Equal(t, DurabilityUnsettledState, tt.Source.Durable)
|
|
require.Equal(t, ExpiryPolicyNever, tt.Source.ExpiryPolicy)
|
|
require.Equal(t, uint32(300), tt.Source.Timeout)
|
|
return newResponse(fake.SenderAttach(0, tt.Name, 0, SenderSettleModeUnsettled))
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, 0, nil))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
const (
|
|
linkAddr = "addr1"
|
|
linkName = "test1"
|
|
)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, linkAddr, &SenderOptions{
|
|
Name: linkName,
|
|
Durability: DurabilityUnsettledState,
|
|
ExpiryPolicy: ExpiryPolicyNever,
|
|
ExpiryTimeout: 300,
|
|
})
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
require.Equal(t, linkAddr, snd.Address())
|
|
require.Equal(t, linkName, snd.LinkName())
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, snd.Close(ctx))
|
|
cancel()
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendOnClosed(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, snd.Close(ctx))
|
|
cancel()
|
|
// sending on a closed sender returns ErrLinkClosed
|
|
var linkErr *LinkError
|
|
require.ErrorAs(t, snd.Send(context.Background(), NewMessage([]byte("failed")), nil), &linkErr)
|
|
require.Equal(t, "amqp: link closed", linkErr.Error())
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendOnSessionClosed(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, session.Close(ctx))
|
|
cancel()
|
|
// sending on a closed sender returns SessionError
|
|
var sessionErr *SessionError
|
|
err = snd.Send(context.Background(), NewMessage([]byte("failed")), nil)
|
|
require.ErrorAs(t, err, &sessionErr)
|
|
var amqpErr *Error
|
|
// there should be no inner error when closed on our side
|
|
require.False(t, errors.As(err, &amqpErr))
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendConcurrentSessionClosed(t *testing.T) {
|
|
muxSem := test.NewMuxSemaphore(0)
|
|
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := newSenderForSession(ctx, session, "target", nil, senderTestHooks{MuxTransfer: muxSem.OnLoop})
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
sendErr := make(chan error)
|
|
|
|
go func() {
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
err := snd.Send(ctx, NewMessage([]byte("failed")), nil)
|
|
cancel()
|
|
sendErr <- err
|
|
}()
|
|
|
|
muxSem.Wait()
|
|
|
|
// transfer was handed off to the sender's mux and it's now paused
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
require.NoError(t, session.Close(ctx))
|
|
cancel()
|
|
|
|
// resume the sender's mux
|
|
muxSem.Release(-1)
|
|
|
|
select {
|
|
case err := <-sendErr:
|
|
var sessionErr *SessionError
|
|
require.ErrorAs(t, err, &sessionErr)
|
|
case <-time.After(1 * time.Second):
|
|
t.Fatal("timed out waiting for send error")
|
|
}
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendOnConnClosed(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
require.NoError(t, client.Close())
|
|
// sending on a closed sender returns a ConnectionError
|
|
err = snd.Send(context.Background(), NewMessage([]byte("failed")), nil)
|
|
var connErr *ConnError
|
|
if !errors.As(err, &connErr) {
|
|
t.Fatalf("unexpected error type %T", err)
|
|
}
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendOnDetached(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
// initiate a server-side detach
|
|
const (
|
|
errcon = "detaching"
|
|
errdesc = "server side detach"
|
|
)
|
|
b, err := fake.PerformDetach(0, 0, &Error{Condition: errcon, Description: errdesc})
|
|
require.NoError(t, err)
|
|
netConn.SendFrame(b)
|
|
// sending on a detached link returns a LinkError
|
|
err = snd.Send(context.Background(), NewMessage([]byte("failed")), nil)
|
|
var linkErr *LinkError
|
|
require.ErrorAs(t, err, &linkErr)
|
|
require.Equal(t, ErrCond(errcon), linkErr.RemoteErr.Condition)
|
|
require.Equal(t, errdesc, linkErr.RemoteErr.Description)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderCloseTimeout(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
return newResponse(fake.SenderAttach(0, tt.Name, tt.Handle, SenderSettleModeUnsettled))
|
|
case *frames.PerformDetach:
|
|
b, err := fake.PerformDetach(0, tt.Handle, nil)
|
|
if err != nil {
|
|
return fake.Response{}, err
|
|
}
|
|
// include a write delay to trigger sender close timeout
|
|
return fake.Response{Payload: b, WriteDelay: 1 * time.Second}, nil
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = snd.Close(ctx)
|
|
cancel()
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = snd.Close(ctx)
|
|
cancel()
|
|
var linkErr *LinkError
|
|
require.ErrorAs(t, err, &linkErr)
|
|
require.Contains(t, linkErr.Error(), context.DeadlineExceeded.Error())
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderAttachError(t *testing.T) {
|
|
detachAck := make(chan bool, 1)
|
|
var enqueueFrames func(string)
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
enqueueFrames(tt.Name)
|
|
return fake.Response{}, nil
|
|
case *frames.PerformDetach:
|
|
// we don't need to respond to the ack
|
|
detachAck <- true
|
|
return fake.Response{}, nil
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
const (
|
|
errcon = "cantattach"
|
|
errdesc = "server side error"
|
|
)
|
|
|
|
enqueueFrames = func(n string) {
|
|
// send an invalid attach response
|
|
b, err := fake.EncodeFrame(frames.TypeAMQP, 0, &frames.PerformAttach{
|
|
Name: n,
|
|
Role: encoding.RoleReceiver,
|
|
})
|
|
require.NoError(t, err)
|
|
netConn.SendFrame(b)
|
|
// now follow up with a detach frame
|
|
b, err = fake.EncodeFrame(frames.TypeAMQP, 0, &frames.PerformDetach{
|
|
Error: &encoding.Error{
|
|
Condition: errcon,
|
|
Description: errdesc,
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
netConn.SendFrame(b)
|
|
}
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
var de *Error
|
|
if !errors.As(err, &de) {
|
|
t.Fatalf("unexpected error type %T", err)
|
|
}
|
|
require.Equal(t, ErrCond(errcon), de.Condition)
|
|
require.Equal(t, errdesc, de.Description)
|
|
require.Nil(t, snd)
|
|
require.Equal(t, true, <-detachAck)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendMismatchedModes(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", &SenderOptions{
|
|
SettlementMode: SenderSettleModeSettled.Ptr(),
|
|
})
|
|
cancel()
|
|
require.Error(t, err)
|
|
require.Equal(t, "amqp: sender settlement mode \"settled\" requested, received \"unsettled\" from server", err.Error())
|
|
require.Nil(t, snd)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendSuccess(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeUnsettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch tt := req.(type) {
|
|
case *frames.PerformTransfer:
|
|
if tt.More {
|
|
return fake.Response{}, errors.New("didn't expect more to be true")
|
|
}
|
|
if tt.Settled {
|
|
return fake.Response{}, errors.New("didn't expect message to be settled")
|
|
}
|
|
if tt.MessageFormat == nil {
|
|
return fake.Response{}, errors.New("unexpected nil MessageFormat")
|
|
}
|
|
if !reflect.DeepEqual([]byte{0, 83, 117, 160, 4, 116, 101, 115, 116}, tt.Payload) {
|
|
return fake.Response{}, fmt.Errorf("unexpected payload %v", tt.Payload)
|
|
}
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, *tt.DeliveryID, nil, &encoding.StateAccepted{}))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, snd.Send(ctx, NewMessage([]byte("test")), nil))
|
|
cancel()
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendSettled(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeSettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch tt := req.(type) {
|
|
case *frames.PerformTransfer:
|
|
if tt.More {
|
|
return fake.Response{}, errors.New("didn't expect more to be true")
|
|
}
|
|
if !tt.Settled {
|
|
return fake.Response{}, errors.New("expected message to be settled")
|
|
}
|
|
if !reflect.DeepEqual([]byte{0, 83, 117, 160, 4, 116, 101, 115, 116}, tt.Payload) {
|
|
return fake.Response{}, fmt.Errorf("unexpected payload %v", tt.Payload)
|
|
}
|
|
return fake.Response{}, nil
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", &SenderOptions{
|
|
SettlementMode: SenderSettleModeSettled.Ptr(),
|
|
})
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, snd.Send(ctx, NewMessage([]byte("test")), nil))
|
|
cancel()
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendSettledModeMixed(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeSettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch tt := req.(type) {
|
|
case *frames.PerformTransfer:
|
|
if tt.More {
|
|
return fake.Response{}, errors.New("didn't expect more to be true")
|
|
}
|
|
if !tt.Settled {
|
|
return fake.Response{}, errors.New("expected message to be settled")
|
|
}
|
|
if !reflect.DeepEqual([]byte{0, 83, 117, 160, 4, 116, 101, 115, 116}, tt.Payload) {
|
|
return fake.Response{}, fmt.Errorf("unexpected payload %v", tt.Payload)
|
|
}
|
|
return fake.Response{}, nil
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{
|
|
ChunkSize: 8,
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.NoError(t, snd.Send(ctx, NewMessage([]byte("test")), &SendOptions{Settled: true}))
|
|
cancel()
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendSettledError(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", &SenderOptions{
|
|
SettlementMode: SenderSettleModeUnsettled.Ptr(),
|
|
})
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.Error(t, snd.Send(ctx, NewMessage([]byte("test")), &SendOptions{Settled: true}))
|
|
cancel()
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendRejectedNoDetach(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
return newResponse(fake.SenderAttach(0, tt.Name, 0, SenderSettleModeUnsettled))
|
|
case *frames.PerformTransfer:
|
|
// reject first delivery
|
|
if *tt.DeliveryID == 0 {
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, *tt.DeliveryID, nil, &encoding.StateRejected{
|
|
Error: &Error{
|
|
Condition: "rejected",
|
|
Description: "didn't like it",
|
|
},
|
|
}))
|
|
}
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, *tt.DeliveryID, nil, &encoding.StateAccepted{}))
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, 0, nil))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = snd.Send(ctx, NewMessage([]byte("test")), nil)
|
|
cancel()
|
|
var asErr *Error
|
|
if !errors.As(err, &asErr) {
|
|
t.Fatalf("unexpected error type %T", err)
|
|
}
|
|
require.Equal(t, ErrCond("rejected"), asErr.Condition)
|
|
|
|
// link should *not* be detached
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = snd.Send(ctx, NewMessage([]byte("test")), nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendDetached(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeUnsettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch req.(type) {
|
|
case *frames.PerformTransfer:
|
|
return newResponse(fake.PerformDetach(0, 0, &Error{
|
|
Condition: "detached",
|
|
Description: "server exploded",
|
|
}))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = snd.Send(ctx, NewMessage([]byte("test")), nil)
|
|
cancel()
|
|
var linkErr *LinkError
|
|
require.ErrorAs(t, err, &linkErr)
|
|
require.NotNil(t, linkErr.RemoteErr)
|
|
require.Equal(t, ErrCond("detached"), linkErr.RemoteErr.Condition)
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendTimeout(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
// no credits have been issued so the send will time out
|
|
ctx, cancel = context.WithTimeout(context.Background(), 10*time.Millisecond)
|
|
err = snd.Send(ctx, NewMessage([]byte("test")), nil)
|
|
cancel()
|
|
|
|
var amqpErr *Error
|
|
require.ErrorAs(t, err, &amqpErr)
|
|
require.EqualValues(t, ErrCondTransferLimitExceeded, amqpErr.Condition)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendMsgTooBig(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
mode := SenderSettleModeUnsettled
|
|
b, err := fake.EncodeFrame(frames.TypeAMQP, 0, &frames.PerformAttach{
|
|
Name: tt.Name,
|
|
Handle: 0,
|
|
Role: encoding.RoleReceiver,
|
|
Target: &frames.Target{
|
|
Address: "test",
|
|
Durable: encoding.DurabilityNone,
|
|
ExpiryPolicy: encoding.ExpirySessionEnd,
|
|
},
|
|
SenderSettleMode: &mode,
|
|
MaxMessageSize: 16, // really small messages only
|
|
})
|
|
if err != nil {
|
|
return fake.Response{}, err
|
|
}
|
|
return fake.Response{Payload: b}, nil
|
|
case *frames.PerformTransfer:
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, *tt.DeliveryID, nil, &encoding.StateAccepted{}))
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, 0, nil))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
defer cancel()
|
|
err = snd.Send(ctx, NewMessage([]byte("test message that's too big")), nil)
|
|
|
|
var amqpErr *Error
|
|
require.ErrorAs(t, err, &amqpErr)
|
|
require.Equal(t, Error{
|
|
Condition: ErrCondMessageSizeExceeded,
|
|
Description: "encoded message size exceeds max of 16",
|
|
}, *amqpErr)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
defer cancel()
|
|
err = snd.Send(ctx, &Message{
|
|
DeliveryTag: []byte("this delivery tag is way bigger than it can be so we'll just return a distinguish amqp.Error"),
|
|
}, nil)
|
|
cancel()
|
|
|
|
require.ErrorAs(t, err, &amqpErr)
|
|
require.Equal(t, Error{
|
|
Condition: ErrCondMessageSizeExceeded,
|
|
Description: "delivery tag is over the allowed 32 bytes, len: 92",
|
|
}, *amqpErr)
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendTagTooBig(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeUnsettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch tt := req.(type) {
|
|
case *frames.PerformTransfer:
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, *tt.DeliveryID, nil, &encoding.StateAccepted{}))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
msg := NewMessage([]byte("test"))
|
|
// make the tag larger than max allowed of 32
|
|
msg.DeliveryTag = make([]byte, 33)
|
|
require.Error(t, snd.Send(ctx, msg, nil))
|
|
cancel()
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendMultiTransfer(t *testing.T) {
|
|
var deliveryID uint32
|
|
transferCount := 0
|
|
const maxReceiverFrameSize = 128
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch tt := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
b, err := fake.EncodeFrame(frames.TypeAMQP, 0, &frames.PerformOpen{
|
|
ChannelMax: 65535,
|
|
ContainerID: "container",
|
|
IdleTimeout: time.Minute,
|
|
MaxFrameSize: maxReceiverFrameSize, // really small max frame size
|
|
})
|
|
if err != nil {
|
|
return fake.Response{}, err
|
|
}
|
|
return fake.Response{Payload: b}, nil
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
return newResponse(fake.SenderAttach(0, tt.Name, 0, SenderSettleModeUnsettled))
|
|
case *frames.PerformTransfer:
|
|
if tt.DeliveryID != nil {
|
|
// deliveryID is only sent on the first transfer frame for multi-frame transfers
|
|
if transferCount != 0 {
|
|
return fake.Response{}, fmt.Errorf("unexpected DeliveryID for frame number %d", transferCount)
|
|
}
|
|
deliveryID = *tt.DeliveryID
|
|
}
|
|
if tt.MessageFormat != nil && transferCount != 0 {
|
|
// MessageFormat is only sent on the first transfer frame for multi-frame transfers
|
|
return fake.Response{}, fmt.Errorf("unexpected MessageFormat for frame number %d", transferCount)
|
|
} else if tt.MessageFormat == nil && transferCount == 0 {
|
|
return fake.Response{}, errors.New("unexpected nil MessageFormat")
|
|
}
|
|
if tt.More {
|
|
transferCount++
|
|
return fake.Response{}, nil
|
|
}
|
|
return newResponse(fake.PerformDisposition(encoding.RoleReceiver, 0, deliveryID, nil, &encoding.StateAccepted{}))
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, 0, nil))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{
|
|
ChunkSize: 8,
|
|
})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
payload := make([]byte, maxReceiverFrameSize*4)
|
|
for i := 0; i < maxReceiverFrameSize*4; i++ {
|
|
payload[i] = byte(i % 256)
|
|
}
|
|
require.NoError(t, snd.Send(ctx, NewMessage(payload), nil))
|
|
cancel()
|
|
|
|
// split up into 8 transfers due to transfer frame header size
|
|
require.Equal(t, 8, transferCount)
|
|
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderConnReaderError(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
go func() {
|
|
// trigger some kind of error
|
|
netConn.ReadErr <- errors.New("failed")
|
|
}()
|
|
|
|
err = snd.Send(context.Background(), NewMessage([]byte("failed")), nil)
|
|
var connErr *ConnError
|
|
if !errors.As(err, &connErr) {
|
|
t.Fatalf("unexpected error type %T", err)
|
|
}
|
|
|
|
err = client.Close()
|
|
if !errors.As(err, &connErr) {
|
|
t.Fatalf("unexpected error type %T", err)
|
|
}
|
|
}
|
|
|
|
func TestSenderConnWriterError(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
sendInitialFlowFrame(t, 0, netConn, 0, 100)
|
|
|
|
// simulate some connWriter error
|
|
netConn.WriteErr <- errors.New("failed")
|
|
|
|
err = snd.Send(context.Background(), NewMessage([]byte("failed")), nil)
|
|
var connErr *ConnError
|
|
require.ErrorAs(t, err, &connErr)
|
|
require.Equal(t, "failed", connErr.Error())
|
|
|
|
err = client.Close()
|
|
require.ErrorAs(t, err, &connErr)
|
|
require.Equal(t, "failed", connErr.Error())
|
|
}
|
|
|
|
func TestSenderFlowFrameWithEcho(t *testing.T) {
|
|
linkCredit := uint32(1)
|
|
echo := make(chan struct{})
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeUnsettled)(remoteChannel, req)
|
|
if resp.Payload != nil || err != nil {
|
|
return resp, err
|
|
}
|
|
switch tt := req.(type) {
|
|
case *frames.PerformFlow:
|
|
require.False(t, tt.Echo)
|
|
defer func() { close(echo) }()
|
|
// here we receive the echo. verify state
|
|
if id := *tt.Handle; id != 0 {
|
|
return fake.Response{}, fmt.Errorf("unexpected Handle %d", id)
|
|
}
|
|
if dc := *tt.DeliveryCount; dc != 0 {
|
|
return fake.Response{}, fmt.Errorf("unexpected DeliveryCount %d", dc)
|
|
}
|
|
if lc := *tt.LinkCredit; lc != linkCredit {
|
|
return fake.Response{}, fmt.Errorf("unexpected LinkCredit %d", lc)
|
|
}
|
|
return fake.Response{}, nil
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
sender, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
nextIncomingID := uint32(1)
|
|
b, err := fake.EncodeFrame(frames.TypeAMQP, 0, &frames.PerformFlow{
|
|
Handle: &sender.l.outputHandle,
|
|
NextIncomingID: &nextIncomingID,
|
|
IncomingWindow: 100,
|
|
OutgoingWindow: 100,
|
|
NextOutgoingID: 1,
|
|
LinkCredit: &linkCredit,
|
|
Echo: true,
|
|
})
|
|
require.NoError(t, err)
|
|
netConn.SendFrame(b)
|
|
|
|
<-echo
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
err = sender.Close(ctx)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestNewSenderTimedOut(t *testing.T) {
|
|
var senderCount uint32
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch fr := req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformAttach:
|
|
if senderCount == 0 {
|
|
senderCount++
|
|
b, err := fake.SenderAttach(0, fr.Name, fr.Handle, SenderSettleModeMixed)
|
|
if err != nil {
|
|
return fake.Response{}, err
|
|
}
|
|
// include a write delay so NewSender times out
|
|
return fake.Response{Payload: b, WriteDelay: 100 * time.Millisecond}, nil
|
|
}
|
|
return newResponse(fake.SenderAttach(0, fr.Name, fr.Handle, SenderSettleModeMixed))
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, fr.Handle, nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
// first sender fails due to deadline exceeded
|
|
ctx, cancel = context.WithTimeout(context.Background(), 20*time.Millisecond)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.ErrorIs(t, err, context.DeadlineExceeded)
|
|
require.Nil(t, snd)
|
|
|
|
// should have one sender to clean up
|
|
require.Len(t, session.abandonedLinks, 1)
|
|
require.Len(t, session.linksByKey, 1)
|
|
|
|
// creating a new sender cleans up the old one
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err = session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
require.Empty(t, session.abandonedLinks)
|
|
require.Len(t, session.linksByKey, 1)
|
|
}
|
|
|
|
func TestNewSenderWriteError(t *testing.T) {
|
|
detachAck := make(chan struct{})
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformAttach:
|
|
return fake.Response{}, errors.New("write error")
|
|
case *frames.PerformDetach:
|
|
close(detachAck)
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
var connErr *ConnError
|
|
require.ErrorAs(t, err, &connErr)
|
|
require.Equal(t, "write error", connErr.Error())
|
|
require.Nil(t, snd)
|
|
|
|
select {
|
|
case <-time.After(time.Second):
|
|
// expected
|
|
case <-detachAck:
|
|
t.Fatal("unexpected ack")
|
|
}
|
|
|
|
// cannot check handle count as this kills the connection
|
|
}
|
|
|
|
func TestNewSenderContextCancelled(t *testing.T) {
|
|
senderCtx, senderCancel := context.WithCancel(context.Background())
|
|
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
switch req.(type) {
|
|
case *fake.AMQPProto:
|
|
return newResponse(fake.ProtoHeader(fake.ProtoAMQP))
|
|
case *frames.PerformOpen:
|
|
return newResponse(fake.PerformOpen("container"))
|
|
case *frames.PerformClose:
|
|
return newResponse(fake.PerformClose(nil))
|
|
case *frames.PerformBegin:
|
|
return newResponse(fake.PerformBegin(0, remoteChannel))
|
|
case *frames.PerformEnd:
|
|
return newResponse(fake.PerformEnd(0, nil))
|
|
case *frames.PerformAttach:
|
|
// cancel the context to trigger early exit and clean-up
|
|
senderCancel()
|
|
return fake.Response{}, nil
|
|
case *frames.PerformDetach:
|
|
return newResponse(fake.PerformDetach(0, 0, nil))
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
snd, err := session.NewSender(senderCtx, "target", nil)
|
|
require.ErrorIs(t, err, context.Canceled)
|
|
require.Nil(t, snd)
|
|
}
|
|
|
|
func TestSenderUnexpectedFrame(t *testing.T) {
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
// senders don't receive transfer frames
|
|
fr, err := fake.PerformTransfer(0, 0, 1, []byte("boom"))
|
|
require.NoError(t, err)
|
|
netConn.SendFrame(fr)
|
|
|
|
// sender should now be dead
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
err = snd.Send(ctx, NewMessage([]byte("hello")), nil)
|
|
cancel()
|
|
|
|
var linkErr *LinkError
|
|
require.ErrorAs(t, err, &linkErr)
|
|
require.NotNil(t, linkErr.inner)
|
|
require.ErrorContains(t, err, "unexpected frame *frames.PerformTransfer")
|
|
require.NoError(t, client.Close())
|
|
}
|
|
|
|
func TestSenderSendFails(t *testing.T) {
|
|
responder := func(remoteChannel uint16, req frames.FrameBody) (fake.Response, error) {
|
|
resp, err := senderFrameHandler(0, SenderSettleModeUnsettled)(remoteChannel, req)
|
|
if err != nil || resp.Payload != nil {
|
|
return resp, err
|
|
}
|
|
switch req.(type) {
|
|
case *frames.PerformTransfer:
|
|
return fake.Response{}, errors.New("send failed")
|
|
default:
|
|
return fake.Response{}, fmt.Errorf("unhandled frame %T", req)
|
|
}
|
|
}
|
|
netConn := fake.NewNetConn(responder, fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := session.NewSender(ctx, "target", nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
const linkCredit = 100
|
|
sendInitialFlowFrame(t, 0, netConn, 0, linkCredit)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
msg := NewMessage([]byte("test"))
|
|
connErr := &ConnError{}
|
|
require.ErrorAs(t, snd.Send(ctx, msg, nil), &connErr)
|
|
cancel()
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.ErrorAs(t, session.Close(ctx), &connErr)
|
|
cancel()
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 100*time.Millisecond)
|
|
require.ErrorAs(t, snd.Close(ctx), &connErr)
|
|
cancel()
|
|
|
|
require.ErrorAs(t, client.Close(), &connErr)
|
|
}
|
|
|
|
func TestSenderSendCancelled(t *testing.T) {
|
|
// selectSem allows three signals: flow frame from peer, sending the transfer frame, rollback
|
|
selectSem := test.NewMuxSemaphore(3)
|
|
// transferSem blocks before the transfer frame is send to the session mux
|
|
transferSem := test.NewMuxSemaphore(0)
|
|
|
|
netConn := fake.NewNetConn(senderFrameHandlerNoUnhandled(0, SenderSettleModeUnsettled), fake.NetConnOptions{})
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
|
client, err := NewConn(ctx, netConn, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
session, err := client.NewSession(ctx, nil)
|
|
cancel()
|
|
require.NoError(t, err)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
snd, err := newSenderForSession(ctx, session, "target", nil, senderTestHooks{
|
|
MuxSelect: selectSem.OnLoop,
|
|
MuxTransfer: transferSem.OnLoop,
|
|
})
|
|
cancel()
|
|
require.NoError(t, err)
|
|
require.NotNil(t, snd)
|
|
|
|
const linkCredit = uint32(100)
|
|
sendInitialFlowFrame(t, 0, netConn, 0, linkCredit)
|
|
|
|
ctx, cancel = context.WithTimeout(context.Background(), 1*time.Second)
|
|
sendErr := make(chan error)
|
|
go func() {
|
|
sendErr <- snd.Send(ctx, NewMessage([]byte("hello")), nil)
|
|
}()
|
|
|
|
transferSem.Wait()
|
|
|
|
// the sender's mux is now paused, cancel the context so the frame isn't written to net.Conn
|
|
cancel()
|
|
|
|
// close semaphore and resume mux
|
|
transferSem.Release(-1)
|
|
|
|
// wait for the send error
|
|
require.ErrorIs(t, <-sendErr, context.Canceled)
|
|
|
|
// wait for the mux to pause after receiving the rollback signal
|
|
selectSem.Wait()
|
|
|
|
// verify that the delivery count and link credit have been rolled back
|
|
assert.Zero(t, snd.l.deliveryCount)
|
|
assert.EqualValues(t, linkCredit, snd.l.linkCredit)
|
|
|
|
selectSem.Release(-1)
|
|
}
|