Some checks failed
go: build dev binaries / cleanup (push) Successful in 4s
Codespell / Check for spelling errors (push) Failing after 3s
docker: build dev containers / build-rust-binaries (amd64, x86_64-unknown-linux-musl) (push) Failing after 3s
docker: build dev containers / build-dev-containers (push) Has been skipped
docker: build dev containers / build-rust-binaries (arm64, true, aarch64-unknown-linux-musl) (push) Failing after 3s
End to End / FUSE Mount (push) Failing after 2s
EC Integration Tests / EC Integration Tests (push) Failing after 30s
FUSE DLM Integration Tests / FUSE DLM Integration Tests (push) Failing after 3s
FUSE Volume Server Failover Tests / FUSE Volume Server Failover (push) Failing after 3s
FUSE Integration Tests / FUSE Integration Testing (push) Failing after 2s
FUSE P2P Peer Chunk Sharing Integration Tests / FUSE P2P Peer Chunk Sharing (push) Failing after 3s
go: build binary / Go Vet (push) Failing after 2s
go: build binary / Go Vet 32-bit (push) Failing after 3s
go: build binary / Build (push) Failing after 4s
go: build binary / Build cross-platform (freebsd/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/amd64) (push) Failing after 3s
go: build binary / Build cross-platform (darwin/arm64) (push) Failing after 3s
go: build binary / Build cross-platform (windows/arm64) (push) Failing after 3s
go: build binary / Test (push) Failing after 2s
go: build binary / Test 32-bit (push) Failing after 3s
helm: lint and test charts / lint-test (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (11) (push) Failing after 3s
Java Client Integration Tests / Java Integration Tests (17) (push) Failing after 3s
go: build dev binaries / build_dev_linux_windows (amd64, linux) (push) Failing after 2s
go: build dev binaries / build_dev_linux_windows (amd64, windows) (push) Failing after 3s
go: build dev binaries / build_dev_darwin (amd64, darwin) (push) Has been skipped
go: build dev binaries / build_dev_darwin (arm64, darwin) (push) Has been skipped
Java Client Unit Tests / Java Unit Tests (11) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (17) (push) Failing after 2s
Java Client Unit Tests / Java Unit Tests (21) (push) Failing after 3s
Java Client Unit Tests / Java Unit Tests (8) (push) Failing after 3s
Kafka Quick Test (Load Test with Schema Registry) / Kafka Client Load Test (Quick) (push) Failing after 3s
Kafka Gateway Tests / Kafka Unit Tests (unit-tests-1) (push) Failing after 11s
Kafka Gateway Tests / Kafka Integration Tests (Critical) (integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka End-to-End Tests (with SMQ) (e2e-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Consumer Group Tests (Highly Isolated) (consumer-group-1) (push) Failing after 2s
Kafka Gateway Tests / Kafka Client Compatibility (with SMQ) (client-compat-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka SMQ Integration Tests (Full Stack) (smq-integration-1) (push) Failing after 3s
Kafka Gateway Tests / Kafka Protocol Tests (Isolated) (protocol-1) (push) Failing after 3s
KMS Tests / KMS Provider Integration Tests (push) Failing after 2s
KMS Tests / S3 KMS End-to-End Tests (push) Failing after 3s
Master Cold Start Tests / Master Cold Start Tests (push) Failing after 4s
Metadata Subscribe Integration Tests / Metadata Subscribe Integration Tests (push) Failing after 3s
Multi-Master Tests / Multi-Master Failover Tests (push) Failing after 3s
Performance / CPU and Heap Profile (push) Failing after 2s
Performance / S3 Read/Write Benchmark (rust volume) (push) Failing after 3s
pjdfstest POSIX Compliance / pjdfstest (push) Failing after 2s
Plugin Worker Integration Tests / Plugin Worker: erasure_coding (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: vacuum (push) Failing after 3s
Rust Volume Server Tests / Rust Integration Tests (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 1) (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 1) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 2) (push) Failing after 3s
Rust Volume Server Tests / Go Tests with Rust Volume (grpc - Shard 3) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Distributed Tests (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Performance Tests (push) Failing after 3s
Performance / Throughput Benchmark (go volume) (push) Failing after 3s
Performance / Throughput Benchmark (rust volume) (push) Failing after 2s
Performance / S3 Read/Write Benchmark (go volume) (push) Failing after 3s
Plugin Worker Integration Tests / Plugin Worker: volume_balance (push) Failing after 3s
PostgreSQL Gateway Tests / PostgreSQL Basic Tests (push) Failing after 3s
Rust Volume Server Tests / Rust Unit Tests (push) Failing after 2s
Rust Volume Server Tests / Go Tests with Rust Volume (http - Shard 3) (push) Failing after 3s
Rust Plugin Worker Tests / Rust Plugin Worker Build and Unit Tests (push) Failing after 3s
rust: build dev volume server binaries / cleanup (push) Successful in 3s
S3 ETag and ACL Tests / S3 ETag + ACL Integration Tests (push) Failing after 3s
S3 IAM Integration Tests / IAM Unit Tests (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (advanced) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (basic) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (group) (push) Failing after 3s
S3 IAM Integration Tests / S3 IAM Integration Tests (policy-enforcement) (push) Failing after 2s
S3 IAM Integration Tests / S3 IAM Integration Tests (sts) (push) Failing after 4s
S3 Keycloak Integration Tests / S3 Keycloak Integration Tests (push) Failing after 3s
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.11) (push) Failing after 3s
S3 PyArrow Parquet Tests / Go Unit Tests (Implicit Directory) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.12) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-amd64, x86_64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-darwin (darwin-arm64, aarch64-apple-darwin) (push) Has been cancelled
rust: build dev volume server binaries / build-rust-volume-dev-linux (linux-amd64, x86_64-unknown-linux-gnu) (push) Has been cancelled
S3 PyArrow Parquet Tests / PyArrow Parquet Tests (Python 3.9) (push) Has been cancelled
S3 Proxy Signature Tests / S3 Proxy Signature Verification Tests (push) Has been cancelled
S3 SDK V2 Route Disambiguation Tests / S3 SDK V2 Routing Tests (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (quick) (push) Has been cancelled
S3 SSE Tests / S3 SSE Compatibility Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Metadata Persistence Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Copy Operations Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Multipart Upload Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Performance Test (push) Has been cancelled
S3 SSE Tests / S3 Volume Encryption Test (push) Has been cancelled
S3 SSE Tests / S3 SSE Integration Tests (comprehensive) (push) Has been cancelled
Spark Integration Tests / Spark Integration Tests (push) Failing after 2s
Telemetry Integration Tests / telemetry-integration-test (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 1) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 1) (push) Failing after 3s
Ceph S3 tests / Basic S3 tests (KV store) (push) Has been cancelled
Ceph S3 tests / S3 Versioning & Object Lock tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Unit Tests (push) Failing after 3s
Ceph S3 tests / S3 CORS tests (push) Has been cancelled
Ceph S3 tests / SeaweedFS Custom S3 Copy tests (push) Has been cancelled
Ceph S3 tests / Basic S3 tests (SQL store) (push) Has been cancelled
test s3 over https using aws-cli / awscli-tests (push) Has been cancelled
TLS Rotation Integration Tests / TLS Rotation Integration Tests (push) Has been cancelled
Vacuum Integration Tests / Vacuum Integration Tests (push) Has been cancelled
S3 Policy Integration Tests / S3 Policy Variables Integration Tests (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (advanced-policy) (push) Failing after 3s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (basic-policy) (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Enforcement Integration Tests (contextual-policy) (push) Failing after 4s
S3 Policy Integration Tests / Trusted Proxy Detection Tests (push) Failing after 2s
S3 Policy Integration Tests / S3 Policy Shell Integration Tests (push) Failing after 3s
Samba on FUSE Integration / samba-integration (push) Failing after 2s
SFTP Integration Tests / SFTP Integration Testing (push) Failing after 3s
terraform: validate and test modules / fmt, validate, plan-level tests (push) Failing after 3s
terraform: validate and test modules / local cluster smoke test (real weed) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 3) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (grpc - Shard 2) (push) Failing after 3s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 2) (push) Failing after 2s
Volume Server Integration Tests / Volume Server Integration Tests (http - Shard 3) (push) Failing after 5s
mount: windows conformance / WinFsp conformance (push) Has been cancelled
mount: windows / Mount on Windows (push) Has been cancelled
271 lines
12 KiB
Go
271 lines
12 KiB
Go
package filer
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/util"
|
|
"github.com/seaweedfs/seaweedfs/weed/wdclient"
|
|
)
|
|
|
|
// TestPersistedLogScanStartCoversSpanningWindow pins the file-selection window.
|
|
// A log file is named for the start of the window it holds, and a window spans
|
|
// up to LogFlushInterval, so a cursor inside the window's later minutes must
|
|
// still open the file named for its earlier one -- otherwise the read reports
|
|
// "nothing on disk" for entries that are sitting in it, and the gap resolvers
|
|
// take that miss as proof the range is empty.
|
|
func TestPersistedLogScanStartCoversSpanningWindow(t *testing.T) {
|
|
// A window sealed at 12:30:59 ending 12:31:58 is written to "12-30".
|
|
sealed := time.Date(2026, 6, 29, 12, 30, 59, 0, time.UTC)
|
|
fileMinute := sealed.Format("15-04")
|
|
|
|
// A subscriber resuming mid-window must not sort past that file.
|
|
for _, cursor := range []time.Time{
|
|
sealed.Add(11 * time.Second), // 12:31:10, the reported case
|
|
sealed.Add(59 * time.Second), // 12:31:58, the window's last entry
|
|
sealed, // exactly the window start
|
|
} {
|
|
scanMinute := persistedLogScanStart(cursor).Format("15-04")
|
|
if scanMinute > fileMinute {
|
|
t.Fatalf("cursor %v scans from %q, which sorts past the file %q holding it",
|
|
cursor.Format("15:04:05"), scanMinute, fileMinute)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A cursor just after midnight has to reach back into the previous day.
|
|
func TestPersistedLogScanStartCrossesMidnight(t *testing.T) {
|
|
cursor := time.Date(2026, 6, 29, 0, 0, 30, 0, time.UTC)
|
|
scanFrom := persistedLogScanStart(cursor)
|
|
if got, want := scanFrom.Format("2006-01-02"), "2026-06-28"; got != want {
|
|
t.Fatalf("scan date = %q, want %q so the previous day's last file is listed", got, want)
|
|
}
|
|
}
|
|
|
|
// TestSpanningLogFileIsNotSkipped pins the other half of the minute-boundary
|
|
// fix, against the predicate the iterator actually calls. Widening which files
|
|
// get listed is useless if the iterator then drops the spanning file, which it
|
|
// did by treating the following file's name as an upper bound on this file's
|
|
// contents -- it is not.
|
|
func TestSpanningLogFileIsNotSkipped(t *testing.T) {
|
|
// Window sealed at 12:30:59, ending 12:31:58, written to "12-30".
|
|
spanning := time.Date(2026, 6, 29, 12, 30, 0, 0, time.UTC).UnixNano()
|
|
// The next window starts at 12:31:59 and is written to "12-31".
|
|
following := time.Date(2026, 6, 29, 12, 31, 0, 0, time.UTC).UnixNano()
|
|
|
|
cursor := time.Date(2026, 6, 29, 12, 31, 10, 0, time.UTC).UnixNano()
|
|
if following > cursor {
|
|
t.Fatal("precondition: the following file's name sorts at or before the cursor")
|
|
}
|
|
if !logFileMayContainAfter(spanning, cursor) {
|
|
t.Fatal("the spanning file holds entries past the cursor and must be read")
|
|
}
|
|
|
|
// A file that genuinely cannot reach the cursor is still skipped, so the
|
|
// widening does not turn into reading the whole day.
|
|
old := time.Date(2026, 6, 29, 12, 0, 0, 0, time.UTC).UnixNano()
|
|
if logFileMayContainAfter(old, cursor) {
|
|
t.Fatal("a file a full interval behind the cursor should still be skipped")
|
|
}
|
|
}
|
|
|
|
// TestClampLogRefsCursorNeverRewinds pins that a chunk-ref read cannot move the
|
|
// subscriber backwards. The refs carry minute-level names, so the last one
|
|
// normally sorts before the requested position, and the caller assigns that
|
|
// value straight to the read cursor.
|
|
func TestClampLogRefsCursorNeverRewinds(t *testing.T) {
|
|
cursor := time.Date(2026, 6, 29, 12, 31, 10, 0, time.UTC).UnixNano()
|
|
spanningFile := time.Date(2026, 6, 29, 12, 30, 0, 0, time.UTC).UnixNano()
|
|
|
|
if got := clampLogRefsCursor(spanningFile, cursor); got != cursor {
|
|
t.Fatalf("cursor moved to %v, want it held at %v", time.Unix(0, got), time.Unix(0, cursor))
|
|
}
|
|
// A ref genuinely ahead of the cursor still advances it.
|
|
ahead := time.Date(2026, 6, 29, 12, 32, 0, 0, time.UTC).UnixNano()
|
|
if got := clampLogRefsCursor(ahead, cursor); got != ahead {
|
|
t.Fatalf("cursor = %v, want it to advance to %v", time.Unix(0, got), time.Unix(0, ahead))
|
|
}
|
|
}
|
|
|
|
// TestPersistedLogScanStartTsNsMinuteAligned pins the prune bound against the
|
|
// collector's minute-granular file comparison: a cursor at 12:31:20 still
|
|
// collects the 12-30 file, so the bound must not sit past 12:30:00 - an
|
|
// exact-ns bound disowns the file's sent state and the next pass reships it
|
|
// whole, re-creating the duplicate-refs class the state exists to prevent.
|
|
func TestPersistedLogScanStartTsNsMinuteAligned(t *testing.T) {
|
|
cursor := time.Date(2026, 6, 29, 12, 31, 20, 0, time.UTC)
|
|
fileTsNs := time.Date(2026, 6, 29, 12, 30, 0, 0, time.UTC).UnixNano()
|
|
|
|
bound := PersistedLogScanStartTsNs(cursor)
|
|
if fileTsNs < bound {
|
|
t.Fatalf("bound %v disowns the 12-30 file the collector still lists", time.Unix(0, bound).UTC())
|
|
}
|
|
// A file a full interval plus a minute behind is genuinely out of scan range.
|
|
old := time.Date(2026, 6, 29, 12, 28, 0, 0, time.UTC).UnixNano()
|
|
if old >= bound {
|
|
t.Fatalf("bound %v keeps state for files the scan can no longer list", time.Unix(0, bound).UTC())
|
|
}
|
|
}
|
|
|
|
// TestLastShippedLogEntryTsNs pins the chunk-mode cursor source against the
|
|
// client's reader, which it must mirror: per file the readable prefix - the
|
|
// client stops at the first unreadable chunk and never resumes within the file
|
|
// - and per filer the newest file with content, because the client keeps
|
|
// reading later files after skipping a bad one. A marker past what the client
|
|
// applies loses events; one short only re-ships.
|
|
func TestLastShippedLogEntryTsNs(t *testing.T) {
|
|
f := &Filer{persistedLogCache: newPersistedLogCache(1 << 20)}
|
|
|
|
entry := func(ts int64) *filer_pb.LogEntry { return &filer_pb.LogEntry{TsNs: ts} }
|
|
chunk := func(id string) *filer_pb.FileChunk { return &filer_pb.FileChunk{FileId: id, Size: 10} }
|
|
ref := func(fileTsNs int64, chunks ...*filer_pb.FileChunk) *filer_pb.LogFileChunkRef {
|
|
return &filer_pb.LogFileChunkRef{FilerId: "a", FileTsNs: fileTsNs, Chunks: chunks}
|
|
}
|
|
|
|
byId := map[string]struct {
|
|
entries []*filer_pb.LogEntry
|
|
err error
|
|
}{
|
|
"good-early": {entries: []*filer_pb.LogEntry{entry(100), entry(200)}},
|
|
"good-late": {entries: []*filer_pb.LogEntry{entry(300), entry(400)}},
|
|
"missing": {err: fmt.Errorf("volume 7 not found")},
|
|
"missing2": {err: fmt.Errorf("volume 8 not found")},
|
|
"empty": {},
|
|
}
|
|
origLoad := loadLogFileEntriesFn
|
|
loadLogFileEntriesFn = func(masterClient *wdclient.MasterClient, c *filer_pb.FileChunk) ([]*filer_pb.LogEntry, bool, error) {
|
|
r := byId[c.FileId]
|
|
return r.entries, true, r.err
|
|
}
|
|
origLookup := lookupLogChunkFn
|
|
deadVolumes := map[string]bool{}
|
|
lookupLogChunkFn = func(f *Filer, fileId string) error {
|
|
if deadVolumes[fileId] {
|
|
return fmt.Errorf("volume 9 not found")
|
|
}
|
|
return nil
|
|
}
|
|
defer func() { loadLogFileEntriesFn = origLoad; lookupLogChunkFn = origLookup }()
|
|
|
|
t.Run("a fully readable file answers with its tail, complete", func(t *testing.T) {
|
|
ts, ok, complete := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("good-late")})
|
|
if !ok || ts != 400 || !complete {
|
|
t.Fatalf("ts=%d ok=%v complete=%v, want 400 and complete", ts, ok, complete)
|
|
}
|
|
})
|
|
|
|
t.Run("the prefix ends at the first missing chunk", func(t *testing.T) {
|
|
// The client's reader stops there and never resumes within the file:
|
|
// answering from the readable suffix would put the marker past entries
|
|
// the client never applied, losing them permanently.
|
|
ts, ok, complete := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("missing"), chunk("good-late")})
|
|
if !ok || ts != 200 {
|
|
t.Fatalf("ts=%d ok=%v, want the prefix end 200, never the suffix 400", ts, ok)
|
|
}
|
|
if complete {
|
|
t.Fatal("a prefix-limited read must report incomplete, or the unread suffix is never re-shipped")
|
|
}
|
|
})
|
|
|
|
t.Run("nothing readable is a clean no-answer", func(t *testing.T) {
|
|
if _, ok, _ := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("missing"), chunk("empty")}); ok {
|
|
t.Fatal("want no answer")
|
|
}
|
|
})
|
|
|
|
t.Run("an entirely missing last file falls back to earlier files", func(t *testing.T) {
|
|
// The client skips the bad file but has applied the earlier ones;
|
|
// discarding their progress would rewind the marker to the start
|
|
// cursor and stall or replay.
|
|
ts, answeredFileTsNs, ok, _ := f.LastShippedLogEntryTsNsForFiler([]*filer_pb.LogFileChunkRef{
|
|
ref(1000, chunk("good-late")),
|
|
ref(2000, chunk("missing"), chunk("missing2")),
|
|
})
|
|
if !ok || ts != 400 {
|
|
t.Fatalf("ts=%d ok=%v, want the previous file's 400", ts, ok)
|
|
}
|
|
if answeredFileTsNs != 1000 {
|
|
t.Fatalf("answered file %d, want 1000: the caller rolls back sent state above it", answeredFileTsNs)
|
|
}
|
|
})
|
|
|
|
t.Run("a cached chunk on a dead volume ends the prefix", func(t *testing.T) {
|
|
// Warm the cache, then kill the middle chunk's volume. The client
|
|
// reads directly and stops there; a probe trusting its own decoded
|
|
// cache would sail past to the tail and put the marker beyond entries
|
|
// the client never applied.
|
|
if ts, ok, _ := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("good-late")}); !ok || ts != 400 {
|
|
t.Fatalf("warmup: ts=%d ok=%v, want 400", ts, ok)
|
|
}
|
|
deadVolumes["good-early"] = true
|
|
defer delete(deadVolumes, "good-early")
|
|
ts, ok, complete := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("good-late")})
|
|
if ok || ts != 0 {
|
|
t.Fatalf("ts=%d ok=%v, want no answer: the first chunk's volume is gone however warm the cache", ts, ok)
|
|
}
|
|
if complete {
|
|
t.Fatal("a lookup-limited read must report incomplete so the ref re-ships")
|
|
}
|
|
})
|
|
|
|
t.Run("a dead needle behind a warm cache is the accepted residual", func(t *testing.T) {
|
|
// Production liveness is a volume lookup: it cannot see a needle 404
|
|
// or a stale location inside a resolvable volume, so a warm cache can
|
|
// answer past a chunk the client fails to read. Accepted because
|
|
// metadata log chunks die volume-at-a-time (TTL'd log volumes) and the
|
|
// alternative is a real read per probe, which is what the probe exists
|
|
// to avoid; the client logs the skip at V(0). This test pins the
|
|
// boundary so a change to it is a decision, not an accident.
|
|
if ts, ok, _ := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("good-late")}); !ok || ts != 400 {
|
|
t.Fatalf("warmup: ts=%d ok=%v, want 400", ts, ok)
|
|
}
|
|
// The needle dies: loads fail, but the volume still resolves.
|
|
prev := byId["good-early"]
|
|
byId["good-early"] = struct {
|
|
entries []*filer_pb.LogEntry
|
|
err error
|
|
}{err: fmt.Errorf("read: 404 Not Found: not found")}
|
|
defer func() { byId["good-early"] = prev }()
|
|
ts, ok, complete := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("good-early"), chunk("good-late")})
|
|
if !ok || ts != 400 || !complete {
|
|
t.Fatalf("ts=%d ok=%v complete=%v: the warm cache masks a dead needle - if this now fails, the residual was closed and this test should assert the new behavior", ts, ok, complete)
|
|
}
|
|
})
|
|
|
|
t.Run("spanning records stream the shipped list", func(t *testing.T) {
|
|
origLoad2 := loadLogFileEntriesFn
|
|
loadLogFileEntriesFn = func(masterClient *wdclient.MasterClient, c *filer_pb.FileChunk) ([]*filer_pb.LogEntry, bool, error) {
|
|
return nil, false, errLogChunkIncomplete
|
|
}
|
|
origStream := newLogFileStreamReader
|
|
newLogFileStreamReader = func(masterClient *wdclient.MasterClient, chunks []*filer_pb.FileChunk) io.Reader {
|
|
var buf bytes.Buffer
|
|
for _, ts := range []int64{500, 600} {
|
|
data, _ := (&filer_pb.LogEntry{TsNs: ts}).MarshalVT()
|
|
var sizeBuf [4]byte
|
|
util.Uint32toBytes(sizeBuf[:], uint32(len(data)))
|
|
buf.Write(sizeBuf[:])
|
|
buf.Write(data)
|
|
}
|
|
// A torn trailing size prefix, as a crashed writer leaves it: the
|
|
// client reads it as a clean end, so the probe must too - failing
|
|
// here would block the marker forever on data the client accepts.
|
|
buf.Write([]byte{0x01, 0x02})
|
|
return &buf
|
|
}
|
|
defer func() { loadLogFileEntriesFn = origLoad2; newLogFileStreamReader = origStream }()
|
|
|
|
ts, ok, complete := f.lastShippedLogEntryTsNs([]*filer_pb.FileChunk{chunk("incomplete")})
|
|
if !ok || ts != 600 {
|
|
t.Fatalf("ts=%d ok=%v, want the streamed tail 600 past the torn prefix", ts, ok)
|
|
}
|
|
if !complete {
|
|
t.Fatal("a torn tail is where the client's read ends too: complete, nothing to re-ship")
|
|
}
|
|
})
|
|
}
|