diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-23 20:19:36 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-23 20:19:36 +0300 |
| commit | 933d55996f826e1adfd839d3a24f97003f97f374 (patch) | |
| tree | d10396adc06881334615bd5574cbd85df61a4401 /internal/syscall_aggregate_schema_test.go | |
| parent | f12756eecf2ea791ad894cc63380ed78f22f8797 (diff) | |
6c add schema drift test for BPF aggregate map struct
The C struct syscall_aggregate (maps.h) is manually mirrored as
rawSyscallAggregate in Go. Add a test that parses the C definition and
asserts field names, types, sizes, and offsets match the Go struct so
any future schema change is caught at test time.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Diffstat (limited to 'internal/syscall_aggregate_schema_test.go')
| -rw-r--r-- | internal/syscall_aggregate_schema_test.go | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/internal/syscall_aggregate_schema_test.go b/internal/syscall_aggregate_schema_test.go new file mode 100644 index 0000000..a29d5c5 --- /dev/null +++ b/internal/syscall_aggregate_schema_test.go @@ -0,0 +1,111 @@ +package internal + +import ( + "encoding/binary" + "os" + "path/filepath" + "runtime" + "testing" + "unsafe" + + "ior/internal/generate" +) + +// TestSyscallAggregateSchemaMatchesCStruct asserts that the Go +// rawSyscallAggregate struct mirrors the C struct syscall_aggregate defined in +// internal/c/maps.h. If a field is added, removed, or reordered in either +// definition without updating the other, this test fails and prevents silent +// decode corruption at runtime. +func TestSyscallAggregateSchemaMatchesCStruct(t *testing.T) { + cStruct := parseSyscallAggregateFromMapsH(t) + goSize := int(unsafe.Sizeof(rawSyscallAggregate{})) + binarySize := binary.Size(rawSyscallAggregate{}) + + // The C struct has 5 scalar fields + 1 array field = 6 members. + wantFields := []struct { + cField string + cType string + cArray string + }{ + {"count", "__u64", ""}, + {"errors", "__u64", ""}, + {"total_duration_ns", "__u64", ""}, + {"min_duration_ns", "__u64", ""}, + {"max_duration_ns", "__u64", ""}, + {"duration_histogram", "__u64", "8"}, + } + + if len(cStruct.Members) != len(wantFields) { + t.Fatalf("C struct syscall_aggregate has %d fields, want %d; did a field change?", + len(cStruct.Members), len(wantFields)) + } + + for i, want := range wantFields { + got := cStruct.Members[i] + if got.FieldName != want.cField { + t.Errorf("field %d: C name = %q, want %q", i, got.FieldName, want.cField) + } + if got.TypeName != want.cType { + t.Errorf("field %d (%s): C type = %q, want %q", i, want.cField, got.TypeName, want.cType) + } + if got.ArraySize != want.cArray { + t.Errorf("field %d (%s): C array size = %q, want %q", i, want.cField, got.ArraySize, want.cArray) + } + } + + // Total size: 5 x uint64 + 8 x uint64 = 13 x 8 = 104 bytes. + wantSize := 13 * 8 + if goSize != wantSize { + t.Errorf("unsafe.Sizeof(rawSyscallAggregate) = %d, want %d", goSize, wantSize) + } + if binarySize != wantSize { + t.Errorf("binary.Size(rawSyscallAggregate) = %d, want %d", binarySize, wantSize) + } + + // Verify field offsets match C layout expectations (packed, no padding + // needed since all fields are uint64-aligned). + assertOffset(t, "Count", unsafe.Offsetof(rawSyscallAggregate{}.Count), 0) + assertOffset(t, "Errors", unsafe.Offsetof(rawSyscallAggregate{}.Errors), 8) + assertOffset(t, "TotalDuration", unsafe.Offsetof(rawSyscallAggregate{}.TotalDuration), 16) + assertOffset(t, "MinDuration", unsafe.Offsetof(rawSyscallAggregate{}.MinDuration), 24) + assertOffset(t, "MaxDuration", unsafe.Offsetof(rawSyscallAggregate{}.MaxDuration), 32) + assertOffset(t, "Histogram", unsafe.Offsetof(rawSyscallAggregate{}.Histogram), 40) +} + +// parseSyscallAggregateFromMapsH reads internal/c/maps.h and returns the +// parsed C struct definition for syscall_aggregate. Fails the test if the +// struct is not found. +func parseSyscallAggregateFromMapsH(t *testing.T) generate.CStruct { + t.Helper() + + _, filename, _, ok := runtime.Caller(0) + if !ok { + t.Fatal("runtime.Caller failed") + } + mapsH := filepath.Join(filepath.Dir(filename), "c", "maps.h") + f, err := os.Open(mapsH) + if err != nil { + t.Fatalf("open maps.h: %v", err) + } + defer f.Close() + + structs, _, err := generate.ParseCTypesInput(f) + if err != nil { + t.Fatalf("ParseCTypesInput(maps.h): %v", err) + } + + for _, s := range structs { + if s.Name == "syscall_aggregate" { + return s + } + } + t.Fatal("struct syscall_aggregate not found in maps.h") + return generate.CStruct{} +} + +func assertOffset(t *testing.T, field string, got, want uintptr) { + t.Helper() + if got != want { + t.Errorf("rawSyscallAggregate.%s offset = %d, want %d", field, got, want) + } +} |
