252 lines
5.8 KiB
Go
252 lines
5.8 KiB
Go
package prism
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"git.wisehodl.dev/jay/go-mana-component"
|
|
"github.com/stretchr/testify/assert"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Helpers
|
|
|
|
func newTestLetter(ctx context.Context, id uint64) OutboundLetter {
|
|
ctx, cancel := context.WithCancel(
|
|
component.MustExtend(ctx, "test_letter"))
|
|
return OutboundLetter{
|
|
id: id,
|
|
peerID: "wss://test",
|
|
data: []byte("[]"),
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// Tests
|
|
|
|
func TestCourierSendsAfterConnect(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
sendFunc := func(data Envelope) error {
|
|
sendCount.Add(1)
|
|
return nil
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
called := make(chan LetterOutcome, 1)
|
|
c.Enqueue(newTestLetter(ctx, 1), func(o LetterOutcome) { called <- o })
|
|
|
|
Never(t, func() bool { return sendCount.Load() > 0 },
|
|
"should not have sent while disconnected")
|
|
|
|
c.HandleConnect()
|
|
|
|
Eventually(t, func() bool { return sendCount.Load() > 0 },
|
|
"should have sent after connect")
|
|
|
|
var outcome LetterOutcome
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case outcome = <-called:
|
|
return true
|
|
}
|
|
}, "should have returned outcome")
|
|
|
|
assert.Equal(t, uint64(1), outcome.LetterID)
|
|
assert.Equal(t, "wss://test", outcome.PeerID)
|
|
assert.Equal(t, OutcomeSent, outcome.Kind)
|
|
assert.False(t, outcome.SentAt.IsZero())
|
|
assert.True(t, outcome.MissedAt.IsZero())
|
|
assert.Equal(t, 0, outcome.Retries)
|
|
}
|
|
|
|
func TestCourierMultipleSends(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
sendFunc := func(data Envelope) error {
|
|
sendCount.Add(1)
|
|
return nil
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
c.HandleConnect()
|
|
|
|
outcomes := make([]LetterOutcome, 0, 2)
|
|
called := make(chan LetterOutcome, 2)
|
|
c.Enqueue(newTestLetter(ctx, 1), func(o LetterOutcome) { called <- o })
|
|
c.Enqueue(newTestLetter(ctx, 2), func(o LetterOutcome) { called <- o })
|
|
|
|
Eventually(t, func() bool { return sendCount.Load() == 2 },
|
|
"should have sent letters")
|
|
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case o := <-called:
|
|
outcomes = append(outcomes, o)
|
|
return len(outcomes) == 2
|
|
}
|
|
}, "should have returned 2 outcomes")
|
|
|
|
// callbacks are called in goroutines and may arrive out of order
|
|
assert.Equal(t, OutcomeSent, outcomes[0].Kind)
|
|
assert.Equal(t, OutcomeSent, outcomes[1].Kind)
|
|
}
|
|
|
|
func TestCourierSkipsCancelledLetter(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
sendFunc := func(data Envelope) error {
|
|
sendCount.Add(1)
|
|
return nil
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
c.HandleConnect()
|
|
|
|
l := newTestLetter(ctx, 1)
|
|
l.cancel()
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
c.Enqueue(l, func(o LetterOutcome) { called <- o })
|
|
|
|
var outcome LetterOutcome
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case outcome = <-called:
|
|
return true
|
|
}
|
|
}, "should have returned outcome")
|
|
|
|
assert.Equal(t, OutcomeCancelled, outcome.Kind)
|
|
}
|
|
|
|
func TestCourierRetryOnFailure(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
sendFunc := func(data Envelope) error {
|
|
sendCount.Add(1)
|
|
if sendCount.Load() < 3 {
|
|
return fmt.Errorf("transient failure")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
c.HandleConnect()
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
c.Enqueue(newTestLetter(ctx, 1), func(o LetterOutcome) { called <- o })
|
|
|
|
Eventually(t, func() bool { return sendCount.Load() > 0 },
|
|
"should send eventually")
|
|
|
|
var outcome LetterOutcome
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case outcome = <-called:
|
|
return true
|
|
}
|
|
}, "should have returned outcome")
|
|
|
|
assert.Equal(t, OutcomeSent, outcome.Kind)
|
|
assert.Equal(t, 2, outcome.Retries)
|
|
}
|
|
|
|
func TestCourierPauseOnDisconnect(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
var gate atomic.Bool
|
|
gate.Store(false)
|
|
sendFunc := func(data Envelope) error {
|
|
// gated send
|
|
if gate.Load() {
|
|
sendCount.Add(1)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("gate is closed")
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
c.HandleConnect()
|
|
|
|
// queue a letter
|
|
called := make(chan LetterOutcome, 1)
|
|
c.Enqueue(newTestLetter(ctx, 1), func(o LetterOutcome) { called <- o })
|
|
|
|
// manually wait for letters to queue
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// manually wait for disconnect toggle
|
|
c.HandleDisconnect()
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// open gate
|
|
gate.Store(true)
|
|
|
|
// should never have sent in this time
|
|
Never(t, func() bool { return sendCount.Load() > 0 },
|
|
"should not have sent while disconnected")
|
|
|
|
// reconnect, gate is open, letter should send
|
|
c.HandleConnect()
|
|
Eventually(t, func() bool { return sendCount.Load() > 0 },
|
|
"should have sent")
|
|
}
|
|
|
|
func TestCourierDrainOnClose(t *testing.T) {
|
|
ctx := component.MustNew(context.Background(), "prism", "test")
|
|
|
|
var sendCount atomic.Uint32
|
|
sendFunc := func(data Envelope) error {
|
|
sendCount.Add(1)
|
|
return nil
|
|
}
|
|
|
|
c := NewCourier(ctx, sendFunc, nil)
|
|
|
|
// do not connect, queue some letters
|
|
outcomes := make([]LetterOutcome, 0, 2)
|
|
called := make(chan LetterOutcome, 4)
|
|
c.Enqueue(newTestLetter(ctx, 1), func(o LetterOutcome) { called <- o })
|
|
c.Enqueue(newTestLetter(ctx, 2), func(o LetterOutcome) { called <- o })
|
|
|
|
// should not send any letters
|
|
Never(t, func() bool { return sendCount.Load() > 0 },
|
|
"should not have sent letters")
|
|
|
|
// close the courier
|
|
c.Close()
|
|
|
|
// expect each letter to return cancelled
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case o := <-called:
|
|
outcomes = append(outcomes, o)
|
|
return len(outcomes) == 2
|
|
}
|
|
}, "should have returned 2 outcomes")
|
|
|
|
if len(outcomes) >= 2 {
|
|
assert.Equal(t, OutcomeCancelled, outcomes[0].Kind)
|
|
assert.Equal(t, OutcomeCancelled, outcomes[1].Kind)
|
|
}
|
|
}
|