102 lines
2.5 KiB
Go
102 lines
2.5 KiB
Go
|
|
package runtime
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
"github.com/tegwick/fluid-core/internal/contract"
|
||
|
|
)
|
||
|
|
|
||
|
|
// stallingSink never returns, standing in for a wedged evidence store.
|
||
|
|
type stallingSink struct{ entered chan struct{} }
|
||
|
|
|
||
|
|
func (s *stallingSink) Write(ctx context.Context, _ contract.FluidTelemetry) error {
|
||
|
|
select {
|
||
|
|
case s.entered <- struct{}{}:
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
<-ctx.Done()
|
||
|
|
return ctx.Err()
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestEmitNeverBlocks is the test for Blueprint invariant 34.2. If it ever
|
||
|
|
// starts failing, the data plane has acquired a dependency on the observation
|
||
|
|
// plane and the isolation the whole architecture rests on is gone.
|
||
|
|
func TestEmitNeverBlocks(t *testing.T) {
|
||
|
|
sink := &stallingSink{entered: make(chan struct{}, 1)}
|
||
|
|
e := NewEmitter(sink, EmitterOptions{Buffer: 8, Workers: 1, WriteTimeout: time.Hour})
|
||
|
|
defer func() {
|
||
|
|
// The stalled worker cannot drain, so do not wait on Close.
|
||
|
|
_ = e
|
||
|
|
}()
|
||
|
|
|
||
|
|
<-func() chan struct{} {
|
||
|
|
e.Emit(contract.FluidTelemetry{ID: "warm"})
|
||
|
|
return sink.entered
|
||
|
|
}()
|
||
|
|
|
||
|
|
done := make(chan struct{})
|
||
|
|
go func() {
|
||
|
|
// Far more than the buffer holds, against a sink that never completes.
|
||
|
|
for i := 0; i < 10_000; i++ {
|
||
|
|
e.Emit(contract.FluidTelemetry{ID: "ev"})
|
||
|
|
}
|
||
|
|
close(done)
|
||
|
|
}()
|
||
|
|
|
||
|
|
select {
|
||
|
|
case <-done:
|
||
|
|
case <-time.After(2 * time.Second):
|
||
|
|
t.Fatal("Emit blocked while the sink was stalled; the request path is now coupled to telemetry")
|
||
|
|
}
|
||
|
|
|
||
|
|
if e.Stats().Dropped == 0 {
|
||
|
|
t.Error("expected drops against a stalled sink, got none")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// countingSink records deliveries.
|
||
|
|
type countingSink struct {
|
||
|
|
ch chan contract.FluidTelemetry
|
||
|
|
err error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (s *countingSink) Write(_ context.Context, ev contract.FluidTelemetry) error {
|
||
|
|
if s.err != nil {
|
||
|
|
return s.err
|
||
|
|
}
|
||
|
|
s.ch <- ev
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEmitterDeliversAndDrains(t *testing.T) {
|
||
|
|
sink := &countingSink{ch: make(chan contract.FluidTelemetry, 32)}
|
||
|
|
e := NewEmitter(sink, EmitterOptions{Buffer: 32, Workers: 2})
|
||
|
|
|
||
|
|
for i := 0; i < 10; i++ {
|
||
|
|
e.Emit(contract.FluidTelemetry{ID: "ev", Kind: contract.FluidTelemetryKindRequest})
|
||
|
|
}
|
||
|
|
e.Close()
|
||
|
|
|
||
|
|
if got := len(sink.ch); got != 10 {
|
||
|
|
t.Errorf("delivered %d events, want 10", got)
|
||
|
|
}
|
||
|
|
if s := e.Stats(); s.Written != 10 || s.Dropped != 0 {
|
||
|
|
t.Errorf("stats = %+v, want 10 written and 0 dropped", s)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestEmitterCountsSinkFailures(t *testing.T) {
|
||
|
|
e := NewEmitter(&countingSink{err: errors.New("store down")}, EmitterOptions{Buffer: 4, Workers: 1})
|
||
|
|
for i := 0; i < 4; i++ {
|
||
|
|
e.Emit(contract.FluidTelemetry{ID: "ev"})
|
||
|
|
}
|
||
|
|
e.Close()
|
||
|
|
|
||
|
|
if s := e.Stats(); s.Failed == 0 {
|
||
|
|
t.Errorf("sink failures not counted: %+v", s)
|
||
|
|
}
|
||
|
|
}
|