Files
upstream-seaweedfs/weed/pb/filer_pb/filer_vtproto_test.go
upstream-mirror 89f5be005d
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
snapshot of https://github.com/seaweedfs/seaweedfs.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 03:23:42 +05:00

307 lines
9.8 KiB
Go

package filer_pb
import (
"fmt"
"testing"
"google.golang.org/protobuf/proto"
)
// sampleResponse builds a metadata event exercising the fields touched on the
// hot subscribe path: nested entries, a repeated chunk list, a map, and byte
// fields.
func sampleResponse() *SubscribeMetadataResponse {
entry := func(name string) *Entry {
return &Entry{
Name: name,
IsDirectory: false,
Chunks: []*FileChunk{
{
FileId: "3,01637037d6",
Offset: 0,
Size: 1234,
ModifiedTsNs: 111,
ETag: "abc",
Fid: &FileId{VolumeId: 3, FileKey: 0x1637037, Cookie: 0xd6},
CipherKey: []byte{1, 2, 3, 4},
},
},
Attributes: &FuseAttributes{
FileSize: 1234, Mtime: 222, FileMode: 0644, Uid: 1000, Gid: 1000,
GroupName: []string{"staff", "wheel"}, Md5: []byte{9, 8, 7},
},
Extended: map[string][]byte{
"foo": []byte("bar"),
"baz": []byte("qux"),
"k3": []byte("v3"),
},
Content: []byte("hello"),
}
}
return &SubscribeMetadataResponse{
Directory: "/buckets/test/dir",
EventNotification: &EventNotification{
OldEntry: entry("old.txt"),
NewEntry: entry("new.txt"),
DeleteChunks: true,
NewParentPath: "/buckets/test/dir2",
IsFromOtherCluster: true,
Signatures: []int32{101, 202, 303},
},
TsNs: 1234567890,
}
}
// eventWithChunks models a real create/update event: a file split into n chunks,
// each a nested FileChunk+FileId message. n scales the number of nested messages
// the decoder must allocate, which is where vtproto's edge over reflection grows.
func eventWithChunks(n int) *SubscribeMetadataResponse {
chunks := make([]*FileChunk, n)
for i := 0; i < n; i++ {
chunks[i] = &FileChunk{
FileId: fmt.Sprintf("%d,%08x%04x", i%64, i*7919, i%251),
Offset: int64(i) * 8 << 20,
Size: 8 << 20,
ModifiedTsNs: 1700000000000000000 + int64(i),
ETag: fmt.Sprintf("etag-%d", i),
Fid: &FileId{VolumeId: uint32(i%64 + 1), FileKey: uint64(i * 7919), Cookie: uint32(i % 251)},
}
}
return &SubscribeMetadataResponse{
Directory: "/buckets/data/2026/07/15",
EventNotification: &EventNotification{
NewEntry: &Entry{
Name: "object.bin",
Chunks: chunks,
Attributes: &FuseAttributes{FileSize: uint64(n) * 8 << 20, Mtime: 1700000000, FileMode: 0644, Uid: 1000, Gid: 1000},
},
Signatures: []int32{12345},
},
TsNs: 1700000000000000000,
}
}
// TestVTWireCompatibility guards that the vtproto-generated MarshalVT/UnmarshalVT
// stay wire-compatible with the reflection-based proto path in both directions,
// so adopting VT on the metadata hot paths cannot silently corrupt events.
func TestVTWireCompatibility(t *testing.T) {
orig := sampleResponse()
// vt marshal -> proto unmarshal
vtBytes, err := orig.MarshalVT()
if err != nil {
t.Fatalf("MarshalVT: %v", err)
}
var fromVT SubscribeMetadataResponse
if err := proto.Unmarshal(vtBytes, &fromVT); err != nil {
t.Fatalf("proto.Unmarshal(vtBytes): %v", err)
}
if !proto.Equal(orig, &fromVT) {
t.Fatalf("proto.Unmarshal(MarshalVT) diverged from original")
}
// proto marshal -> vt unmarshal
protoBytes, err := proto.Marshal(orig)
if err != nil {
t.Fatalf("proto.Marshal: %v", err)
}
var fromProto SubscribeMetadataResponse
if err := fromProto.UnmarshalVT(protoBytes); err != nil {
t.Fatalf("UnmarshalVT(protoBytes): %v", err)
}
if !proto.Equal(orig, &fromProto) {
t.Fatalf("UnmarshalVT(proto.Marshal) diverged from original")
}
// MarshalToSizedBufferVT into a pre-sized slice matches MarshalVT, the write
// path used by the log buffer.
sized := make([]byte, orig.SizeVT())
if _, err := orig.MarshalToSizedBufferVT(sized); err != nil {
t.Fatalf("MarshalToSizedBufferVT: %v", err)
}
var fromSized SubscribeMetadataResponse
if err := fromSized.UnmarshalVT(sized); err != nil {
t.Fatalf("UnmarshalVT(sized): %v", err)
}
if !proto.Equal(orig, &fromSized) {
t.Fatalf("MarshalToSizedBufferVT diverged from original")
}
// LogEntry carrying a marshaled event, mirroring the log buffer write path.
le := &LogEntry{TsNs: 7, PartitionKeyHash: 9, Data: vtBytes, Key: []byte("k"), Offset: 42}
leBytes, err := le.MarshalVT()
if err != nil {
t.Fatalf("LogEntry.MarshalVT: %v", err)
}
var leDecoded LogEntry
if err := proto.Unmarshal(leBytes, &leDecoded); err != nil {
t.Fatalf("proto.Unmarshal(LogEntry): %v", err)
}
if !proto.Equal(le, &leDecoded) {
t.Fatalf("LogEntry vt/proto roundtrip diverged")
}
}
// TestUTF8Validation documents the deliberate UTF-8 difference between the two
// decoders. UnmarshalVT skips proto3's UTF-8 validation for string fields, so
// the metadata delivery paths keep decoding SubscribeMetadataResponse with
// proto.Unmarshal; only LogEntry (bytes-only) decodes use UnmarshalVT.
func TestUTF8Validation(t *testing.T) {
// SubscribeMetadataResponse{Directory: "\xff"}: field 1 (string), wire type 2,
// length 1, byte 0xff.
malformed := []byte{0x0a, 0x01, 0xff}
var viaProto SubscribeMetadataResponse
if err := proto.Unmarshal(malformed, &viaProto); err == nil {
t.Fatal("proto.Unmarshal accepted invalid UTF-8 in a string field; expected rejection")
}
var viaVT SubscribeMetadataResponse
if err := viaVT.UnmarshalVT(malformed); err != nil {
t.Fatalf("UnmarshalVT rejected invalid UTF-8: %v", err)
}
if viaVT.Directory != "\xff" {
t.Fatalf("UnmarshalVT Directory = %q, want \\xff", viaVT.Directory)
}
// LogEntry carries only bytes fields, so the decoders agree on arbitrary bytes;
// that is why the log entry decode paths can use UnmarshalVT.
// LogEntry{Data: "\xff"}: field 3 (bytes), wire type 2, length 1, byte 0xff.
leBytes := []byte{0x1a, 0x01, 0xff}
var leProto LogEntry
if err := proto.Unmarshal(leBytes, &leProto); err != nil {
t.Fatalf("proto.Unmarshal LogEntry: %v", err)
}
var leVT LogEntry
if err := leVT.UnmarshalVT(leBytes); err != nil {
t.Fatalf("UnmarshalVT LogEntry: %v", err)
}
if !proto.Equal(&leProto, &leVT) {
t.Fatal("LogEntry proto/VT decode of a bytes field diverged")
}
}
var chunkCounts = []int{1, 8, 32, 128}
// BenchmarkUnmarshal is the decode hot path: each subscriber decodes every
// delivered event, so this cost is paid once per (event, subscriber).
func BenchmarkUnmarshal(b *testing.B) {
for _, n := range chunkCounts {
data, _ := proto.Marshal(eventWithChunks(n))
b.Run(fmt.Sprintf("proto/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var m SubscribeMetadataResponse
if err := proto.Unmarshal(data, &m); err != nil {
b.Fatal(err)
}
}
})
b.Run(fmt.Sprintf("vt/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
var m SubscribeMetadataResponse
if err := m.UnmarshalVT(data); err != nil {
b.Fatal(err)
}
}
})
}
}
// BenchmarkMarshal is the encode path: each metadata write marshals one LogEntry
// payload.
func BenchmarkMarshal(b *testing.B) {
for _, n := range chunkCounts {
ev := eventWithChunks(n)
b.Run(fmt.Sprintf("proto/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := proto.Marshal(ev); err != nil {
b.Fatal(err)
}
}
})
b.Run(fmt.Sprintf("vt/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if _, err := ev.MarshalVT(); err != nil {
b.Fatal(err)
}
}
})
}
}
// BenchmarkMarshalIntoBuffer mirrors the log buffer write path: marshal into a
// reused destination buffer. proto.Marshal must allocate a fresh slice and copy;
// SizeVT + MarshalToSizedBufferVT writes straight into the destination.
func BenchmarkMarshalIntoBuffer(b *testing.B) {
for _, n := range chunkCounts {
ev := eventWithChunks(n)
dst := make([]byte, 0, ev.SizeVT()*2)
b.Run(fmt.Sprintf("proto+copy/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
data, err := proto.Marshal(ev)
if err != nil {
b.Fatal(err)
}
dst = dst[:len(data)]
copy(dst, data)
}
})
b.Run(fmt.Sprintf("vt-sized/chunks=%d", n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
size := ev.SizeVT()
dst = dst[:size]
if _, err := ev.MarshalToSizedBufferVT(dst); err != nil {
b.Fatal(err)
}
}
})
}
}
// The log position fields must survive VT round-trips, or a VT-marshaled
// response silently loses the version the mount fences handles with.
func TestVtProtoPreservesLogTsNs(t *testing.T) {
messages := []interface {
MarshalVT() ([]byte, error)
}{
&LookupDirectoryEntryResponse{LogTsNs: 42},
&CacheRemoteObjectToLocalClusterResponse{LogTsNs: 43},
&CreateEntryResponse{LogTsNs: 44},
&UpdateEntryResponse{LogTsNs: 45},
}
for i, m := range messages {
data, err := m.MarshalVT()
if err != nil {
t.Fatalf("MarshalVT %d: %v", i, err)
}
switch v := m.(type) {
case *LookupDirectoryEntryResponse:
out := &LookupDirectoryEntryResponse{}
if err := out.UnmarshalVT(data); err != nil || out.LogTsNs != v.LogTsNs {
t.Fatalf("LookupDirectoryEntryResponse round-trip LogTsNs = %d, %v; want %d", out.LogTsNs, err, v.LogTsNs)
}
case *CacheRemoteObjectToLocalClusterResponse:
out := &CacheRemoteObjectToLocalClusterResponse{}
if err := out.UnmarshalVT(data); err != nil || out.LogTsNs != v.LogTsNs {
t.Fatalf("CacheRemoteObjectToLocalClusterResponse round-trip LogTsNs = %d, %v; want %d", out.LogTsNs, err, v.LogTsNs)
}
case *CreateEntryResponse:
out := &CreateEntryResponse{}
if err := out.UnmarshalVT(data); err != nil || out.LogTsNs != v.LogTsNs {
t.Fatalf("CreateEntryResponse round-trip LogTsNs = %d, %v; want %d", out.LogTsNs, err, v.LogTsNs)
}
case *UpdateEntryResponse:
out := &UpdateEntryResponse{}
if err := out.UnmarshalVT(data); err != nil || out.LogTsNs != v.LogTsNs {
t.Fatalf("UpdateEntryResponse round-trip LogTsNs = %d, %v; want %d", out.LogTsNs, err, v.LogTsNs)
}
}
}
}