mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-11 09:00:45 +02:00
A format adapter reduces one container format to three things the core understands: extent sizes, an alignment quantum, and an opaque payload. Capabilities beyond identity (Indexer, SidecarIndexer, Viewer) are discovered by type assertion. The layout persists in one compact extended attribute keyed by extent sizes rather than chunk ids, so it survives chunk manifest folding, and the Cutter turns it into upload chunk boundaries clamped by maxMB and the align quantum. The formattest kit holds every adapter to no-panic parsing of truncated input.
162 lines
5.0 KiB
Go
162 lines
5.0 KiB
Go
package format
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestLayoutEncodeDecodeRoundTrip(t *testing.T) {
|
|
layout := &Layout{
|
|
Format: "hls-ts",
|
|
ExtentSizes: []int64{188 * 3, 188 * 2, 188 * 7},
|
|
Align: 188,
|
|
Payload: []byte{1, 2, 3},
|
|
}
|
|
encoded, err := layout.Encode()
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
decoded, err := DecodeLayout(encoded)
|
|
if err != nil {
|
|
t.Fatalf("DecodeLayout() error = %v", err)
|
|
}
|
|
if decoded.Format != layout.Format || decoded.Align != layout.Align {
|
|
t.Fatalf("decoded = %+v, want %+v", decoded, layout)
|
|
}
|
|
if len(decoded.ExtentSizes) != len(layout.ExtentSizes) {
|
|
t.Fatalf("extent count = %d, want %d", len(decoded.ExtentSizes), len(layout.ExtentSizes))
|
|
}
|
|
for i := range layout.ExtentSizes {
|
|
if decoded.ExtentSizes[i] != layout.ExtentSizes[i] {
|
|
t.Fatalf("extent %d = %d, want %d", i, decoded.ExtentSizes[i], layout.ExtentSizes[i])
|
|
}
|
|
}
|
|
if string(decoded.Payload) != string(layout.Payload) {
|
|
t.Fatalf("payload = %v, want %v", decoded.Payload, layout.Payload)
|
|
}
|
|
}
|
|
|
|
func TestDecodeLayoutRejectsCorruptInput(t *testing.T) {
|
|
layout := &Layout{Format: "parquet", ExtentSizes: []int64{10, 20}, Align: 1}
|
|
encoded, err := layout.Encode()
|
|
if err != nil {
|
|
t.Fatalf("Encode() error = %v", err)
|
|
}
|
|
for cut := 0; cut < len(encoded); cut++ {
|
|
if _, err := DecodeLayout(encoded[:cut]); err == nil {
|
|
t.Fatalf("DecodeLayout() accepted truncation at %d", cut)
|
|
}
|
|
}
|
|
if _, err := DecodeLayout(append(append([]byte{}, encoded...), 0)); err == nil {
|
|
t.Fatalf("DecodeLayout() accepted trailing bytes")
|
|
}
|
|
}
|
|
|
|
func TestLayoutValidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
layout Layout
|
|
fileSize int64
|
|
wantErr string
|
|
}{
|
|
{"valid", Layout{Format: "x", ExtentSizes: []int64{5, 5}, Align: 1}, 10, ""},
|
|
{"skip size check", Layout{Format: "x", ExtentSizes: []int64{5}, Align: 1}, -1, ""},
|
|
{"wrong total", Layout{Format: "x", ExtentSizes: []int64{5, 5}, Align: 1}, 11, "but the file has"},
|
|
{"zero extent", Layout{Format: "x", ExtentSizes: []int64{5, 0}, Align: 1}, -1, "invalid size"},
|
|
{"no extents", Layout{Format: "x", Align: 1}, -1, "no extents"},
|
|
{"bad align", Layout{Format: "x", ExtentSizes: []int64{5}, Align: 0}, -1, "align"},
|
|
{"no name", Layout{ExtentSizes: []int64{5}, Align: 1}, -1, "format name"},
|
|
}
|
|
for _, test := range tests {
|
|
err := test.layout.Validate(test.fileSize)
|
|
if test.wantErr == "" {
|
|
if err != nil {
|
|
t.Fatalf("%s: Validate() error = %v", test.name, err)
|
|
}
|
|
continue
|
|
}
|
|
if err == nil || !strings.Contains(err.Error(), test.wantErr) {
|
|
t.Fatalf("%s: Validate() error = %v, want %q", test.name, err, test.wantErr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExtentRange(t *testing.T) {
|
|
layout := &Layout{Format: "x", ExtentSizes: []int64{10, 20, 30}, Align: 1}
|
|
offset, size, ok := layout.ExtentRange(1)
|
|
if !ok || offset != 10 || size != 20 {
|
|
t.Fatalf("ExtentRange(1) = (%d, %d, %v), want (10, 20, true)", offset, size, ok)
|
|
}
|
|
if _, _, ok := layout.ExtentRange(3); ok {
|
|
t.Fatalf("ExtentRange(3) accepted out-of-range index")
|
|
}
|
|
if _, _, ok := layout.ExtentRange(-1); ok {
|
|
t.Fatalf("ExtentRange(-1) accepted negative index")
|
|
}
|
|
}
|
|
|
|
// collectChunks walks the cutter the way the upload loop does.
|
|
func collectChunks(t *testing.T, cutter *Cutter) [][2]int64 {
|
|
t.Helper()
|
|
var chunks [][2]int64
|
|
var offset int64
|
|
for {
|
|
size := cutter.NextChunkSize(offset)
|
|
if size <= 0 {
|
|
return chunks
|
|
}
|
|
chunks = append(chunks, [2]int64{offset, size})
|
|
offset += size
|
|
}
|
|
}
|
|
|
|
func TestCutterKeepsExtentBoundaries(t *testing.T) {
|
|
layout := &Layout{Format: "x", ExtentSizes: []int64{5, 4}, Align: 1}
|
|
chunks := collectChunks(t, layout.Cutter(16))
|
|
want := [][2]int64{{0, 5}, {5, 4}}
|
|
if len(chunks) != len(want) {
|
|
t.Fatalf("chunks = %v, want %v", chunks, want)
|
|
}
|
|
for i := range want {
|
|
if chunks[i] != want[i] {
|
|
t.Fatalf("chunk %d = %v, want %v", i, chunks[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCutterSplitsOversizedExtentsOnAlign(t *testing.T) {
|
|
// maxChunkSize 5 with align 2 quantizes down to 4-byte interior cuts.
|
|
layout := &Layout{Format: "x", ExtentSizes: []int64{10, 3}, Align: 2}
|
|
chunks := collectChunks(t, layout.Cutter(5))
|
|
want := [][2]int64{{0, 4}, {4, 4}, {8, 2}, {10, 3}}
|
|
if len(chunks) != len(want) {
|
|
t.Fatalf("chunks = %v, want %v", chunks, want)
|
|
}
|
|
for i := range want {
|
|
if chunks[i] != want[i] {
|
|
t.Fatalf("chunk %d = %v, want %v", i, chunks[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCutterAlignLargerThanChunkLimit(t *testing.T) {
|
|
// Align above maxChunkSize still cuts on whole atoms.
|
|
layout := &Layout{Format: "x", ExtentSizes: []int64{20}, Align: 8}
|
|
chunks := collectChunks(t, layout.Cutter(5))
|
|
want := [][2]int64{{0, 8}, {8, 8}, {16, 4}}
|
|
for i := range want {
|
|
if chunks[i] != want[i] {
|
|
t.Fatalf("chunk %d = %v, want %v", i, chunks[i], want[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCutterUnlimitedKeepsOneChunkPerExtent(t *testing.T) {
|
|
layout := &Layout{Format: "x", ExtentSizes: []int64{10, 3}, Align: 188}
|
|
chunks := collectChunks(t, layout.Cutter(0))
|
|
want := [][2]int64{{0, 10}, {10, 3}}
|
|
if len(chunks) != len(want) {
|
|
t.Fatalf("chunks = %v, want %v", chunks, want)
|
|
}
|
|
}
|