226 lines
6.1 KiB
Go
226 lines
6.1 KiB
Go
package prism
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"github.com/stretchr/testify/assert"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Helpers
|
|
|
|
func mockPostmaster(
|
|
ctx context.Context,
|
|
) (pm *Postmaster, poolEvents chan PoolEvent) {
|
|
poolHasPeer := func(id string) (string, bool) { return id, true }
|
|
poolEvents = make(chan PoolEvent, 4)
|
|
poolSendFunc := func(id string, data Envelope) error { return nil }
|
|
pm = NewPostmaster(ctx, poolHasPeer, poolEvents, poolSendFunc, nil)
|
|
return
|
|
}
|
|
|
|
func expectLetterOutcome(
|
|
t *testing.T, ch chan LetterOutcome, kind LetterOutcomeKind,
|
|
) {
|
|
t.Helper()
|
|
|
|
var outcome LetterOutcome
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case outcome = <-ch:
|
|
return true
|
|
}
|
|
}, "should have received outcome")
|
|
|
|
assert.Equal(t, kind, outcome.Kind)
|
|
}
|
|
|
|
func expectAllLetterOutcomes(
|
|
t *testing.T, ch chan LetterOutcome, kind LetterOutcomeKind, count int,
|
|
) {
|
|
t.Helper()
|
|
|
|
outcomes := make([]LetterOutcome, 0, count)
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case o := <-ch:
|
|
outcomes = append(outcomes, o)
|
|
return len(outcomes) == count
|
|
}
|
|
}, fmt.Sprintf("should have returned %d outcomes", count))
|
|
|
|
if len(outcomes) >= count {
|
|
for i := range count {
|
|
assert.Equal(t, OutcomeCancelled, outcomes[i].Kind)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tests
|
|
|
|
func TestPostmasterUnknownPeerSend(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, _ := mockPostmaster(ctx)
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
expectLetterOutcome(t, called, OutcomeRejected)
|
|
}
|
|
|
|
func TestPostmasterSend(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventConnected, At: time.Now()}
|
|
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
expectLetterOutcome(t, called, OutcomeSent)
|
|
}
|
|
|
|
func TestPostmasterCancelInFlight(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
_, cancel := pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
// wait for letter to queue
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// cancel the letter using its callback
|
|
cancel()
|
|
|
|
// connect the pool
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventConnected, At: time.Now()}
|
|
|
|
expectLetterOutcome(t, called, OutcomeCancelled)
|
|
}
|
|
|
|
func TestPostmasterExpire(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
called := make(chan LetterOutcome, 1)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o },
|
|
WithDeadline(1*time.Millisecond))
|
|
|
|
// wait for letter to queue and expire
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// connect the pool
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventConnected, At: time.Now()}
|
|
|
|
expectLetterOutcome(t, called, OutcomeExpired)
|
|
}
|
|
|
|
func TestPostmasterPeerRemoved(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
// add peer, but do not connect
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
// send two letters
|
|
called := make(chan LetterOutcome, 2)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
// wait for them to hit the courier queue
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// remove the peer
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventRemoved, At: time.Now()}
|
|
|
|
// expect each letter to return cancelled
|
|
expectAllLetterOutcomes(t, called, OutcomeCancelled, 2)
|
|
|
|
// subsequent sends should fail
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
expectLetterOutcome(t, called, OutcomeRejected)
|
|
}
|
|
|
|
func TestPostmasterCourierCloseRace(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
// add peer, but do not connect
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
// remove the peer
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventRemoved, At: time.Now()}
|
|
|
|
// send a letter
|
|
time.Sleep(5 * time.Microsecond) // small wait lines up the race condition
|
|
var outcome *LetterOutcome
|
|
called := make(chan LetterOutcome, 1)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
Eventually(t, func() bool {
|
|
select {
|
|
default:
|
|
return false
|
|
case o := <-called:
|
|
outcome = &o
|
|
return true
|
|
}
|
|
}, "should have returned 1 outcomes")
|
|
|
|
if outcome == nil {
|
|
t.Fatal("did not receive an outcome")
|
|
}
|
|
|
|
// depending on the race, the outcome could be:
|
|
// close, then send: send is rejected by the postmaster
|
|
// send, then close: send is cancelled by the courier
|
|
assert.Contains(t,
|
|
[]LetterOutcomeKind{OutcomeCancelled, OutcomeRejected},
|
|
outcome.Kind,
|
|
)
|
|
}
|
|
|
|
func TestPostmasterClose(t *testing.T) {
|
|
ctx := context.Background()
|
|
pm, poolEvents := mockPostmaster(ctx)
|
|
|
|
// add peer, but do not connect
|
|
poolEvents <- PoolEvent{ID: "peer", Kind: EventAdded, At: time.Now()}
|
|
Eventually(t, func() bool { return len(pm.Peers()) > 0 }, "should add peer")
|
|
|
|
// send two letters
|
|
called := make(chan LetterOutcome, 2)
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
|
|
// wait for them to hit the courier queue
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
// close postmaster
|
|
pm.Close()
|
|
|
|
// expect each letter to return cancelled
|
|
expectAllLetterOutcomes(t, called, OutcomeCancelled, 2)
|
|
|
|
// subsequent sends should be rejected
|
|
pm.Send(ctx, "peer", nil, func(o LetterOutcome) { called <- o })
|
|
expectLetterOutcome(t, called, OutcomeRejected)
|
|
}
|