summaryrefslogtreecommitdiff
path: root/internal/syscall_aggregate_schema_test.go
blob: a29d5c52d823a634b7ff04693250a895b5a844ee (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
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)
	}
}