bf9395e022
CI / license-header (push) Has been skipped
CI / e2e-dry-run (push) Has been skipped
CI / fast-gate (push) Failing after 0s
Test PR Label Logic / test-pr-labels (push) Failing after 1s
Skill Format Check / check-format (push) Failing after 2s
CI / security (push) Failing after 5s
CI / unit-test (push) Has been skipped
CI / lint (push) Has been skipped
CI / script-test (push) Has been skipped
CI / deterministic-gate (push) Has been skipped
CI / coverage (push) Has been skipped
CI / results (push) Has been cancelled
CI / deadcode (push) Has been cancelled
CI / e2e-live (push) Has been cancelled
105 lines
2.8 KiB
Go
105 lines
2.8 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package consume
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/larksuite/cli/internal/event/protocol"
|
|
)
|
|
|
|
// Mirrors the inline gap-detection logic from consumeLoop's reader; keep in sync with loop.go.
|
|
type seqGapDetector struct {
|
|
lastSeq uint64
|
|
errOut io.Writer
|
|
quiet bool
|
|
}
|
|
|
|
func (d *seqGapDetector) observe(m *protocol.Event) {
|
|
if d.lastSeq > 0 && m.Seq > 0 && m.Seq > d.lastSeq+1 {
|
|
gap := m.Seq - d.lastSeq - 1
|
|
if !d.quiet {
|
|
fmt.Fprintf(d.errOut, "WARN: event seq gap %d->%d, missed %d events (dropped by bus backpressure)\n",
|
|
d.lastSeq, m.Seq, gap)
|
|
}
|
|
}
|
|
// CRITICAL: only advance forward — concurrent Publishers may deliver Seq out-of-order.
|
|
if m.Seq > d.lastSeq {
|
|
d.lastSeq = m.Seq
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorNoWarningOnFirstEvent(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf}
|
|
d.observe(&protocol.Event{Seq: 5})
|
|
if strings.Contains(buf.String(), "gap") {
|
|
t.Errorf("unexpected gap warning on first event: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorNoWarningOnContiguous(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf}
|
|
for i := uint64(1); i <= 10; i++ {
|
|
d.observe(&protocol.Event{Seq: i})
|
|
}
|
|
if buf.Len() > 0 {
|
|
t.Errorf("unexpected output on contiguous seqs: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorWarnsOnActualGap(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf}
|
|
d.observe(&protocol.Event{Seq: 1})
|
|
d.observe(&protocol.Event{Seq: 5})
|
|
out := buf.String()
|
|
if !strings.Contains(out, "gap 1->5") {
|
|
t.Errorf("expected 'gap 1->5' in output, got: %s", out)
|
|
}
|
|
if !strings.Contains(out, "missed 3 events") {
|
|
t.Errorf("expected 'missed 3 events' in output, got: %s", out)
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorHandlesOutOfOrderWithoutFalsePositive(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf}
|
|
d.observe(&protocol.Event{Seq: 6})
|
|
d.observe(&protocol.Event{Seq: 5})
|
|
d.observe(&protocol.Event{Seq: 7})
|
|
if buf.Len() > 0 {
|
|
t.Errorf("unexpected warning for out-of-order (no actual gap): %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorQuietMode(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf, quiet: true}
|
|
d.observe(&protocol.Event{Seq: 1})
|
|
d.observe(&protocol.Event{Seq: 10})
|
|
if buf.Len() > 0 {
|
|
t.Errorf("quiet mode should suppress warnings, got: %s", buf.String())
|
|
}
|
|
}
|
|
|
|
func TestSeqGapDetectorZeroSeqIgnored(t *testing.T) {
|
|
var buf bytes.Buffer
|
|
d := &seqGapDetector{errOut: &buf}
|
|
d.observe(&protocol.Event{Seq: 5})
|
|
d.observe(&protocol.Event{Seq: 0})
|
|
d.observe(&protocol.Event{Seq: 6})
|
|
if buf.Len() > 0 {
|
|
t.Errorf("unexpected warning across legacy zero-seq event: %s", buf.String())
|
|
}
|
|
if d.lastSeq != 6 {
|
|
t.Errorf("expected lastSeq=6 after legacy skip, got %d", d.lastSeq)
|
|
}
|
|
}
|