231 lines
6.9 KiB
Go
231 lines
6.9 KiB
Go
package eventbus
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/metrics"
|
|
)
|
|
|
|
func TestPartitionedAsyncSinkRawQueueIsIsolatedFromDerivedBacklog(t *testing.T) {
|
|
delegate := newBlockingDerivedSink()
|
|
sink := NewPartitionedAsyncSink(delegate, PartitionedAsyncConfig{
|
|
RawQueueSize: 1,
|
|
DerivedQueueSize: 1,
|
|
RawWorkers: 1,
|
|
DerivedWorkers: 1,
|
|
EnqueueTimeout: 20 * time.Millisecond,
|
|
OperationTimeout: time.Second,
|
|
})
|
|
defer sink.Close()
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808, Phone: "13307795425"}
|
|
if err := sink.PublishFields(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishFields() error = %v", err)
|
|
}
|
|
select {
|
|
case <-delegate.fieldsStarted:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("delegate fields publish was not started")
|
|
}
|
|
if err := sink.PublishUnified(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishUnified() error = %v", err)
|
|
}
|
|
|
|
start := time.Now()
|
|
if err := sink.PublishRaw(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishRaw() error = %v", err)
|
|
}
|
|
if elapsed := time.Since(start); elapsed > 50*time.Millisecond {
|
|
t.Fatalf("PublishRaw() blocked behind derived backlog for %s", elapsed)
|
|
}
|
|
delegate.release()
|
|
}
|
|
|
|
func TestPartitionedAsyncSinkRecordsPerQueueMetrics(t *testing.T) {
|
|
registry := metrics.NewRegistry()
|
|
delegate := newBlockingDerivedSink()
|
|
sink := NewPartitionedAsyncSink(delegate, PartitionedAsyncConfig{
|
|
RawQueueSize: 2,
|
|
DerivedQueueSize: 3,
|
|
RawWorkers: 1,
|
|
DerivedWorkers: 1,
|
|
OperationTimeout: time.Second,
|
|
Metrics: registry,
|
|
Name: "nats",
|
|
})
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolGB32960, VIN: "LNBSCB3D4R1234567"}
|
|
if err := sink.PublishRaw(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishRaw() error = %v", err)
|
|
}
|
|
if err := sink.PublishFields(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishFields() error = %v", err)
|
|
}
|
|
delegate.release()
|
|
if err := sink.Close(); err != nil {
|
|
t.Fatalf("Close() error = %v", err)
|
|
}
|
|
|
|
text := registry.Render()
|
|
for _, want := range []string{
|
|
`vehicle_async_sink_queue_capacity{queue="raw",sink="nats"} 2`,
|
|
`vehicle_async_sink_queue_capacity{queue="derived",sink="nats"} 3`,
|
|
`vehicle_async_sink_workers{queue="raw",sink="nats"} 1`,
|
|
`vehicle_async_sink_workers{queue="derived",sink="nats"} 1`,
|
|
`vehicle_async_sink_enqueue_total{kind="raw",sink="nats",status="queued"} 1`,
|
|
`vehicle_async_sink_enqueue_total{kind="fields",sink="nats",status="queued"} 1`,
|
|
`vehicle_async_sink_publish_total{kind="raw",sink="nats",status="ok"} 1`,
|
|
`vehicle_async_sink_publish_total{kind="fields",sink="nats",status="ok"} 1`,
|
|
`vehicle_async_sink_queue_wait_duration_ms_histogram_count{kind="raw",queue="raw",sink="nats"} 1`,
|
|
`vehicle_async_sink_queue_wait_recent_p99_ms{kind="raw",queue="raw",sink="nats"}`,
|
|
`vehicle_async_sink_queue_wait_recent_samples{kind="raw",queue="raw",sink="nats"} 1`,
|
|
`vehicle_async_sink_queue_wait_duration_ms_histogram_count{kind="fields",queue="derived",sink="nats"} 1`,
|
|
} {
|
|
if !strings.Contains(text, want) {
|
|
t.Fatalf("partitioned async metric missing %s:\n%s", want, text)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPartitionedAsyncSinkUsesIndependentDerivedEnqueueTimeout(t *testing.T) {
|
|
delegate := newBlockingDerivedSink()
|
|
sink := NewPartitionedAsyncSink(delegate, PartitionedAsyncConfig{
|
|
RawQueueSize: 1,
|
|
DerivedQueueSize: 1,
|
|
RawWorkers: 1,
|
|
DerivedWorkers: 1,
|
|
RawEnqueueTimeout: 200 * time.Millisecond,
|
|
DerivedEnqueueTimeout: 10 * time.Millisecond,
|
|
OperationTimeout: time.Second,
|
|
})
|
|
defer sink.Close()
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808, Phone: "13307795425"}
|
|
if err := sink.PublishFields(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishFields() error = %v", err)
|
|
}
|
|
select {
|
|
case <-delegate.fieldsStarted:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("delegate fields publish was not started")
|
|
}
|
|
if err := sink.PublishUnified(context.Background(), env); err != nil {
|
|
t.Fatalf("first PublishUnified() error = %v", err)
|
|
}
|
|
|
|
start := time.Now()
|
|
err := sink.PublishUnified(context.Background(), env)
|
|
elapsed := time.Since(start)
|
|
if !errors.Is(err, ErrAsyncSinkEnqueueTimeout) {
|
|
t.Fatalf("second PublishUnified() error = %v, want ErrAsyncSinkEnqueueTimeout", err)
|
|
}
|
|
if elapsed > 100*time.Millisecond {
|
|
t.Fatalf("derived enqueue timeout took %s, want quick failure", elapsed)
|
|
}
|
|
delegate.release()
|
|
}
|
|
|
|
func TestPartitionedAsyncSinkCloseUnblocksBlockedDerivedEnqueue(t *testing.T) {
|
|
delegate := newBlockingDerivedSink()
|
|
sink := NewPartitionedAsyncSink(delegate, PartitionedAsyncConfig{
|
|
RawQueueSize: 1,
|
|
DerivedQueueSize: 1,
|
|
RawWorkers: 1,
|
|
DerivedWorkers: 1,
|
|
EnqueueTimeout: -1,
|
|
OperationTimeout: time.Second,
|
|
})
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808, Phone: "13307795425"}
|
|
if err := sink.PublishFields(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishFields() error = %v", err)
|
|
}
|
|
select {
|
|
case <-delegate.fieldsStarted:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("delegate fields publish was not started")
|
|
}
|
|
if err := sink.PublishUnified(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishUnified() error = %v", err)
|
|
}
|
|
|
|
publishErr := make(chan error, 1)
|
|
go func() {
|
|
publishErr <- sink.PublishUnified(context.Background(), env)
|
|
}()
|
|
closeErr := make(chan error, 1)
|
|
go func() {
|
|
closeErr <- sink.Close()
|
|
}()
|
|
|
|
select {
|
|
case err := <-publishErr:
|
|
if !errors.Is(err, ErrAsyncSinkClosed) {
|
|
t.Fatalf("blocked PublishUnified() error = %v, want ErrAsyncSinkClosed", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("blocked PublishUnified() was not released by Close")
|
|
}
|
|
delegate.release()
|
|
select {
|
|
case err := <-closeErr:
|
|
if err != nil {
|
|
t.Fatalf("Close() error = %v", err)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("Close() did not finish after delegate release")
|
|
}
|
|
}
|
|
|
|
type blockingDerivedSink struct {
|
|
fieldsStarted chan struct{}
|
|
releaseFields chan struct{}
|
|
}
|
|
|
|
func newBlockingDerivedSink() *blockingDerivedSink {
|
|
return &blockingDerivedSink{
|
|
fieldsStarted: make(chan struct{}),
|
|
releaseFields: make(chan struct{}),
|
|
}
|
|
}
|
|
|
|
func (s *blockingDerivedSink) PublishRaw(context.Context, envelope.FrameEnvelope) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *blockingDerivedSink) PublishUnified(context.Context, envelope.FrameEnvelope) error {
|
|
return nil
|
|
}
|
|
|
|
func (s *blockingDerivedSink) PublishFields(context.Context, envelope.FrameEnvelope) error {
|
|
s.signalFieldsStarted()
|
|
<-s.releaseFields
|
|
return nil
|
|
}
|
|
|
|
func (s *blockingDerivedSink) Close() error {
|
|
s.release()
|
|
return nil
|
|
}
|
|
|
|
func (s *blockingDerivedSink) signalFieldsStarted() {
|
|
select {
|
|
case <-s.fieldsStarted:
|
|
default:
|
|
close(s.fieldsStarted)
|
|
}
|
|
}
|
|
|
|
func (s *blockingDerivedSink) release() {
|
|
select {
|
|
case <-s.releaseFields:
|
|
default:
|
|
close(s.releaseFields)
|
|
}
|
|
}
|